Full Code of http-party/http-server for AI

master 0d3b7bb5b6e8 cached
125 files
267.5 KB
99.4k tokens
35 symbols
1 requests
Download .txt
Showing preview only (295K chars total). Download the full file or copy to clipboard to get everything.
Repository: http-party/http-server
Branch: master
Commit: 0d3b7bb5b6e8
Files: 125
Total size: 267.5 KB

Directory structure:
gitextract_g8irr2o3/

├── .dockerignore
├── .eslintrc.json
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.md
│   │   └── feature-request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── node.js.yml
│       ├── release-drafter.yml
│       └── stale.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│   └── http-server
├── doc/
│   └── http-server.1
├── lib/
│   ├── core/
│   │   ├── aliases.json
│   │   ├── etag.js
│   │   ├── index.js
│   │   ├── opts.js
│   │   ├── show-dir/
│   │   │   ├── icons.json
│   │   │   ├── index.js
│   │   │   ├── last-modified-to-string.js
│   │   │   ├── perms-to-string.js
│   │   │   ├── size-to-string.js
│   │   │   ├── sort-files.js
│   │   │   └── styles.js
│   │   └── status-handlers.js
│   ├── http-server.js
│   └── shims/
│       └── https-server-shim.js
├── package.json
├── public/
│   ├── 404.html
│   └── index.html
└── test/
    ├── 304.test.js
    ├── accept-encoding.test.js
    ├── allowed-hosts.test.js
    ├── cache.test.js
    ├── check-headers.js
    ├── cli.test.js
    ├── compression.test.js
    ├── content-type.test.js
    ├── coop.test.js
    ├── core-error.test.js
    ├── core.test.js
    ├── cors.test.js
    ├── custom-content-type-file-secret.test.js
    ├── custom-content-type-file.test.js
    ├── custom-content-type.test.js
    ├── default-default-ext.test.js
    ├── dir-overrides-404.test.js
    ├── enotdir.test.js
    ├── escaping.test.js
    ├── express-error.test.js
    ├── express.test.js
    ├── fixtures/
    │   ├── common-cases-error.js
    │   ├── common-cases.js
    │   ├── custom_mime_type.types
    │   ├── https/
    │   │   ├── agent2-cert.pem
    │   │   └── agent2-key.pem
    │   ├── proxy-all-local/
    │   │   ├── does-not-exist
    │   │   └── file
    │   └── root/
    │       ├── canYouSeeMe
    │       ├── compression/
    │       │   ├── index.html
    │       │   └── index.html.br
    │       ├── file
    │       └── htmlButNot
    ├── force-content-encoding.test.js
    ├── headers.test.js
    ├── illegal-access-date.test.js
    ├── localhost.test.js
    ├── main.test.js
    ├── malformed-dir.test.js
    ├── malformed.test.js
    ├── mime.test.js
    ├── network-interfaces.test.js
    ├── pathname-encoding.test.js
    ├── private-network-access.test.js
    ├── process-env-port.test.js
    ├── proxy-all.test.js
    ├── proxy-config.test.js
    ├── proxy-options.test.js
    ├── public/
    │   ├── 404.html
    │   ├── a.txt
    │   ├── another-subdir/
    │   │   └── scripts.js
    │   ├── b.txt
    │   ├── brotli/
    │   │   ├── fake_ecstatic
    │   │   ├── fake_ecstatic.br
    │   │   ├── index.html
    │   │   ├── index.html.br
    │   │   ├── not_actually_brotli.br
    │   │   ├── real_ecstatic
    │   │   └── real_ecstatic.br
    │   ├── c.js
    │   ├── charset/
    │   │   ├── arabic.html
    │   │   └── shift_jis.html
    │   ├── compress/
    │   │   ├── foo.js
    │   │   └── foo_2.js
    │   ├── curimit@gmail.com (40%)/
    │   │   └── index.html
    │   ├── custom_mime_type.opml
    │   ├── custom_mime_type.types
    │   ├── d.js
    │   ├── dir-overrides-404/
    │   │   ├── 404.html
    │   │   └── directory/
    │   │       └── file.txt
    │   ├── e.js
    │   ├── f_f
    │   ├── gzip/
    │   │   ├── fake_ecstatic
    │   │   ├── index.html
    │   │   └── real_ecstatic
    │   ├── show-dir$$href_encoding$$/
    │   │   └── aname+aplus.txt
    │   ├── subdir/
    │   │   ├── app.wasm
    │   │   ├── e.html
    │   │   └── index.html
    │   ├── subdir_with space/
    │   │   ├── file_with space.html
    │   │   └── index.html
    │   └── 中文/
    │       └── 檔案.html
    ├── range.test.js
    ├── showdir-href-encoding.test.js
    ├── showdir-search-encoding.test.js
    ├── showdir-with-spaces.test.js
    ├── timeout.test.js
    ├── trailing-slash.test.js
    └── websocket-proxy.test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
node_modules
yarn-error.log
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
testdir/privatefile


================================================
FILE: .eslintrc.json
================================================
{
  "extends": "eslint-config-populist",
  "rules": {
    "strict": "warn",
    "indent": ["warn", 2],
    "valid-jsdoc": "warn",
    "no-undefined": "warn",
    "comma-dangle": "warn",
    "callback-return": ["warn", ["next"]]
  }
}


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to http-server

> Please read these guidelines before submitting an issue, filing a feature request, or contributing code.

## :bug: I Found a Bug

Sorry! It happens to the best of us. If you've found a bug in http-server, **please [search](https://github.com/http-party/http-server/issues/) to see if it's already been reported**. Otherwise, create a [new issue](https://github.com/http-party/http-server/issues/new). If you can fix the bug yourself, feel free to create a [pull request](#propose-a-change) thereafter.

Please include _as much detail as possible_ to help us reproduce and diagnose the bug. Most importantly:

- Make use of the issue template!
- Let us know _how_ you're running http-server (options, flags, environment, etc.)
- Include your test code or file(s). If large, please provide a link to a repository or [gist](https://gist.github.com).
- Please show code in JavaScript only (any version)

If we need more information from you, we'll let you know. If you don't within a reasonable time frame (TBD), your issue will be automatically tagged as stale and eventually closed for inactivity.

## :exclamation: Propose a Change

Before you get your hands dirty, please [search](https://github.com/http-party/http-server/issues/) for a related issue, or [create a new one](https://github.com/http-party/http-server/issues/new). If you wish to contribute a new feature, this is doubly important! Let's discuss your proposed changes first; we don't want you to waste time implementing a change that is at odds with the project's direction. That said, we'll happily consider any contribution, no matter how great or small.

### :shoe: Contributing Code: Step-by-Step

Follow these steps to get going.

1. [Install the latest version of Node.js](https://nodejs.org/en/download).
    - If you're new to installing Node, a tool like [nvm](https://github.com/creationix/nvm#install-script) can help you manage multiple version installations.
1. Follow [Github's documentation](https://help.github.com/articles/fork-a-repo/) on setting up Git, forking and cloning.
1. Create a new branch in your fork, giving it a descriptive name
1. Execute `npm install` to install the prod and dev dependencies
   - Do not use `yarn install` for development, as it may not get the same package versions as other developers.
1. Make your changes and add them via `git add`.
   - **Tests are required** for any non-trivial code change. If you're having trouble making tests, go ahead and open the pull request and we can help
   - Keep your PR focused. Don't fix multiple things at once, and don't upgrade dependencies unless necessary.
1. Before committing, run `npm test`
   - Tests will also run on your PR, but running them locally will let you catch problems ahead-of-time.
1. Commit your changes.
   - See [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
   - **Please do not use "Conventional Commits" style**
1. Push your changes to your fork.
1. Now on [http-party/http-server](https://github.com/http-party/http-server), you should see a notification about your recent changes in your fork's branch, with a green button to create a pull request. Click the button.
1. Describe your changes in detail here, following the template. Once you're satisfied, submit the form.
1. Be patient while your PR is reviewed. This can take a while. We may request changes, but don't be afraid to question them.
1. Your PR might become conflicted with the code in `master`. If this is the case, you will need to [update your PR](#up-to-date) and resolve your conflicts.
1. You don't need to make a new PR to any needed changes. Instead, commit on top of your changes, and push these to your fork's branch. The PR will be updated, and CI will re-run.
   - **Please do not rebase and force-push**, it ruins the git history

## :angel: I Just Want To Help

_Excellent._ Here's how:

- **Handy with JavaScript?** Please check out the issues labeled [`help-wanted`](https://github.com/http-party/http-server/issues?q=is%3Aopen+is%3Aissue+label%3A%22help-wanted%22) or [`good first issue`](https://github.com/http-party/http-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Agood+first+issue). 
- **Wait--you write unit tests for _fun_?** A PR which increases coverage is unlikely to ever be turned down.


================================================
FILE: .github/FUNDING.yml
================================================
github: thornjad


================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug report
about: Create a report to help us improve
---

<!-- Describe the issue briefly here. -->

#### Environment Versions

1. OS Type
1. Node version: `$ node --version`
1. http-server version: `$ http-server --version`

#### Steps to reproduce

<!-- Include the actual command -->

1. ...
2. ...
3. ...

#### Expected result

...

#### Actual result

<!-- Include full output and/or stack trace -->

...

#### Other information

<!-- Include related issues, suggestions for a fix or further debug information, etc. -->


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
---

#### What's the problem this feature will solve?

<!-- What are you trying to do, that you are unable to achieve with http-server as it currently stands? -->

#### Describe the solution you'd like

<!-- A clear and concise description of what you want to happen. -->

<!-- Provide examples of real-world use cases that this would enable and how it solves the problem described above. -->

#### Alternative Solutions

<!-- Have you tried to workaround the problem using http-server or other tools? Or a different approach to solving this issue? Please elaborate here. -->

#### Additional context

<!-- Add any other context, links, etc. about the feature here. -->


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--- Describe the changes here. --->

##### Relevant issues

<!--
    Link to the issue(s) this pull request fixes here, if applicable: "Fixes #xxx" or "Resolves #xxx"
    
    If your PR fixes multiple issues, list them individually like "Fixes #xx1, fixes #xx2, fixes #xx3". This is a quirk of how GitHub links issues.
-->

##### Contributor checklist

- [ ] Provide tests for the changes (unless documentation-only)
- [ ] Documented any new features, CLI switches, etc. (if applicable)
    - [ ] Server `--help` output
    - [ ] README.md
    - [ ] doc/http-server.1 (use the same format as other entries)
- [ ] The pull request is being made against the `master` branch

##### Maintainer checklist

- [ ] Assign a version triage tag
- [ ] Approve tests if applicable


================================================
FILE: .github/release-drafter.yml
================================================
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'

change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&'

categories:
  - title: 'Breaking changes'
    labels:
      - 'major version'
  - title: 'Features and enhancements'
    labels:
      - 'feature'
      - 'enhancement'
  - title: 'Bug Fixes'
    labels:
      - 'fix'
      - 'bug'
  - title: 'Other changes'
    labels:
      - 'dependencies'
      - 'documentation'

exclude-labels:
  - "skip-changelog"
  - "maintenance"
  - "trivial"

version-resolver:
  major:
    labels:
      - 'major version'
  minor:
    labels:
      - 'minor version'
  patch:
    labels:
      - 'patch version'
  default: patch

template: |
  $CHANGES


================================================
FILE: .github/workflows/node.js.yml
================================================
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:
    name: Test
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        node-version: [20.x, 22.x, 24.x]
        os: [ubuntu-latest, macOS-latest, windows-latest]

    steps:
    - uses: actions/checkout@v4.2.2
    - name: Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
      uses: actions/setup-node@v4.1.0
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npx npm@7 ci
    - run: npm test


================================================
FILE: .github/workflows/release-drafter.yml
================================================
name: release-drafter

on:
  push:
    # branches to consider in the event; optional, defaults to all
    branches:
      - master

jobs:
  update_release_draft:
    permissions:
      contents: write
      pull-requests: read
    if: github.repository == 'http-party/http-server'
    runs-on: ubuntu-latest
    steps:
      # Drafts your next release notes as pull requests are merged into master
      - uses: release-drafter/release-drafter@v5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/stale.yml
================================================
name: Mark stale issues and pull requests

on:
  schedule:
  - cron: '25 12 * * *'

jobs:
  stale:

    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write

    steps:
    - uses: actions/stale@v4.0.0
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        days-before-stale: 360
        days-before-issue-stale: 180
        days-before-pr-stale: 360
        stale-issue-message: 'This issue has been inactive for 180 days'
        stale-pr-message: 'This pull request has been inactive for 360 days'
        stale-issue-label: 'stale'
        stale-pr-label: 'stale'
        exempt-issue-labels: 'no-stale'
        exempt-pr-labels: 'no-stale'
        exempt-all-milestones: true
        days-before-close: -1


================================================
FILE: .gitignore
================================================
node_modules/
npm-debug.log*
.nyc_*/
.dir-locals.el
.DS_Store
.httpserver*
.tap


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
jademichael @ jmthornton.net.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


================================================
FILE: Dockerfile
================================================
FROM node:16-alpine
VOLUME /public
WORKDIR /srv/http-server
COPY package.json package-lock.json ./
RUN npm install --production
COPY . .
EXPOSE 8080
ENTRYPOINT ["node", "./bin/http-server"]


================================================
FILE: LICENSE
================================================
Copyright (c) 2011-2026 Charlie Robbins, Marak Squires, Jade Michael Thornton and the Contributors.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
[![GitHub Workflow Status (master)](https://img.shields.io/github/actions/workflow/status/http-party/http-server/node.js.yml?style=flat-square&branch=master)](https://github.com/http-party/http-server/actions)
[![npm](https://img.shields.io/npm/v/http-server.svg?style=flat-square)](https://www.npmjs.com/package/http-server) [![homebrew](https://img.shields.io/homebrew/v/http-server?style=flat-square)](https://formulae.brew.sh/formula/http-server) [![npm downloads](https://img.shields.io/npm/dm/http-server?color=blue&label=npm%20downloads&style=flat-square)](https://www.npmjs.com/package/http-server)
[![license](https://img.shields.io/github/license/http-party/http-server.svg?style=flat-square)](https://github.com/http-party/http-server/blob/master/LICENSE)

# http-server: a simple static HTTP server

`http-server` is a simple, zero-configuration command-line static HTTP server.  It is powerful enough for production usage, but it's simple and hackable enough to be used for testing, local development and learning.

![Example of running http-server](https://github.com/http-party/http-server/raw/master/screenshots/public.png)

## Installation:

#### Running on-demand:

Using `npx` you can run the script without installing it first:

    npx http-server [path] [options]

#### Globally via `npm`

    npm install --global http-server

This will install `http-server` globally so that it may be run from the command line anywhere.

#### Globally via Homebrew

    brew install http-server
     
#### As a dependency in your `npm` package:

    npm install http-server

#### Using Docker

Note: a public image is not provided currently, but you can build one yourself
with the provided Dockerfile.

1. Create an image
   ```
   docker build -t my-image .
   ```
2. Run a container
   ```
   docker run -p 8080:8080 -v "${pwd}:/public" my-image
   ```
   In the example above we're serving the directory `./` (working directory).
   If you wanted to serve `./test` you'd replace `${pwd}` with `${pwd}/test`.

## Usage:

     http-server [path] [options]

`[path]` defaults to `./public` if the folder exists, and `./` otherwise.

*Now you can visit http://localhost:8080 to view your server*

**Note:** Caching is on by default. Add `-c-1` as an option to disable caching.

## Available Options:

| Command         | 	Description         | Defaults  |
| -------------  |-------------|-------------|
|`-p` or `--port` |Port to use. Use `-p 0` to look for an open port, starting at 8080. It will also read from `process.env.PORT`. |8080 |
|`-a`   |Address to use |0.0.0.0|
|`--base-dir` | Base path to serve files from | `/` |
|`-d`     |Show directory listings |`true` |
|`-dir-overrides-404` | Whether `-d` should override magic `404.html` | `false`
|`-i`   | Display autoIndex | `true` |
|`-g` or `--gzip` |When enabled it will serve `./public/some-file.js.gz` in place of `./public/some-file.js` when a gzipped version of the file exists and the request accepts gzip encoding. If brotli is also enabled, it will try to serve brotli first.|`false`|
|`-b` or `--brotli`|When enabled it will serve `./public/some-file.js.br` in place of `./public/some-file.js` when a brotli compressed version of the file exists and the request accepts `br` encoding. If gzip is also enabled, it will try to serve brotli first. |`false`|
|`-e` or `--ext`  |Default file extension if none supplied |`html` | 
|`-s` or `--silent` |Suppress log messages from output  | |
|`--coop` |Enable COOP via the `Cross-Origin-Opener-Policy` header  | |
|`--cors` |Enable CORS via the `Access-Control-Allow-Origin` header  | |
|`--private-network-access` |Enable Private Network Access via the `Access-Control-Allow-Private-Network` header  | |
|`--cors` | Enable CORS via the `Access-Control-Allow-Origin: *` header. Optionally provide comma-separated values to add to `Access-Control-Allow-Headers`  | |
|`-H` or `--header` |Add an extra response header (can be used several times)  | |
|`-o [path]` |Open browser window after starting the server. Optionally provide a URL path to open. e.g.: -o /other/dir/ | |
|`-c` |Set cache time (in seconds) for cache-control max-age header, e.g. `-c10` for 10 seconds. To disable caching, use `-c-1`.|`3600` |
|`-t` |Connection timeout in seconds, e.g. `-t60` for 1 minute. To disable timeout, use `-t0`.|`120` |
|`-T` or `--title` |Custom title suffix for the terminal window. The title will be "http-server PORT [TITLE]".| |
|`-U` or `--utc` |Use UTC time format in log messages.| |
|`--log-ip` |Enable logging of the client's IP address |`false` |
|`-P` or `--proxy` |Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com | |
|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false | |
|`--proxy-config` |Pass in `.json` configuration file or stringified JSON. e.g.: `./path/to/config.json` | |
|`--proxy-all` |Forward every request to the proxy target instead of serving local files|`false`|
|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false |
|`--user` or `--username` |Username for basic authentication | |
|`--password` |Password for basic authentication | |
|`-S`, `--tls` or `--ssl` |Enable secure request serving with TLS/SSL (HTTPS)|`false`|
|`-C` or `--cert` |Path to ssl cert file |`cert.pem` |
|`-K` or `--key` |Path to ssl key file |`key.pem` |
|`-r` or `--robots` | Automatically provide a /robots.txt (The content of which defaults to `User-agent: *\nDisallow: /`)  | `false` |
|`--no-dotfiles` |Do not show dotfiles| |
|`--mimetypes` |Path to a .types file for custom mimetype definition| |
|`--hide-permissions` |Do not show file permissions| |
|`--allowed-hosts` |Comma-separated list of hosts allowed to access the server. e.g.: `--allowed-hosts localhost,example.com`| |
|`-h` or `--help` |Print this list and exit. |   |
|`-v` or `--version`|Print the version and exit. | |
| `--no-panic` | Don't print error stack in the console, put it in a log file | `false`|

## Magic Files

- `index.html` will be served as the default file to any directory requests.
- `404.html` will be served if a file is not found. This can be used for Single-Page App (SPA) hosting to serve the entry page.

## Catch-all redirect

To implement a catch-all redirect, use the index page itself as the proxy with:

```
http-server --proxy http://localhost:8080?
```

Note the `?` at the end of the proxy URL. Thanks to [@houston3](https://github.com/houston3) for this clever hack!

## TLS/SSL

First, you need to make sure that [openssl](https://github.com/openssl/openssl) is installed correctly, and you have `key.pem` and `cert.pem` files. You can generate them using this command:

``` sh
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
```

You will be prompted with a few questions after entering the command. Use `127.0.0.1` as value for `Common name` if you want to be able to install the certificate in your OS's root certificate store or browser so that it is trusted.

This generates a cert-key pair and it will be valid for 3650 days (about 10 years).

Then you need to run the server with `-S` for enabling SSL and `-C` for your certificate file.

``` sh
http-server -S -C cert.pem
```

If you wish to use a passphrase with your private key you can include one in the openssl command via the -passout parameter (using password of foobar)


e.g.
`openssl req -newkey rsa:2048 -passout pass:foobar -keyout key.pem -x509 -days 365 -out cert.pem`

For security reasons, the passphrase will only be read from the `NODE_HTTP_SERVER_SSL_PASSPHRASE` environment variable.


This is what should be output if successful:

``` sh
Starting up http-server, serving ./ through https

http-server settings:
COOP: disabled
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none

Available on:
  https://127.0.0.1:8080
  https://192.168.1.101:8080
  https://192.168.1.104:8080
Hit CTRL-C to stop the server
```

# Development

Checkout this repository locally, then:

```sh
$ npm i
$ npm start
```

*Now you can visit http://localhost:8080 to view your server*

You should see the turtle image in the screenshot above hosted at that URL. See
the `./public` folder for demo content.


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported |
|---------|------------------------|
| 14.x  | ✔️ Yes |
| 13.x  | 🔐 Security updates until April 2022 |
| <= 0.13.x  | ❌ No  |

## Reporting a Vulnerability

In general, vulnerabilities can be reported as an issue, pull requests are very welcome. If you'd like to report privately, please email jademichael+http-server@jmthornton.net.


================================================
FILE: bin/http-server
================================================
#!/usr/bin/env node

'use strict';

var chalk     = require('chalk'),
  os         = require('os'),
  httpServer = require('../lib/http-server'),
  portfinder = require('portfinder'),
  opener     = require('opener'),
  fs         = require('fs'),
  url        = require('url');
var argv = require('minimist')(process.argv.slice(2), {
  alias: {
    tls: 'ssl',
    header: 'H',
    user: 'username',
  },
  boolean: ['proxy-all']
});
var ifaces = os.networkInterfaces();

if (argv.h || argv.help) {
  console.log([
    'usage: http-server [path] [options]',
    '',
    'options:',
    '  -p --port    Port to use. If 0, look for open port. [8080]',
    '  -a           Address to use [0.0.0.0] or [::]',
    '  -d           Show directory listings [true]',
    '  --dir-overrides-404   Whether -d should override magic 404.html [false]',
    '  --base-dir   Base directory to serve files from [/]',
    '  -i           Display autoIndex [true]',
    '  -g --gzip    Serve gzip files when possible [false]',
    '  -b --brotli  Serve brotli files when possible [false]',
    '               If both brotli and gzip are enabled, brotli takes precedence',
    '',
    '  --force-content-encoding',
    '               When using --gzip or --brotli, includes the content encoding',
    '               header even when the extension for the compressed file is',
    '               specified in the URL. "test.png.br" will be served the same',
    '               way as "test.png".',
    '',
    '  -e --ext     Default file extension if none supplied [none]',
    '  -s --silent  Suppress log messages from output',
    '  --coop[=mode]   Enable COOP via the "Cross-Origin-Opener-Policy" header',
    '                  Optionally provide COOP mode.',
    '  --content-type     Default content type for unknown file types [application/octet-stream]',
    '  --cors[=headers]   Enable CORS via the "Access-Control-Allow-Origin" header',
    '                     Optionally provide CORS headers list separated by commas',
    '  --private-network-access Enable Private Network Access via the',
    '                           "Access-Control-Allow-Private-Network" header',
    '                     When enabled, sets Access-Control-Allow-Origin to "*"',
    '                     Optional value adds to Access-Control-Allow-Headers',
    '  -H',
    '  --header',
    '               Add an extra response header (can be used several times)',
    '  -o [path]    Open browser window after starting the server.',
    '               Optionally provide a URL path to open the browser window to.',
    '  -c           Cache time (max-age) in seconds [3600], e.g. -c10 for 10 seconds.',
    '               To disable caching, use -c-1.',
    '  -t           Connection timeout in seconds [120], e.g. -t60 for 1 minute.',
    '               To disable timeout, use -t0.',
    '  -T --title   Custom title suffix for the terminal window [none]',
    '               The terminal title will be "http-server PORT [TITLE]"',
    '  -U --utc     Use UTC time format in log messages.',
    '  --log-ip     Enable logging of the client\'s IP address',
    '',
    '  -P --proxy       Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com',
    '  --proxy-all      Send every request to the proxy target instead of serving local files',
    '  --proxy-options  Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false',
    '  --proxy-config   Pass in .json configuration file. e.g.: ./path/to/config.json',
    '  --websocket      Enable websocket proxy',
    '',
    '  --user --username   Username for basic authentication [none]',
    '                      Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME',
    '  --password   Password for basic authentication [none]',
    '               Can also be specified with the env variable NODE_HTTP_SERVER_PASSWORD',
    '',
    '  -S --tls --ssl   Enable secure request serving with TLS/SSL (HTTPS)',
    '  -C --cert    Path to TLS cert file (default: cert.pem)',
    '  -K --key     Path to TLS key file (default: key.pem)',
    '',
    '  -r --robots        Respond to /robots.txt [User-agent: *\\nDisallow: /]',
    '  --no-dotfiles      Do not show dotfiles',
    '  --hide-permissions Do not show file permissions',
    '  --mimetypes        Path to a .types file for custom mimetype definition',
    '  -h --help          Print this list and exit.',
    '  -v --version       Print the version and exit.',
    '  --no-panic         If error occurs, gracefully shut down and create log file',
    '                     Can also be specified with the env variable NODE_HTTP_SERVER_NO_PANIC',
    '  --allowed-hosts    Comma-separated list of hosts allowed to access the server. e.g.: --allowed-hosts localhost,example.com',
  ].join('\n'));
  process.exit();
}

var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
    nopanic = !argv['panic'] || argv.n || process.env.NODE_HTTP_SERVER_NO_PANIC,
    host = argv.a || '::',
    tls = argv.S || argv.tls,
    title = argv.T || argv.title,
    sslPassphrase = process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE,
    proxy = argv.P || argv.proxy,
    proxyOptions = argv['proxy-options'],
    proxyConfig = argv['proxy-config'],
    websocket = argv.websocket,
    proxyAll = Boolean(argv['proxy-all']),
    utc = argv.U || argv.utc,
    version = argv.v || argv.version,
    baseDir = argv['base-dir'],
    logger,
    allowedHosts = argv['allowed-hosts'];

if (nopanic){
  process.on('error', (e)=> {
    // Results in a string like "2021-12-27 14:56:31"
    const etime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
    console.log(chalk.green(etime));
    console.log(`${chalk.red('Fatal error: ')}${e.code}: ${e.message}`);
    const filename = `httpserver-${etime.split(' ').join('_')}.log`;
    console.log(chalk.bold(`Check ${filename} file in this folder.`));
    fs.writeFileSync(filename, JSON.stringify(e));
    process.exit(1);
  });
}

var proxyOptionsBooleanProps = [
  'ws', 'xfwd', 'secure', 'toProxy', 'prependPath', 'ignorePath', 'changeOrigin',
  'preserveHeaderKeyCase', 'followRedirects', 'selfHandleResponse'
];

if (proxyOptions) {
  Object.keys(proxyOptions).forEach(function (key) {
    if (proxyOptionsBooleanProps.indexOf(key) > -1) {
      proxyOptions[key] = proxyOptions[key].toLowerCase() === 'true';
    }
  });
}

if (!argv.s && !argv.silent) {
  logger = {
    info: console.log,
    warning: console.warn,
    request: function (req, res, error) {
      var date = utc ? new Date().toUTCString() : new Date();
      var ip = argv['log-ip']
        ? req.headers['x-forwarded-for'] || '' +  req.connection.remoteAddress
        : '';
      if (error) {
        logger.info(
          '[%s] %s "%s %s" Error (%s): "%s"',
          date, ip, chalk.red(req.method), chalk.red(req.url),
          chalk.red(error.status.toString()), chalk.red(error.message)
        );
      }
      else if (req.proxy) {
        logger.info(
          '[%s] %s "%s" (%s)-> "%s"',
          date, ip, chalk.cyan(req.url), chalk.magenta('Proxy'), chalk.cyan(req.proxy.target)
        );
      } else {
        logger.info(
          '[%s] %s "%s %s" "%s"',
          date, ip, chalk.cyan(req.method), chalk.cyan(req.url),
          req.headers['user-agent']
        );
      }
    }
  };
} else if (chalk) {
  logger = {
    info: function () {},
    request: function () {}
  };
}

if (version) {
  logger.info('v' + require('../package.json').version);
  process.exit();
}

if (!port) {
  portfinder.basePort = 8080;
  portfinder.getPort(function (err, port) {
    if (err) { throw err; }
    listen(port);
  });
} else {
  listen(port);
}

if (allowedHosts && typeof allowedHosts === 'string') {
  allowedHosts = allowedHosts.split(',').map((host) => host.trim().toLowerCase());
} else {
  allowedHosts = undefined;
}

function listen(port) {
  var options = {
    root: argv._[0],
    cache: argv.c,
    timeout: argv.t,
    showDir: argv.d,
    dirOverrides404: argv['dir-overrides-404'],
    baseDir: baseDir,
    autoIndex: argv.i,
    gzip: argv.g || argv.gzip,
    brotli: argv.b || argv.brotli,
    robots: argv.r || argv.robots,
    ext: argv.e || argv.ext,
    logFn: logger.request,
    proxy: proxy,
    proxyOptions: proxyOptions,
    proxyConfig: proxyConfig,
    proxyAll: proxyAll,
    showDotfiles: argv.dotfiles,
    hidePermissions: argv['hide-permissions'],
    mimetypes: argv.mimetypes,
    contentType: argv['content-type'],
    username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
    password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD,
    headers: {},
    allowedHosts,
  };

  function setHeader(str) {
    const m = /^(.+?)\s*(:\s*(.*))$/.exec(str);
    if (!m || m.length < 4) {
      options.headers[str] = '';
    } else {
      options.headers[m[1]] = m[3];
    }
  }

  if (argv.coop) {
    options.coop = true;
    if (typeof argv.coop === 'string') {
      options.coopHeader = argv.coop;
    }
  }

  if (websocket) {
    if (!proxy) {
      logger.warning(chalk.yellow('WebSocket proxy will not be enabled because proxy is not enabled'));
    } else {
      options.websocket = true;
    }
  }

  if (argv.cors) {
    options.cors = true;
    if (typeof argv.cors === 'string') {
      options.corsHeaders = argv.cors;
    }
  }
  
  if ( argv['force-content-encoding'] ) {
    options.forceContentEncoding = true;
  }

  if (argv.header) {
    if (Array.isArray(argv.header)) {
      argv.header.forEach(h => setHeader(h));
    }
    else {
      setHeader(argv.header);
    }
  }

  if (argv['private-network-access']) {
    options.privateNetworkAccess = true;
  }

  if (proxy) {
    try {
      new url.URL(proxy);
    } catch (err) {
      logger.info(chalk.red('Error: Invalid proxy url'));
      process.exit(1);
    }
  }

  if (proxyConfig) {
    try {
      if (fs.existsSync(proxyConfig)) {
        proxyConfig = fs.readFileSync(proxyConfig, 'utf8');
      }
      if (typeof proxyConfig === 'string') {
        proxyConfig = JSON.parse(proxyConfig);
      }
      if (typeof proxyConfig !== 'object') {
        throw new Error('Invalid proxy config');
      }
    }
    catch (err) {
      logger.info(chalk.red('Error: Invalid proxy config or file'));
      process.exit(1);
    }
    // Proxy file overrides cli config
    proxy = undefined;
    proxyOptions = undefined;
  }

  if (proxyAll && proxyConfig) {
    logger.info(chalk.red('Error: --proxy-all cannot be used with --proxy-config'));
    logger.info(
      '%s\n%s\n%s',
      chalk.yellow('Hint: Use'),
      chalk.cyan('"/**": {\n  "target": "your-proxy"\n}'),
      chalk.yellow('in the proxy config to achieve the same effect.')
    );
    process.exit(1);
  }

  if (proxyAll && !proxy) {
    logger.info(chalk.red('Error: --proxy-all requires --proxy to be set'));
    process.exit(1);
  }

  if (tls) {
    options.https = {
      cert: argv.C || argv.cert || 'cert.pem',
      key: argv.K || argv.key || 'key.pem',
      passphrase: sslPassphrase
    };
    try {
      fs.lstatSync(options.https.cert);
    } catch (err) {
      logger.info(chalk.red('Error: Could not find certificate ' + options.https.cert));
      process.exit(1);
    }
    try {
      fs.lstatSync(options.https.key);
    } catch (err) {
      logger.info(chalk.red('Error: Could not find private key ' + options.https.key));
      process.exit(1);
    }
  }

  var server = httpServer.createServer(options);
  server.listen(port, host, function () {
    // Set process title with port and optional custom suffix
    process.title = 'http-server ' + port + (title ? ' ' + title : '');

    var protocol = tls ? 'https://' : 'http://',
      path = baseDir ? '/' + baseDir.replace(/^\//, '') : '';

    logger.info([
      chalk.yellow('Starting up http-server, serving '),
      chalk.cyan(server.root),
      tls ? (chalk.yellow(' through') + chalk.cyan(' https')) : ''
    ].join(''));

    logger.info([chalk.yellow('\nhttp-server version: '), chalk.cyan(require('../package.json').version)].join(''));

    logger.info([
      chalk.yellow('\nhttp-server settings: '),
      ([chalk.yellow('COOP: '), argv.coop ? chalk.cyan(argv.coop) : chalk.red('disabled')].join('')),
      ([chalk.yellow('CORS: '), argv.cors ? chalk.cyan(argv.cors) : chalk.red('disabled')].join('')),
      ([chalk.yellow('Private Network Access: '), argv['private-network-access'] ? chalk.cyan(argv['private-network-access']) : chalk.red('disabled')].join('')),
      ([chalk.yellow('Cache: '), argv.c ? (argv.c === '-1' ? chalk.red('disabled') : chalk.cyan(argv.c + ' seconds')) : chalk.cyan('3600 seconds')].join('')),
      ([chalk.yellow('Connection Timeout: '), Math.max(0, argv.t) === 0 ? chalk.red('disabled') :
        ((!isNaN(argv.t) && !isNaN(parseFloat(argv.t))) ?
          chalk.cyan(Number(argv.t) + ' seconds') : chalk.cyan('120 seconds'))].join('')),
      ([chalk.yellow('Directory Listings: '), argv.d ? chalk.red('not visible') : chalk.cyan('visible')].join('')),
      ([chalk.yellow('AutoIndex: '), argv.i ? chalk.red('not visible') : chalk.cyan('visible')].join('')),
      ([chalk.yellow('Serve GZIP Files: '), argv.g || argv.gzip ? chalk.cyan('true') : chalk.red('false')].join('')),
      ([chalk.yellow('Serve Brotli Files: '), argv.b || argv.brotli ? chalk.cyan('true') : chalk.red('false')].join('')),
      ([chalk.yellow('Default File Extension: '), argv.e ? chalk.cyan(argv.e) : (argv.ext ? chalk.cyan(argv.ext) : chalk.red('none'))].join('')),
      ([chalk.yellow('Base directory: '), baseDir ? chalk.cyan(baseDir) : chalk.cyan('/')].join(''))
    ].join('\n'));

    if (options.headers) {
      logger.info(chalk.yellow('Additional Headers:'));
      for (let k in options.headers) {
        let v = options.headers[k];
        logger.info(chalk.yellow(`\t${k}:`) + chalk.cyan(` ${v}`));
      }
    }

    logger.info(chalk.yellow('\nAvailable on:'));

    if (allowedHosts) {
      for (const host of allowedHosts) {
        logger.info(`  ${protocol}${host}:${chalk.green(port.toString())}${path}`);
      }
    } else if (argv.a && (host !== '::' || host !== '0.0.0.0')) {
      logger.info(`  ${protocol}${host}:${chalk.green(port.toString())}${path}`);
    } else {
      Object.keys(ifaces).forEach(function (dev) {
        ifaces[dev].forEach(function (details) {
          if (details.family === 'IPv4' || details.family === 4) {
            logger.info(('  ' + protocol + details.address + ':' + chalk.green(port.toString()) + path));
          }
          if (details.family === 'IPv6' && !details.address.startsWith("fe80") ) { // Ignoring Ipv6-Link Local addresses
            logger.info(('  ' + protocol + details.address + ':' + chalk.green(port.toString())));
          }
        });
      });
    }

    if (typeof proxy === 'string') {
      if (proxyOptions) {
        logger.info('Unhandled requests will be served from: ' + proxy + '. Options: ' + JSON.stringify(proxyOptions));
      } else {
        logger.info('Unhandled requests will be served from: ' + proxy);
      }
    }

    // Set up "CTRL-C" hook, before printing out "Hit CTRL-C to stop the server"
    function stopServer() {
      server.close();
      logger.info(chalk.red('http-server stopped.'));
      process.exit();
    }

    process.on('SIGINT', stopServer);
    process.on('SIGTERM', stopServer);

    if (process.platform === 'win32') {
      require('readline').createInterface({
        input: process.stdin,
        output: process.stdout
      }).on('SIGINT', function () {
        process.emit('SIGINT');
      });
    }

    logger.info('Hit CTRL-C to stop the server');

    if (argv.o) {
      let openHost = host
      if ('::' === host || '0.0.0.0'===host){
        openHost = '127.0.0.1'
      }
      let openUrl = `${protocol}${openHost}:${port}`;
      if (typeof argv.o === 'string') {
        openUrl += argv.o[0] === '/' ? argv.o : '/' + argv.o;
      }
      logger.info('Open: ' + openUrl);
      opener(openUrl);
    }

    // Spacing before logs
    if (!argv.s) logger.info();
  });
}


================================================
FILE: doc/http-server.1
================================================
.TH http-server 1 "April 2020" GNU "http-server man page"

.SH NAME
http-server \- a simple zero-configuration command-line http server

.SH SYNOPSIS
.B http-server
[\fIPATH\fR]
[\fIOPTIONS\fR]

.SH DESCRIPTION
\fBhttp-server\fR is a simple, zero-configuration command-line http server. It is powerful enough for production usage, but it's simple and hackable enough to be used for testing, local development, and learning.

.SH OPTIONS

.TP
.BI [\fIPATH\fR]
The directory to serve.
Defaults to ./public if it exists, and ./ otherwise.

.TP
.BI \-p ", " \-\-port " " \fIPORT\fR
Port to use. If 0, look for the first available port, starting at 8080.
Default is 8080.

.TP
.BI \-a " " \fIADDRESS\fR
Address to use.
Default is 0.0.0.0.

.TP
.BI \-d
Show directory listings.
Default is true.

.TP
.BI \-d
Whether -d should override magic 404.html
Default is false.

.TP
.BI \-i
Display autoIndex.
Default is true.

.TP
.BI \-g ", " \-\-gzip
Serve gzip files when possible.
Default is false.

.TP
.BI \-b ", " \-\-brotli
Serve brotli files when possible.
If both brotli and gzip are enabled, brotli takes precedence.
Default is false.

.TP
.BI \-\-force\-content\-encoding
When using --gzip or --brotli, includes the content encoding
header even when the extension for the compressed file is
specified in the URL. "test.png.br" will be served the same
way as "test.png".

.TP
.BI \-e ", " \-\-ext " " \fIEXTENSION\fR
Default file extension is none is provided.

.TP
.BI \-s ", " \-\-silent
Suppress log messages from output.

.TP
.BI \-n ", " \-\-no-panic
Gracefully shut down whenever a fatal error occurs, sending stack to log file, not console.

.TP
.BI \-\-coop " " [\fIMODE\fR]
Enable COOP via the "Cross-Origin-Opener-Policy" header and sets
the "Cross-Origin-Embedder-Policy" header to "require-corp".
Optionally provide COOP mode which defaults to "same-origin".

.TP
.BI \-\-cors " " [\fIHEADERS\fR]
Enable CORS by setting "Access-Control-Allow-Origin" to "*".
Optional comma-separated headers list adds to "Access-Control-Allow-Headers".
Default Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Range.

.TP
.BI \-H ", " \-\-header " " \fIHEADER\fR
Add an extra response header (can be used several times)

.TP
.BI \-\-private-network-access
Enable Private Network Access via the "Access-Control-Allow-Private-Network" header.

.TP
.BI \-o " " [\fIPATH\fR]
Open default browser window after starting the server.
Optionally provide a URL path to open the browser window to.

.TP
.BI \-c " " \fITIME\fR
Cache time (max-age) in seconds.
To disable caching, use \-c \-1.
Default is 3600.

.TP
.BI \-t " " \fITIMEOUT\fR
Connection timeout in seconds, e.g. -t60 for 1 minute.
To disable timeout, use \-t0.
Default is 120.

.TP
.BI \-T ", " \-\-title " " \fITITLE\fR
Custom title suffix for the terminal window.
The terminal title will be "http-server PORT [TITLE]".

.TP
.BI \-U ", " \-\-utc
Use UTC time format in log messages.

.TP
.BI \-\-log\-ip
Enable logging of the client IP address.

.TP
.BI \-P ", " \-\-proxy
Fallback proxy if the request cannot be resolved.

.TP
.BI \-\-proxy\-all
Forward every request to the proxy target and disable local file serving.
Requires \-\-proxy.

.TP
.BI \-\-proxy\-options
Pass proxy options using nested dotted objects.

.TP
.BI \-\-proxy\-config
Pass in .json configuration file.

.TP
.BI \-\-user ", " \-\-username " " \fIUSERNAME\fR
Username for basic authentication.
Can also be specified with the environment variable NODE_HTTP_SERVER_USERNAME.
Defaults to none.

.TP
.BI \-\-password " " \fIPASSWORD\fR
Password for basic authentication.
Can also be specified with the environment variable NODE_HTTP_SERVER_PASSWORD.
Defaults to none.

.TP
.BI \-S ", " \-\-tls ", " \-\-ssl
Enable https.

.TP
.BI \-C ", " \-\-cert " " [\fIFILE\fR]
Path to SSL certificate file.
If not specified, uses cert.pem.

.TP
.BI \-K ", " \-\-key " " [\fIFILE\fR]
Path to SSL key file.
If not specified, uses key.pem.
Passphrase will be read from NODE_HTTP_SERVER_SSL_PASSPHRASE (if set)

.TP
.BI \-r ", " \-\-robots " " [\fIUSER\-AGENT\fR]
Respond to /robots.txt request.
If not specified, uses "User-agent: *\\nDisallow: /]"

.TP
.BI \-\-no\-dotfiles
Do not show dotfiles.

.TP
.BI \-\-hide\-permissions
Do not show file permissions.

.TP
.BI \-\-allowed\-hosts
Comma-separated list of hosts allowed to access the server. e.g.: \-\-allowed\-hosts localhost,example.com

.TP
.BI \-h ", " \-\-help
Print usage and exit.

.TP
.BI \-v ", " \-\-version
Print version and exit.

.SH FILES
.B index.html
will be served as the default file to any directory requests.

.B 404.html
will be served if a file is not found. This can be used for SPA hosting to serve the entry page.

.SH COPYING
Copyright (c) 2011-2022 Charlie Robbins, Marak Squires, and the Contributors.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

.SH VERSION
Version 0.12.2


================================================
FILE: lib/core/aliases.json
================================================
{
  "autoIndex": [ "autoIndex", "autoindex" ],
  "showDir": [ "showDir", "showdir" ],
  "dirOverrides404": [
    "dirOverrides404", 
    "diroverrides404", 
    "dir-overrides-404",
    "listingsOverride404",
    "listings-override-404"
  ],
  "showDotfiles": ["showDotfiles", "showdotfiles"],
  "humanReadable": [ "humanReadable", "humanreadable", "human-readable" ],
  "hidePermissions": ["hidePermissions", "hidepermissions", "hide-permissions"],
  "si": [ "si", "index" ],
  "handleError": [ "handleError", "handleerror" ],
  "coop": [ "coop", "COOP" ],
  "cors": [ "cors", "CORS" ],
  "privateNetworkAccess": [ "privateNetworkAccess", "privatenetworkaccess", "private-network-access" ],
  "headers": [ "H", "header", "headers" ],
  "contentType": [ "contentType", "contenttype", "content-type" ],
  "mimeType": [
    "mimetype",
    "mimetypes",
    "mimeType",
    "mimeTypes",
    "mime-type",
    "mime-types",
    "mime-Type",
    "mime-Types"
  ],
  "weakEtags": [ "weakEtags", "weaketags", "weak-etags" ],
  "weakCompare": [
    "weakcompare",
    "weakCompare",
    "weak-compare",
    "weak-Compare"
  ],
  "handleOptionsMethod": [
    "handleOptionsMethod",
    "handleoptionsmethod",
    "handle-options-method"
  ]
}


================================================
FILE: lib/core/etag.js
================================================
'use strict';

module.exports = (stat, weakEtag) => {
  let etag = `"${[stat.ino, stat.size, stat.mtime.toISOString()].join('-')}"`;
  if (weakEtag) {
    etag = `W/${etag}`;
  }
  return etag;
};


================================================
FILE: lib/core/index.js
================================================
#! /usr/bin/env node

'use strict';

const path = require('path');
const fs = require('fs');
const url = require('url');
const { Readable } = require('stream');
const buffer = require('buffer');
const mime = require('mime');
const urlJoin = require('url-join');
const showDir = require('./show-dir');
const version = require('../../package.json').version;
const status = require('./status-handlers');
const generateEtag = require('./etag');
const optsParser = require('./opts');
const htmlEncodingSniffer = require('html-encoding-sniffer');

let httpServerCore = null;

function decodePathname(pathname) {
  const pieces = pathname.replace(/\\/g, '/').split('/');

  const normalized = path.normalize(pieces.map((rawPiece) => {
    const piece = decodeURIComponent(rawPiece);

    if (process.platform === 'win32' && /\\/.test(piece)) {
      throw new Error('Invalid forward slash character');
    }

    return piece;
  }).join('/'));
  return process.platform === 'win32'
    ? normalized.replace(/\\/g, '/') : normalized;
}

const nonUrlSafeCharsRgx = /[\x00-\x1F\x20\x7F-\uFFFF]+/g;
function ensureUriEncoded(text) {
  return text
  return String(text).replace(nonUrlSafeCharsRgx, encodeURIComponent);
}

// Check to see if we should try to compress a file with gzip.
function shouldCompressGzip(req) {
  const headers = req.headers;

  return headers && headers['accept-encoding'] &&
    headers['accept-encoding']
    .split(',')
    .some(el => ['*', 'compress', 'gzip', 'deflate'].indexOf(el.trim()) !== -1)
  ;
}

function shouldCompressBrotli(req) {
  const headers = req.headers;

  return headers && headers['accept-encoding'] &&
    headers['accept-encoding']
    .split(',')
    .some(el => ['*', 'br'].indexOf(el.trim()) !== -1)
  ;
}

function hasGzipId12(gzipped, cb) {
  const stream = fs.createReadStream(gzipped, { start: 0, end: 1 });
  let buffer = Buffer.from('');
  let hasBeenCalled = false;

  stream.on('data', (chunk) => {
    buffer = Buffer.concat([buffer, chunk], 2);
  });

  stream.on('error', (err) => {
    if (hasBeenCalled) {
      throw err;
    }

    hasBeenCalled = true;
    cb(err);
  });

  stream.on('close', () => {
    if (hasBeenCalled) {
      return;
    }

    hasBeenCalled = true;
    cb(null, buffer[0] === 31 && buffer[1] === 139);
  });
}


module.exports = function createMiddleware(_dir, _options) {
  let dir;
  let options;
  if (typeof _dir === 'string') {
    dir = _dir;
    options = _options;
  } else {
    options = _dir;
    dir = options.root;
  }

  const root = path.join(path.resolve(dir), '/');
  const opts = optsParser(options);

  opts.root = dir;
  

  // Support hashes and .types files in mimeTypes @since 0.8
  if (opts.mimeTypes) {
    try {
      // You can pass a JSON blob here---useful for CLI use
      opts.mimeTypes = JSON.parse(opts.mimeTypes);
    } catch (e) {
      // swallow parse errors, treat this as a string mimetype input
    }
    if (typeof opts.mimeTypes === 'string') {
      mime.load(opts.mimeTypes);
    } else if (typeof opts.mimeTypes === 'object') {
      mime.define(opts.mimeTypes);
    }
  }

  function shouldReturn304(req, serverLastModified, serverEtag) {
    if (!req || !req.headers) {
      return false;
    }

    const clientModifiedSince = req.headers['if-modified-since'];
    const clientEtag = req.headers['if-none-match'];
    let clientModifiedDate;

    if (!clientModifiedSince && !clientEtag) {
      // Client did not provide any conditional caching headers
      return false;
    }

    if (clientModifiedSince) {
      // Catch "illegal access" dates that will crash v8
      try {
        clientModifiedDate = new Date(Date.parse(clientModifiedSince));
      } catch (err) {
        return false;
      }

      if (clientModifiedDate.toString() === 'Invalid Date') {
        return false;
      }
      // If the client's copy is older than the server's, don't return 304
      if (clientModifiedDate < new Date(serverLastModified)) {
        return false;
      }
    }

    if (clientEtag) {
      // Do a strong or weak etag comparison based on setting
      // https://www.ietf.org/rfc/rfc2616.txt Section 13.3.3
      if (opts.weakCompare && clientEtag !== serverEtag
          && clientEtag !== `W/${serverEtag}` && `W/${clientEtag}` !== serverEtag) {
        return false;
      }
      if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) {
        return false;
      }
    }

    return true;
  }

  return function middleware(req, res, next) {
    // Figure out the path for the file from the given url
    const parsed = url.parse(req.url);
    let pathname = null;
    let file = null;
    let gzippedFile = null;
    let brotliFile = null;

    try {
      decodeURIComponent(req.url); // check validity of url
      pathname = decodePathname(parsed.pathname);
    } catch (err) {
      status[400](res, next, { error: err });
      return;
    }

    file = path.normalize(
      path.join(
        root,
        path.relative(path.join('/', opts.baseDir), pathname)
      )
    );
    // determine compressed forms if they were to exist, make sure to handle pre-compressed files, i.e. files with .br/.gz extension. we will serve them "as-is"
    gzippedFile = `${file}.gz`;
    brotliFile = `${file}.br`;
    
    if ( opts.forceContentEncoding ) {
      if ( file.endsWith('.gz') ) gzippedFile = file;
      if ( file.endsWith('.br') ) brotliFile = file;
    }

    Object.keys(opts.headers).forEach((key) => {
      res.setHeader(key, opts.headers[key]);
    });

    if (req.method === 'OPTIONS' && opts.handleOptionsMethod) {
      res.end();
      return;
    }

    // TODO: This check is broken, which causes the 403 on the
    // expected 404.
    if (file.slice(0, root.length) !== root) {
      status[403](res, next);
      return;
    }

    if (req.method && (req.method !== 'GET' && req.method !== 'HEAD')) {
      status[405](res, next);
      return;
    }


    function serve(stat) {
      // Do a MIME lookup, fall back to octet-stream and handle gzip
      // and brotli special case.
      const defaultType = opts.contentType || 'application/octet-stream';
      let contentType = mime.lookup(file, defaultType);
      const range = (req.headers && req.headers.range);
      const lastModified = (new Date(stat.mtime)).toUTCString();
      const etag = generateEtag(stat, opts.weakEtags);
      let cacheControl = opts.cache;
      let stream = null;
      if (contentType && isTextFile(contentType)) {
        if (stat.size < buffer.constants.MAX_LENGTH) {
          const bytes = fs.readFileSync(file);
          const sniffedEncoding = htmlEncodingSniffer(bytes, {
            defaultEncoding: 'UTF-8'
          });
          contentType += `; charset=${sniffedEncoding}`;
          stream = Readable.from(bytes)
        } else {
          // Assume text types are utf8
          contentType += '; charset=UTF-8';
        }
      }

      if (file === gzippedFile) { // is .gz picked up
        res.setHeader('Content-Encoding', 'gzip');
        // strip gz ending and lookup mime type
        contentType = mime.lookup(path.basename(file, '.gz'), defaultType);
      } else if (file === brotliFile) { // is .br picked up
        res.setHeader('Content-Encoding', 'br');
        // strip br ending and lookup mime type
        contentType = mime.lookup(path.basename(file, '.br'), defaultType);
      }

      if (typeof cacheControl === 'function') {
        cacheControl = opts.cache(pathname);
      }
      if (typeof cacheControl === 'number') {
        cacheControl = `max-age=${cacheControl}`;
      }

      if (range) {
        const total = stat.size;
        const parts = range.trim().replace(/bytes=/, '').split('-');
        const partialstart = parts[0];
        const partialend = parts[1];
        const start = parseInt(partialstart, 10);
        const end = Math.min(
          total - 1,
          partialend ? parseInt(partialend, 10) : total - 1
        );
        const chunksize = (end - start) + 1;
        let fstream = null;

        if (start > end || isNaN(start) || isNaN(end)) {
          status['416'](res, next, { size: total });
          return;
        }

        fstream = fs.createReadStream(file, { start, end });
        fstream.on('error', (err) => {
          status['500'](res, next, { error: err });
        });
        res.on('close', () => {
          fstream.destroy();
        });
        res.writeHead(206, {
          'Content-Range': `bytes ${start}-${end}/${total}`,
          'Accept-Ranges': 'bytes',
          'Content-Length': chunksize,
          'Content-Type': contentType,
          'cache-control': cacheControl,
          'last-modified': lastModified,
          etag,
        });
        fstream.pipe(res);
        return;
      }

      // TODO: Helper for this, with default headers.
      res.setHeader('cache-control', cacheControl);
      res.setHeader('last-modified', lastModified);
      res.setHeader('etag', etag);

      // Return a 304 if necessary
      if (shouldReturn304(req, lastModified, etag)) {
        status[304](res, next);
        return;
      }

      res.setHeader('content-length', stat.size);
      res.setHeader('content-type', contentType);

      // set the response statusCode if we have a request statusCode.
      // This only can happen if we have a 404 with some kind of 404.html
      // In all other cases where we have a file we serve the 200
      res.statusCode = req.statusCode || 200;

      if (req.method === 'HEAD') {
        res.end();
        return;
      }

      // stream may already have been assigned during encoding sniffing.
      if (stream === null) {
        stream = fs.createReadStream(file);
      }

      stream.pipe(res);
      stream.on('error', (err) => {
        status['500'](res, next, { error: err });
      });
      stream.on('close', () => {
        stream.destroy();
      })
    }

    function statWithAccess (file, cb) {
      fs.stat(file, (err, stat) => {
        if (err) {
          cb(err);
          return;
        }
        fs.access(file, fs.constants.R_OK, (err) => {
          stat.readable = !err;
          cb(err, stat);
        });
      });
    }


    function statFile() {
      try {
        statWithAccess(file, (err, stat) => {
          const effectively404 =
            (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) ||
            (!stat || !stat.readable);
            
          if (effectively404) {
            if (req.statusCode === 404) {
              // This means we're already trying ./404.html and can not find it.
              // So send plain text response with 404 status code
              status[404](res, next);
            } else if (!path.extname(parsed.pathname).length && opts.defaultExt) {
              // If there is no file extension in the path and we have a default
              // extension try filename and default extension combination before rendering 404.html.
              middleware({
                url: `${parsed.pathname}.${opts.defaultExt}${(parsed.search) ? parsed.search : ''}`,
                headers: req.headers,
              }, res, next);
            } else if (opts.showDir && opts.dirOverrides404) {
              // If showDir and dirOverrides404 are true, show the directory instead of 404.html
              req.url = path.dirname(req.url);
              showDir(opts, stat)(req, res);
              return;
            } else {
              // Try to serve default ./404.html
              const rawUrl = (opts.handleError ? `/${path.join(opts.baseDir, `404.${opts.defaultExt}`)}` : req.url);
              const encodedUrl = ensureUriEncoded(rawUrl);
              middleware({
                url: encodedUrl,
                headers: req.headers,
                statusCode: 404,
              }, res, next);
            }
          } else if (err) {
            status[500](res, next, { error: err });
          } else if (stat.isDirectory()) {
            if (!opts.autoIndex && !opts.showDir) {
              status[404](res, next);
              return;
            }


            // 302 to / if necessary
            if (!pathname.match(/\/$/)) {
              res.statusCode = 302;
              const q = parsed.query ? `?${parsed.query}` : '';
              res.setHeader(
                'location',
                ensureUriEncoded(`${parsed.pathname}/${q}`)
              );
              res.end();
              return;
            }

            if (opts.autoIndex) {
              middleware({
                url: urlJoin(
                  encodeURIComponent(pathname),
                  `/index.${opts.defaultExt}`
                ),
                headers: req.headers,
              }, res, (autoIndexError) => {
                if (autoIndexError) {
                  status[500](res, next, { error: autoIndexError });
                  return;
                }
                if (opts.showDir) {
                  showDir(opts, stat)(req, res);
                  return;
                }
                status[403](res, next);
              });
              return;
            }

            if (opts.showDir) {
              showDir(opts, stat)(req, res);
            }
          } else {
            serve(stat);
          }
        });
      } catch (err) {
        status[500](res, next, { error: err.message });
      }
    }

    function isTextFile(mimeType) {
      return (/^text\/|^application\/(javascript|json)/).test(mimeType);
    }

    // serve gzip file if exists and is valid
    function tryServeWithGzip() {
      try {
        fs.stat(gzippedFile, (err, stat) => {
          if (!err && stat.isFile()) {
            hasGzipId12(gzippedFile, (gzipErr, isGzip) => {
              if (!gzipErr && isGzip) {
                file = gzippedFile;
                serve(stat);
              } else {
                statFile();
              }
            });
          } else {
            statFile();
          }
        });
      } catch (err) {
        status[500](res, next, { error: err.message });
      }
    }

    // serve brotli file if exists, otherwise try gzip
    function tryServeWithBrotli(shouldTryGzip) {
      try {
        fs.stat(brotliFile, (err, stat) => {
          if (!err && stat.isFile()) {
            file = brotliFile;
            serve(stat);
          } else if (shouldTryGzip) {
            tryServeWithGzip();
          } else {
            statFile();
          }
        });
      } catch (err) {
        status[500](res, next, { error: err.message });
      }
    }

    const shouldTryBrotli = opts.brotli && shouldCompressBrotli(req);
    const shouldTryGzip = opts.gzip && shouldCompressGzip(req);
    // always try brotli first, next try gzip, finally serve without compression
    if (shouldTryBrotli) {
      tryServeWithBrotli(shouldTryGzip);
    } else if (shouldTryGzip) {
      tryServeWithGzip();
    } else {
      statFile();
    }
  };
};


httpServerCore = module.exports;
httpServerCore.version = version;
httpServerCore.showDir = showDir;


================================================
FILE: lib/core/opts.js
================================================
'use strict';

const aliases = require('./aliases.json');


/**
 * @typedef {Object} ParsedOptions
 * @property {boolean} autoIndex
 * @property {boolean} showDir
 * @property {boolean} dirOverrides404
 * @property {boolean} showDotfiles
 * @property {boolean} humanReadable
 * @property {boolean} hidePermissions
 * @property {boolean} si
 * @property {string|function} cache
 * @property {string} defaultExt
 * @property {string} baseDir
 * @property {boolean} gzip
 * @property {boolean} brotli
 * @property {boolean} forceContentEncoding
 * @property {function} handleError
 * @property {Object.<string, string|boolean>} headers
 * @property {string} contentType
 * @property {Object|undefined} mimeTypes
 * @property {boolean} weakEtags
 * @property {boolean} weakCompare
 * @property {boolean} handleOptionsMethod
 */


/**
* Converts a user-provided options object into a ParsedOptions object
* @param {object} opts - User provided options
* @returns {ParsedOptions}
*/
module.exports = (opts) => {
  /** @type {ParsedOptions} */
  const options = {
    autoIndex: true,
    showDir: true,
    dirOverrides404: false,
    showDotfiles: true,
    humanReadable: true,
    hidePermissions: false,
    si: false,
    cache: "max-age=3600",
    coop: false,
    cors: false,
    privateNetworkAccess: false,
    gzip: true,
    brotli: false,
    forceContentEncoding: false,
    defaultExt: "html",
    baseDir: "/",
    handleError: true,
    contentType: "application/octet-stream",
    weakEtags: true,
    weakCompare: true,
    handleOptionsMethod: false,
    headers: {},
    mimeTypes: undefined,
  };

  function isDeclared(k) {
    return typeof opts[k] !== 'undefined' && opts[k] !== null;
  }

  function validateNoCRLF(str) {
    if (typeof str === 'string' && (str.includes('\r') || str.includes('\n'))) {
      throw new Error('Header is not a string or contains CRLF');
    }
  }

  function addHeader(key, value) {
    validateNoCRLF(key);
    validateNoCRLF(value);
    options.headers[key] = value;
  }

  function setHeader(str) {
    validateNoCRLF(str);

    const m = /^(.+?)\s*:\s*(.*)$/.exec(str);
    if (!m) {
      addHeader(str, true);  // Use addHeader instead of direct assignment
    } else {
      addHeader(m[1], m[2]); // Use addHeader instead of direct assignment
    }
  }


  if (opts) {
    aliases.autoIndex.some((k) => {
      if (isDeclared(k)) {
        options.autoIndex = opts[k];
        return true;
      }
      return false;
    });

    aliases.showDir.some((k) => {
      if (isDeclared(k)) {
        options.showDir = opts[k];
        return true;
      }
      return false;
    });

    aliases.dirOverrides404.some((k) => {
      if (isDeclared(k)) {
        options.dirOverrides404 = opts[k];
        return true;
      }
      return false;
    });

    aliases.showDotfiles.some((k) => {
      if (isDeclared(k)) {
        options.showDotfiles = opts[k];
        return true;
      }
      return false;
    });

    aliases.humanReadable.some((k) => {
      if (isDeclared(k)) {
        options.humanReadable = opts[k];
        return true;
      }
      return false;
    });

    aliases.hidePermissions.some((k) => {
      if (isDeclared(k)) {
        options.hidePermissions = opts[k];
        return true;
      }
      return false;
    });

    aliases.si.some((k) => {
      if (isDeclared(k)) {
        options.si = opts[k];
        return true;
      }
      return false;
    });

    if (opts.defaultExt && typeof opts.defaultExt === 'string') {
      let ext = opts.defaultExt;
      // Remove the leading dot if it exists
      if (/^\./.test(ext)) {
        ext = ext.replace(/^\./, '');
      }
      options.defaultExt = ext;
    }

    if (typeof opts.cache !== 'undefined' && opts.cache !== null) {
      if (typeof opts.cache === 'string') {
        options.cache = opts.cache;
      } else if (typeof opts.cache === 'number') {
        options.cache = `max-age=${opts.cache}`;
      } else if (typeof opts.cache === 'function') {
        options.cache = opts.cache;
      }
    }

    if (typeof opts.gzip !== 'undefined' && opts.gzip !== null) {
      options.gzip = opts.gzip;
    }

    if (typeof opts.brotli !== 'undefined' && opts.brotli !== null) {
      options.brotli = opts.brotli;
    }
    if (typeof opts.forceContentEncoding !== 'undefined' && opts.forceContentEncoding !== null) {
      options.forceContentEncoding = opts.forceContentEncoding;
    }

    if (typeof opts.baseDir !== 'undefined' && opts.baseDir !== null) {
      options.baseDir = opts.baseDir;
    }

    aliases.handleError.some((k) => {
      if (isDeclared(k)) {
        options.handleError = opts[k];
        return true;
      }
      return false;
    });

    aliases.coop.forEach((k) => {
      if (isDeclared(k) && opts[k]) {
        options.handleOptionsMethod = true;
        options.headers['Cross-Origin-Opener-Policy'] = 'same-origin';
        options.headers['Cross-Origin-Embedder-Policy'] = 'require-corp';
      }
    });

    aliases.cors.forEach((k) => {
      if (isDeclared(k) && opts[k]) {
        options.handleOptionsMethod = true;
        options.headers['Access-Control-Allow-Origin'] = '*';
        options.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since';
      }
    });

    aliases.privateNetworkAccess.forEach((k) => {
      if (isDeclared(k) && opts[k]) {
        options.headers['Access-Control-Allow-Private-Network'] = 'true';
      }
    });

    aliases.headers.forEach((k) => {
      if (isDeclared(k)) {
        if (Array.isArray(opts[k])) {
          opts[k].forEach(setHeader);
        } else if (opts[k] && typeof opts[k] === 'object') {
          Object.keys(opts[k]).forEach((key) => {
            addHeader(key, opts[k][key]);  // Uses same validation path
          });
        } else {
          setHeader(opts[k]);
        }
      }
    });

    aliases.contentType.some((k) => {
      if (isDeclared(k)) {
        options.contentType = opts[k];
        return true;
      }
      return false;
    });

    aliases.mimeType.some((k) => {
      if (isDeclared(k)) {
        options.mimeTypes = opts[k];
        return true;
      }
      return false;
    });

    aliases.weakEtags.some((k) => {
      if (isDeclared(k)) {
        options.weakEtags = opts[k];
        return true;
      }
      return false;
    });

    aliases.weakCompare.some((k) => {
      if (isDeclared(k)) {
        options.weakCompare = opts[k];
        return true;
      }
      return false;
    });

    aliases.handleOptionsMethod.some((k) => {
      if (isDeclared(k)) {
        options.handleOptionsMethod = options.handleOptionsMethod || opts[k];
        return true;
      }
      return false;
    });
  }

  return options;
};


================================================
FILE: lib/core/show-dir/icons.json
================================================
{
  "_blank": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII=",
  "_page": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpsUztv01AYPfdhOy/XTZ80VV1VoCqlA2zQqUgwMEErWBALv4GJDfEDmOEHsFTqVCTExAiiSI2QEKJKESVFFBWo04TESRzfy2c7LY/kLtf2d8+555zvM9NaI1ora5svby9OnbUEBxgDlIKiWjXQeLy19/X17sEtcPY2rtHS96/Hu0RvXXLz+cUzM87zShsI29DpHCYt4E6Box4IZzTnbDx7V74GjhOSfwgE0H2638K9h08A3iHGVbjTw7g6YmAyw/BgecHNGGJjvfQhIfmfIFDAXJpjuugi7djIFVI4P0plctgJQ0xnFe5eOO02OwEp2VkhSCnC8WOCdqgwnzFx4/IyppwRVN+XYXsecqZA1pB48ekAnw9/4GZx3L04N/GoTwEjX4cNH5vlPfjtAIYp8cWrQutxrC5Mod3VsXVTMFSqtaE+gl9dhaUxE2tXZiF7nYiiatJ3v5s8R/1yOCNLOuwjkELiTbmC9dJHpIaGASsDkoFQGJQwHWMcHWJYOmUj1OjvQotuytt5nHMLEGkCyx6QU384jwkUAd2sxJbS/QShZtg/8rHzzQOzSaFhxQrA6YgQMQHojCUlgnCAAvKFBoXXaHfArSCZDE0gyWJgFIKmvUFKO4MUNIk2a4+hODtDUVuJ/J732AKS6ZtImdTyAQQB3bZN8l9t75IFh0JMUdVKsohsUPqRgnka0tYgggYpCHkKGTsHI5NOMojB4iTICCepvX53AIEfQta1iUCmoTiBmdEri2RgddKFhuJoqb/af/yw/d3zTNM6UkaOfis62aUgddAbnz+rXuPY+Vnzjt9/CzAAbmLjCrfBiRgAAAAASUVORK5CYII=",
  "aac": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNp0Uk1PE0EYftruVlvAUkhVEPoBcsEoLRJBY01MPHjCs3cvogcT/4qJJN5NvHhoohcOnPw4YEGIkCh+oLGBKm3Z7nZ3dme2vjOhTcjiJJvZzPvOM8/HG2q325Dr3kLp7Y1ibpIxjs4KhQBZfvV6s7K5Vb0bjeof5ZlcGysP1a51mifODybvzE8mzCbrAoTDIThMoGXZiZ4YSiurf+Z1XeuCqJ7Oj+sK3jQcNAmg8xkGQ71mYejcAB49vpmeuzJccl0+dUj6KIAvfHCPg3N+uAv4vg9BOxcCmfEzuP/genpmeqhEMgude10Jwm+DuUIyUdTlqu2byoMfX/dRermBeExHsTiWNi3+lMpzRwDki8zxCIATmzbevfmClukiP5NFhJgwkjeRTeLShdOoVJqnAgwkgCAZ6+UdLC9twjQZ8pdzioFkZBHY3q6B3l4dJEEEPOCeD4cYVH7Xsf15F+FImC775INAJBJSkVoWo0QY9YqgiR4ZZzRaGBkdwK3bFxGLRZUfB3Rm2x4x9CGtsUxH9QYkKICDFuLxKAozGZwdTqBRs2FbLlXbiPdECMCHadj/AaDXZNFqedCIvnRcS4UpRo7+hC5zUmw8Ope9wUFinvpmZ7NKt2RTmB4hKZo6n8qP4Oq1HBkKlVYAQBrUlziB0XQSif4YmQhksgNIJk9iaLhPaV9b/Um+uJSCdzyDbGZQRSkvjo+n4JNxubGUSsCj+ZCpODYjkGMAND2k7exUsfhkCd+29yguB88Wl7FW/o6tT7/gcXqAgGv7hhx1LWBireHVn79YP6ChQ3njb/eFlfWqGqT3H3ZlGIhGI2i2UO/U/wkwAAmoalcxlNA1AAAAAElFTkSuQmCC",
  "ai": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAk5JREFUeNpsU01vElEUPTPzZqBAQaSFQiJYUmlKYhoTF41L3Tbu/Q/+AvsX3Bp/gPsuWLrqyqQ7TUxMtAvF1tYGoXwNw7wv7zwYgtKX3Lw379575p5z77O01ohW+/DVh8zj7aYKhflGdG9ZsGwLNydffgVfr19YHvsEa+Zu/nxndob5StQK+dyzvZzyw/gKlmMj7IygFM+xvNcanp4/t5dAomXHBy2UUBOO2MAl/B9/cPb6PULuoHx0WM0e3GvpUOxD3wZAJWutZqYUYmqpSg5OMgH3YQObL59W0/ullpryR3HegkKEqiWBSGV4R3vQ7sIhScTZFTpHx3A215B5sluVY/WWMg7+ATB/lcLsKpTonHzD+OMFEuTz8ikkt9Kwt9YJZB38cpBdoQAZJdLvCGByfoPB6Xdk90pYy6Xg3c/DaWwArg09DaG5lCsUFN0pckZAojdC8m4auBqaALuSgez7VB1RtDSUWOQvUaBLFUzJBMJ2DwmPgd1Jwm0WoSgJfjDvrTKxtwAIyEkAOQ5hU//Zdg5uowDlUNMnwZLW0sSuUuACYhwQRwFvJxupCjEYUUccOkoaKmdOlZnY1TkgAcXAhxhOwLsDsHoN3u4O5JTDfVCH6I9nfjId3gIgSUATFJk/hVevGtOMwS0XwQ3AzB/FrlKg8Q27I2javVoZrFgwD4qVipAEyMlnaFArzaj/D0DiMXlJAFQyK2r8fnMMRZp4lQ1MaSL5tU/1kqAkMCh2tYI+7+kh70cjPbr4bEZ51jZr8TJnB9PJXpz3V4ABAPOQVJn2Q60GAAAAAElFTkSuQmCC",
  "aiff": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAohJREFUeNpkU9tqE1EUXZmZpE3aTBLbJFPTtFURtSCthr7UCyKKFJ/9An3og6Ag/oXfoUj7og9asCBYKT6UIPHaWtpq7NU2aZK5z5wZ9xxMpMwZDuewz9prr32ZiO/7CNaDx3OLt6fOjBqGg/aKRCIInp8+KzfKH7fudnVF58nE16el+/yU2mBFSWZKpWJKVc0OgUBo02K4NDmU6o75Mx+Wdu9IUXFeiOA/pn1xHeYaugVDdzpbp91qGlAKGTx8dC19/Wpxhjnsxj/RRwk85hGJC9d1O6fneWAuoztDYSSLe9OT6SuXB2ccx73Z9uukwDwfls1g0xZIY/Ad/Gnyt/XVfbyYrSDRE8PExHB6/8B6QuaxIwRBFMt0iIAiMx+LCys8jfGJEUik2WpZOD2SQf9oDtVqQwopCAiY66FS/om3b75CVS2MlU7AJ2WiJBCZjZ2dJuRkDJZFwFAR7UCBja3fNfxY2YEoCtRCj9em3Tpds6FpJseGCBxS0GgYGBzqw62p84gnYnAI2CSbSbPhEpFAaE2zODaUAlWWwDoS5DheGqbWpVE/0CmqCY9qkEyINBceb2uADRNQ8bSWAVVzIFKomCQim+0luS4yKYlsHlRyZo7EsSEC23K5vAsXh/H92zZkuRvxeBS5nEx2yp2KqhxPoV5TYS/8CtdApylM9sZQKKSQzyeRTseRV2QoAzIYY8jme5DN9fI0dQoUIjANGydP9VM7PZw9p/AiBpNYrdbw/t0yTJqRtdU9UrfJCUMpSJIgbWzsYe51BcViHzLHeqCRqhZ1YX1tFwNfZBxS9O3NWkAcHqR606k/n/3coKAoV/Y7vQ/OYCZevlrmv3c0GsFh06u3/f4KMABvSWfDHmbK2gAAAABJRU5ErkJggg==",
  "avi": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm1JREFUeNpsU8tu00AUPXZcN0nzTpq2KQ3pAwkIAnWHqCoeexBb+AQ+ABZ8A2s+AIkdm266QUJIFWKBkHg1KpRHi5omJGkbJ3bGHj+4M1EQrTvSyGPPueeec++1EgQBxHp+/9mbyuriRZdxjJaiKBD3W+u1+p9a856max+gDO8ebT+WT20Ezi9NZi/crqadvn2MQBAGfpCOpqNru2937vxPIpY6Onjccx3Twck9MBiSU0ncfHirXFmZX3Md9wqCUwiEVN/zaQfHt0vfbBe5uQyuPVgpl5Zn11ybL4/i/lkICOw5niQRGQShoiqI6Bo43W2ub8n3hRtLZT7gTynk6gkCX9gAOxpAnxhHZDwC1/aI1EViJolu/QhKRMHZ1UX0Gr1USIEn5FPWHy+/wTokkrQOq2vBaHZBN4hmY9Jwfr4An/teiEB45ZZDwDiMhoExT0N+sYDCuUkkplLIlXP4/XEXdo+RUhdhBSSfUwtVTUG8MIHK9QVqI7D/uY6vr2pwmCPrkz+Tk9gwARWQ9WxppbXZhNnpw+ya4A5HZi6L4lIR8WyCcL6sTZiAWjWgAmpxkn5+kqTamK6WkCwmERmLDLvjB0ML9ikWXPLFuozYOap3L8HYN6DHdbS/d5CeTVBndBz87FCBLYkNTyIjBQemnIEsSY5lYrK1+UoWcToLMjEHAyIQ2BCBSx/NVh+ZUhrqmEqBebS3WyhdLg0zt/ugAaIklsSGLHCLa6zDMGhZ2HjyGsnpFPqNHnY2fmHv3R5SMymYbROszSQ2ROAY9qHiofvlxSc5xsKKqqnY3diRE9h4X5d/pzg7lnM4ivsrwADe9Wg/CQJgFAAAAABJRU5ErkJggg==",
  "bmp": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmZJREFUeNp0U+1rUlEY/13v9YV0vq2wttI5CdpL9aEGBZUDv0df668I6n+or0UQ/RuuD0EgVDAZrsKF4AR1a6COKW5qXvXec27PuVeda3bgcF6e8/ye5/d7niMZhgExnK9fbTrm5pbBGMZDkgCyq+VyhTUaT6Eo2ZHJePPWXJXRhez3B1yxmM/QdctXUSCgtV4Py4CvY3cky4e1x5DlLCaGbbzjXDcousG5OQe5HPRSCQPK4PpsEM/XH4WvhS4noeu3JwHGGRiULhsMoKZS4I0GtEIB9mgULJGA0+9DPBpBT7sffvf1W/Lg6OgJufw8C0CRGEXWazUwiiyFQjA8bsjVKjaJzovMD/Q5gxyJhG2cvyeXe2cAuADQNGBmBvLaGuTFRaDfh31lBTWi9pumjbK0B4JQul3vOQpM8JdskOLrdCvDcDjAsjtg5TIkoiKLaokMNR2cnZbqNAMycqG7XbHKR2fMzwO/dsxSwu0BiBJsNsv2LwAJAJCI5ux2gXYbqNetcz5PoORI1cDS0n8AxGW7A+zvEYBKZ2ZlcsEtJLbedMjePBaCTQMghx45ulyWkzxMVUQ2RMQhLfFO16YAqCrixPnm6iqKrRb2W23EfF4cUNSrHg90cr7hDyB33MTnSmUKALVs4uIlROjxg+AsPhGVl3fuIl2tIOB0Ya91gkOi9mxhAal0ekork1ic/kGLBORMxy2K1qS9V1ZQbNThIj2EGh+2tsyOnSai8r1UxMNIBB+LRTTULr4Uds0K1tU/uOLxIrmbNz8XXSrnASSpubG9fbKRyVh1n/zSw29t9oC1b47MfwUYAAUsLiWr4QUJAAAAAElFTkSuQmCC",
  "c": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcxJREFUeNqEUk1rE0EYfmZnkgoJCaGNCehuJTalhJZSUZB66a0HwXsP/Qn+FM+9+hty0LNYCr2I7UVLIW0Fc0hpQpSS7O7MrO9MspuvVV8YMnk/nn2e5x0WRRFMvP/w6WSz5jbi/9NxfP693Wp3DrJCnMW5d28P7a+IE15lufR8o1ZEStwPhkWHsWbrZ+eNEPxsuubEF6m0TBv2Q4liPofXuzveulttSqW2UwH+GjqC0horpSL2njU89+FyMwjlTlxOJMTa9ZQHzDQIjgwdom9zLzfXPc75kbnOAswBJTlC2XrqQRMLxhi442DgB4UFBhgPpm3B5pgBHNUUxQKAHs8pHf3TEuFMetM9IKr/i2mWMwC0SnuSFTG2YKyppwKYVdGO7TFhzBqGIenVeLCUtfURgErucx5ECKREKBU4d3B718PHz6cICGT/1Qs8qpQtGOdyhtGEARWDQFqQJSeDL98u4VbLaKw9IRAJPwjtoJGlVAoDQ800+fRFTTYXcjlcXN2g++s36p5Lzzlve1iEROa8BGH1EbrSAeqrjxEqicHQt8/YSDHMpaNs7wJAp9vvfb287idboAVkRAa5fBYXP9rxO4Mgf0xvPPdHgAEA8OoGd40i1j0AAAAASUVORK5CYII=",
  "cpp": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfJJREFUeNqEUs9PE0EU/mZ2WgqpXX+QIDFdalVslh8NlAOQaOKFAwfvHvwT/FM8e/U/MOnBmwcj8WD0ACEGghIkbU0baaEthe3OTJ0ZWV26q37JZt68ee/b9733yGAwgMbL12/fz+azbnAPY2Nrt7Zfqz9JMrYZ+J4/e2pOFjiciRvXlgp5GzHonXk2o6S8V6k/TjBrM/xGA4MLyeOSPZ8jkx7D+uqCU3Amy1yIYizB36AlCSkwfjWDR4uu40yMl/s+XwjeWThQQ4Z6QNSnSkYykcDXasP4lmfvOZTSF9q8TDBEFPbN5bOqCglCCCxK0TvvZyIV4CIxbgpC+4gm/PUmFCIE8iJPyME/e8Lon9j4HvyHYLjKSwRCSEUgf9+15mFbx8QS6CZJMzJ9SlBCwX3fJDLG4PX7ykcwkmQmJtpEhWa7g1dvNlSwjwelebz7tAXLolh0p/Fxe9fErK2WDFGEgKjxfNjegX0lDTc/heNuF99/HGEslcKXwyoazWNDdlCr6+DoJgrBzdI0T9rYO6yg2zszMlaKM3Dv5OBzbuyZuzm1B16U4Nzz2f3cFOx0Gq12F9cztpExncsqYoaHpSIKtx0zJdVIFpHQ6py29muNk1uTN829o/6SHEnh80HFaE6NjmLnWxUJy1LyTltB3k8BBgBeEeQTiWRskAAAAABJRU5ErkJggg==",
  "css": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAk1JREFUeNpsUktvUlEQ/u5DoCLl/RAKKKUvWmIxjYntQtcu3LvwJ/hTXLt16coFC2PsojEaMKZtCqFaTdGmjbS0CG3By+vei3OOBSGXSU7uzNyZ78z3zRF6vR6YvXzzPrMUCyf68bB9zO+VfpROn5hkOdfPPX/2lH/lfiLidztX5mN2jLGG0rKLENIE8liWpdzwP7HvqJqujmvudFU4bFY8Wk1FZsOBtKppd8YCDNu77CZevd3gflfTUFcUhP0ePLibiIR9rjSBpgwAfe4dVcV6dhtep4PH5msylGYLrzeybErcT85FYiH/CyPAf74gObC2vMhzsiRhPhpC6eQUM+EA1pJzILEnjRSuJsju7MJqsUCSRei6Dp3yXqcdGlHZ/rLPazQWGCn8+6YW4pAkEW0SjzUzanWlCa/LgcR0lNfovTEi6lcIkzesnM/R8RlN0INGp3h4DHoDsE5YRvQyiKiRSMzikRAOS2WoqoZWu41K7RwzlOOAVDMMMHhIGvFlRxJFrKYW0ep0IYgC3SDh4b1lTJjNfENsrazOAMAw680mPuW+8lFno1P4XDigRhOiwQAyJK7TbsNS/PaA7giAIAhYz2yRgBIfsVA8wIetPG6FAqhdNrC5u0f+TUyHgyMTDDToEt/ftQsEvW4EPG5OZcrvw0mlimarTXkPfpXPcNlQoGtjACgpryQXsPNtH/nvRXqBJpoKHMzGNkNB0Odls7LNyAYKpUq1dt1iuvB7fRDp9kr9D1xOFwkpoksXusmXaZWFn0coV89r/b6/AgwAkUENaQaRxswAAAAASUVORK5CYII=",
  "dat": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfVJREFUeNqMU01PE1EUPe/Na0uptmlASg3MoiZgCA3hQ8PHAjbqwsS9C3+CP8W1W/+BSReyYUPwI4QAVkAgUEgIbVIg1FZb2pl5b3zv2cHBjsaTTOa+e989OffcGeK6LhTevFv+OJoZHPHOfrz/sl86KpWfhxnLe7lXL1/oN/MSZqonOXU/k0AA6lfNhEFIrlAsP2PMyPtr1AscLpyg5pbtIHErhqez4+awmc45nI8FEvwNaiQuBHqTcSxMjJhmX0/Osp1xr878FxWEzwMinxAzEA4xFIpnOjedHTKpYbxW4U2CP4j8uWxmUKsghMCgFI2mFe9QgHZj0Ba4yhFF+KvGJToIRLuPC/efnjD6+26wB1Lq/xgbSCBXKeWJG/OTdky8cWTdT3C9RmWSGk2XCLlWo4xTNbfN5qh7PpXM72GjZeHt0gpq9QbmH4whGb+NpU/reDQ7hcWVVXxvXOHxzCQopQEKXKEbL6o1ZIcy+LC5g62DY2zsHeC0fA4zndIrHOjvg2XbAQRSfsuy9XxC2qzi/H5B6/68W0AsGkW0KyJPBLbDO0fg3JX/CUM81i0bD6WKe6j9qOPJ3EMcF0tSNsFA6g6alqW+VtZBUL78Vtk+Oqne7U9rs5qOQCjSheJFBeFIFOfVujSUYu3rIc4uqxWv76cAAwCwbvRb3SgYxQAAAABJRU5ErkJggg==",
  "dmg": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAn9JREFUeNpsU01rE1EUPe9lkk47yWTStCmtNhFSWxos2EXVhSsRcasuxYV05V8Qf4DgD/AvCK5EV1oFI7iUBqmCNdDvppq2mWSSzEzy3vPOpFFq+uDNfR/3nnvueXeYUgrBWH1/9/NE7k5BKRnuRcfF2qdnmJq9DeF9tQ+2isuMsxXGWHh/a1mEVsPJSI5fSU3OPEj291IIlN49RXz0KqzEQjIeZS/L5Y/3wPGhDxIM/i/A7fZWgVG0t5EaG0ZUa0JGM8gvPrZmLt58QYwv91mfAqCIE0sAqgumBFITGQzpUYhuF0KfRa7waDyXXXolpVrsh/0tgSLDr5I+wUZo1UHCSkAficPzY6juFSmbRPrC/azjq+fkcO00gAqoU7B0ETKkfWbuCTjTYeq5oESAauexcTScX+ZACWFm0YQSLZKhHdr67+/wW0e0dgjYo3sCEXXybYtBDVSHLp2es3IpsILS24c42lkBg6DzRjgRzCDZ/xr0GNRJwwYiWgzt+hYMawleu0V3wbkT+kUirOc7IGJAz68R/Qak1BAlx3hqASPGBJRXpXOv58dkz3eAgQoOm4hyj57NgZm0MHvpBmK6QdUdg/DAg9cRkhicBSDaKJdeo1bdxmR2DtWDDUxl51HZ+QHTysD3XdQO95Gfv06aeGcAdBrY3Chi8lwO3768QWX7J5q1XWyVSxgajiOXLyBG2hzurRKV9lmt7ISNkkjo6HhNyjoK+2gXRsKE57ZIE2ot10Z1fz0Ue4ABVw3NMjnW14rInh8jTYywoTg3EOFpOM4mXNfH9PQUfGlrAwBOs3I8ljbtuMWhRWzIIPrkn+GcYcgIWEowbZ+0qB334/4IMADESjqbnHbH0gAAAABJRU5ErkJggg==",
  "doc": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAppJREFUeNpsU79PFEEU/mZ39vZu77g7DokcP04BBSUmiEKCSCxs7Ei00JAYO2NlTKyMrX+CJhaGwopSQ0dMtFEsbDRBgiZEQIF4IHcg+2t2Z8eZ5QDlnM1mZ9+8973vfe8NEUJArfSNhzPG0VIfeIiDRSDkw1cWVt3N8rhG6SdSO2Gvn8dfuueqZwuNZqk3Jxg7iNcIfBbgXD6ZC8u5qffzX8eoYeyDxC77uygKhcouovgVUQj1H4YB2ovNuD9+tTTU0zMVBmG/+C8AIYh8F361DL/yE5HnADKYlVdg6MDAmW7cuz5WGuw+PsWDYGAvbL8ECFUt4K7/AHd/I9c7BLaxinD2Ld5Zo7g78RLuRhlBS2cpWbGfStfhfwCEpK0nUjCbWuGsLciSOELPhkq/YgdY3l6HsLfRcLYf+pHNbH0JigEPkLAyMsiEJ7NrqQzM1i7wyhoMZqOhvQs6Z0ovXgdAJACRoulEg5HOwrOroKk0zOY2BDtVpTF0CU6kLkQJXa+BNEoG0lMSsBBKQXWNQktmoGcaYeSaQCIVWOvUYQAiWZFQtk5mSMoSzEILtBrTfEcviC5bwVwQmoh96wA0ic5dB57ngeoaTIPCdb34zDITYNLOOIeVSsW+dQC+7+NSWx6jJ4tY/rWNV7PfcGv0tBoPTM7M4eKJVgx2FTE9u4QPS6x+kHzfw/mOAjarW2hJG3hy8zIceweuY+PRtREMdzbjzcd5WBqPB6xeRGUMGRzHjWvMmxQ7tiOF1JBN6FiTd6Sy9RuFbHpX7MMMqOD088Ii+op5OUAO7jyeRGfBwrF8Cg8mXuDL4neMXzgFwhwZz+hf7a9d5yu3Z6DTPjVQIY9k7erO7Y63Lvc8ErEeyq6JaM6efjai4v4IMABI0DEPqPKkigAAAABJRU5ErkJggg==",
  "dotx": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAndJREFUeNpsU01rE1EUPTPzJk0y+WhMStW2qdVWxUVEQUF0I+4ELQiC7lz4N9z0T+hG9wrdZKUgLqulhrbSag1CKpT0g7RpYjqZmffle5NEKdMHlzfvvXvPPffcO4aUEno9f3Vt4dTp+BXOe+fB0u/NbVpv7h89NU1j1TCM8H7+xY9wJwPHZMbOjRadLAvE/2gToJTiTPx89k+OlVd/LT+0TPIPpO/SzyQk40xCMxBSZ9Z3CoAx5DOjeHT7SbE0XSpzwa8OWB9jINELolQg8AR0EgUKn1PIlIWpkUt4cPNxkTOU12trs8p95RiAXpqaztqou8q6SKQJJmZSqGwsodFsIJk1kcyLYv7IeafcLx4HUNkFF4jFTExMZ0B9DrfD4HUEusYhWs4GPEJg5wly/tBYRIOeDhpEwlS34xcyajdQr3UwOT2MlJOEBRuGNHWp9AQRVXDfQiFV/U5GBSiQ5p6ngBEa5z3fiIhC6g6IMDBwOdoHPkYnHPVyhN0tF7E4QSpr94CEOKELffq+y9Bq+DCJ7rWBoQQBVbPR2O6G4OlsLASJMtCZfQqm0NP5IVWnamdAkUxbyuIYtD7wWegb0YAzAVMkkI6NwPM9xEwHloyDGAmk7AKS9rAS0FKOdugbYeAHPu7OPEM+MY7q3hIKqTFQHmC3XcONc/fxdfMDrk/ew/edzyhvvTmBAddocVRqH3Frahau56qpZDho7+PnTgXffi/gbHYmLEvPSIQBp5JU62sYz13G609zKBXvoOMdYn2zgm7Xg2MVML/4Eu3uPgxhk2gXmNl8v/i2pcXTP8tKdTEcbWLZqDQXwu/l6pfwbEnSGsT9FWAA4mdHv2/9YJ4AAAAASUVORK5CYII=",
  "dwg": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoFJREFUeNpsU0tPE2EUPfOg006hD4rQh8WgbCSwkKgbF2owujaCiQsXxpX+D6MmbtXEsHCLmIAbE6NLo8YlGIxREIshIqVl+mQ6j8/zFVCb4UtuZua795577rl3FCEE5Bl79vPd5LHYiOP7cH1AUWi85ytmvlas1bJ9E5ryBntH3BpuP/X9i7ovkluuiE8N9SDepaLpCcRCCqa/VDCaMuIjSWP25Upl6n+QDoCz6Yh7KKzh3sI2LuUimPtRRyaqodj0MDloYiITSTi+mH29Wu0AUf9CsZPJoW5czJl48LmCc5kIKo5Al67B9gUGYxrun+5NnMlFZ+GKiQADj2a7AquseLIvjMv5KMaSBu4sWVir+3i8VIVKYSby0UTdFU8Znu8AYBHQgVOJEN5uOXi4UsdawwU0FSf6TaSoyw6DRvukPkgGWpDKy4F8a3jImCrqFDFn6rhKPR4VGnhvOTAY3WLcjifcQAsqRfhUc/Gq1MKNbBh9nIAMDjEppocxs9HCMktfGTCwP/oOBkUKNk/qF3pDYC6Ktk8RfWzyaaoKrqdDaBDwya8W1m0/CPCR3kFy7CcnmWQRUJqcRJFUKtTnPCeR71LwoeYF92CYyVnCFZpCTrRtCv5to2St8SOrKxiPqEEA4fkYT+mI0rdoeUiH1XZVuQPpsIKqw2QmfifTsnOABiWySlH9uU0Hh2MqjsZV5LtpPSoGeN9rKnhBX7ehoOSLIIPfnGONXGMMWN7xUfVldYDbjM3mrh5HCDgS17DhHgDQcIU+XbBxnDTn1x1UuQcJ9iv7l5Q5e1zLGri92EDJFnoAgHtcfr6wbbVXUqq193+0z97n3UJt1+d51n7aHwEGAAHXJoAuZNlzAAAAAElFTkSuQmCC",
  "dxf": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo5JREFUeNpsU0trE1EYPfNMmtdoH2kDNmJbaVFcaBVFpAsREQpFwY0bu3HjQnTj1mVd+ANcuC3qQixmry6E0kWFVIQ+bKy2tbFJm3emyXTujGca+4DkwsedfLnn3POd77uS67rw1vC79ek7fZEzpu3AYUqS9tKQGZPLpa3VXP0uFCmJ/8t9OLC3q/uJbcs5bkIybvdHoMsSbLKENRmvU2WcNnTjRFD7ML1WGSPJHI6sA4KRWMAWVDPxLYex3iCmfpuIh1QsFSyMxQO4GvXHHwOJ6XWSyIck8v6HQsnjAxFc7vTj2VwBg4aG78VdBHQFCk+dbVcxMdwev9gTSEC455sIBOu2KLsoJFzqasP9vjCeDBlYqzn4VXXwarGKZN7Crd5QfLDT/7KpBM84c9fFUFjFp2wdk6smflRsKKqMa7EgfJJ3Ac2OKlit2pEmBTQfngdpnupoU7BUtRGiiTe7fXiRqmK+KuDn6TpvYogmBRJcrOwIJLIWxmM+dOsyLKryQAaJpjJ1/AxrGO3SqdZt7kKZJrzJWBg5piHENuY8vV6e0UOye1TyftvC5l+gZB8SHJTwpSx4q4JeTUKaxhXoR57h7Rn+3iFolJ3xvPhab6HgJG/pJ7jsNP4sUX+jZiCgEsWd/DjH5IrSYpBUAr0yHpzSoXKOP25a6OBhndh0zcX1qIYM2RIbu6i0KiHD5B/GTMHG03kTGpEL7H80wHFOWwhqDZ+SpkBOtCDYJDhZE4gRcKNbYynAqbCMbXpwpVPFbEng0aKJGbYzK1p4wIegLlcEPmdt+DjXbzcsxFlCynRwwVAwW6hjqeg0Zt521SYCWCJvbe0Un29UDx7Hgrs3IEitHXkw3jOv2fl92D8BBgAJeyqBh90ENQAAAABJRU5ErkJggg==",
  "eps": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNp0U01vElEUPfMFCEVArdoSqEA0KV246UJdUJM2Lo2JK/9FjXu3utJqTNz4D9worrsQExbFpAFT0TYp0CZ8pIAiyMfMvBnvm2Foa9uX3Lw7c98979x77hNM0wRf7ufPsq7Z2SQYw2QJAkDxQalUZa3WI8hy3gmZr15bu+z8kILBkCeRCJi6bufKMji0NhwiCQR6iitdatTvQ5LyOLLEiWcYukm3m4Zhmbq1BX13FyoxuH7xAlbvpqKRK1fT0PWbRwEmDEyiy1QVg/V1GO02tO1tKLEY2PIy3KEAlmJRDLXb0TeZL+n9g4MHlLJ5HIBuYnSzXq+DlcsQLk/D9Hoh1WrIUjlPcpsYGQzS3LWoaBhvKeXWMQCDA1D9pt8PaXERUjwOjEZQFhZQp9L2yERiqYRCkPt/z58ogTGqHQLE1BLgUmC6XGD5AlipBIFKkbhanKHGYLBDqQ4ZED0OAbfLlo8OIxwGvhVgyTHlA3xkomjH/gegBgDURMv6faDbBZpN+/tHkUApkdTA/PwZAPxntwdUyjYA/+ZMqJHjLgM9iv/6zRt2GgMaIE21aVIjnSm0DGPfmhzyde0UAE2Dj+p7urKCPvkZku9eJILOSMUnkvVhIo7GYIB3xSKYdhoA1erXGVKXpvFxZwdBonnD68PQ7YEwM4O4xwMPxc8RYE87g4FIcz+kvfmnA0YzIJIy77/m0OCqsTkkCTysKPjJG3viLei63Gm3kCO6UWqcMejjxecMPmxsoFKtYop6UNirYL9Wtc5OHqzznIXHq1na7OfMJROcK8a6O7MjW7nfzZdrd7jzT4ABACh3NGsh3GcdAAAAAElFTkSuQmCC",
  "exe": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo1JREFUeNp0k8tPE1EUxr+ZzvRJO62lUAQaKIQ0FVJFjBBdoIkrDDHuXJi4NnHtX+HCjW408Q/QmHTRaCRRohIJifgiiBICTQu29mHfnc7MHc+MlECKdxZz595zf+c737nD6boOYzxJLC6Nhwej7e/24HkO779s7G6mMjcEwfKZ21+/d+em+RbagaFev28qEpZwzKg3ZckqCPH1nfS8hScIdyhBe6JqTG3PfyTTeLrwFhvbKdy9/xi5QglXL0yGJsKDccZY7LDIAwWHpSferWBh+RN8ni4UylVER8MY6PHj0uSpUK0hxzfTmWsUtnoEwO3rer64jEyxim6/Hy67DXaHExvJX3jw7CX8XjfORUdDlOohhU4fAVjILCPbm9V1yIqK2FgYt+ZmsZcv4lH8Nb5upXD7+hVMjIRQa8qeDg8UTYPU5cTcxSk4nS709XTD53ZhpD+IYMAPj+TBz93fZiz5oHV4AP1fGdlyHZIkIZkrI7GyhnK9CZXy+Aig6p1+HQAY003AcF8AVtGGfLWG9XTO4MLZ5cL0WAixoT4zVmPHADSiMo3hzHA/xgeDWFjbNg8H3A7kKnX0koEcPdTu/ylgRGZgOjNv38zoSXC8BZJDRKOlwGEV0VJVGM0y4joAPO1spXbx6sNHeD1uRIYGUCxVSRlDt1fC8rfvcDnsmJ+dOaLgoAs6AVLZPJJ7WdhEkUyT8GJpBflSBcVKDTvpDBw2GzQqQT1OgaZqUOhtFQUTUKnVTVWNpgy51YLVKph7sqKYkA4A1ScEfT66vm5kC3+ofh6Xz59FQ5bpkvE4QW3M5Apoyorhl9ABIKnFgNdTOh2NkJG6WSf9eRBJtmFwLDJmriUzeaOkYvvcXwEGAIVNH6cDA1DkAAAAAElFTkSuQmCC",
  "flv": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpsUl1PE0EUPbssLYUCXdpaC9gWoSTgAyFigiRGY+KjvuuTr/4A44MP/gx/gMYfwIsan0RjIjGiJIZgSIGFIoXSD0t3Z3dnd70zpITazuZmJzP3nnvumaMEQQCx3jx69SV3a3KWMxetpSgKxP3m242Do43SQy2k/YRydvds67n8a63k+FRSn7l/bdg5tdsAuM3he/5weDC8vLdqPLgIIpba2niux52mg//DqlsYSg3iztO7mczN3DJ3+ByCLgCBH4hOFEF7cDpzPCRyOpaeLGXSc2PL3HbnW3XaRQCPEgWI2MsRVAVqrwbX9bHxbhOKpiJ/bzpDOr2k68V2BtRNzMtqDEqPejY/4zSGjb54BM0mQ8k4xsDoIMauXxnqYOD7PmwScP31d0SS/eAuh1lrolFpIBQNQw2pqJdqsAlIceB1AJCIkkE/FZskXDQVRXw6IYHiE0nBEcaPXSSvJnGwWkQXAE4acAhbxPMJpOdHweoMhc9b2F8zwKizbdlyPLVH7QLg+JKBYzoorxzjz3oRzUoToaEw9KyO8XQW5AE5jrFT6AbAYVVNxCZ0Ka3So+DSTAoDiej5ywTySbls1OEDobhFlMcXxrHw+AbINEjNXgb7y6BndLhk8cRkHHbD7g4gEhiJFxsdhrDqaamBaDKKerGGSKwPI9kR9EZCaNA5ubE7A5s8IFhsrxQkgJhZoa/06xC5xRz2v+3BOjFlbqcGlquxsondT9vY+2pAJdeZR6fI355CgQCN2A4O1w7gkQ7cdLUOAKdhV6uFSv3kd/n8mT68eC8dKWLnY4FsfeZQh7nVVt0/AQYAsf5g+SvepeQAAAAASUVORK5CYII=",
  "gif": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmVJREFUeNp0U0tPE1EU/trplAqlL0laiw40xASByEJIZFGVnSvj1j+gWxNXJq7VrbrwF7h10cSNhMRHojEuACVBKmH6SJQyJeXRxzzv9dyZPiCtN5lMe8853znf953xcc4hztDzZ1+C6fQMHAfd4/MBFG+p6h/n4OAeAoGNToi/eOm+A50LKRaLh6amoty2vVpZdotNXccMEK3LwZxa2bsDSdrAqePv/mLM5tSdMwYBYqyvw9zdhUn/L59P4OGtG8qlZCoH254/DdCdQBCxqZu+ugqnWoW9swN5ehp2NotgIo6bGQWGtaS8+vQ5V9a0u5S+1gfABEilAqdUgm98HDwUQkDT8JXoPPq+BoM5kCYmFT9jryn1+hkAt7heBx8dhbSwACmTAUwTgdlZ/CVKJaLnI1GD8TikZiPSR8Gxib8chH95mZTxgwWHwH7+gFMswqcokIRbjMO2HDCnZ1VvArpjEmnKZc8+cZJJYGsLsMiZ8AgwEqaY6Mb6RQR33JFhGECzCRyfAFXNu9v+RVNRZWIMuDJNuYMAaDycUFGhCOgtuAtFVDA83G5A8TrFDw+F5QMAxAKJJxz2xnW3RPJGbm+rCyjotZetH4DGzaSSeDA3h4Zl4R0JOEZWTpIzF4n/m995bNdqZwB6m0gFft3Ak6vz+KYWwFsGlqIxXItEcDt1ARMEtKdVgZb+fwA0G2C2hXM0ZTZNRcSf0b1pmXi7uYnjI+Lfanm5fRQsK8BIxKcrK7i/uIgP+Tw+FlREqHN5fx/vyU4uHBE6UO4gDWqk/JFaLuMxcXeFk6TuJ90V0HOk1in7J8AAjmgkPfjU+isAAAAASUVORK5CYII=",
  "h": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbRJREFUeNqMUk1Lw0AQnf0woK0ttVqp0hwqVCl+UBERT94F7x78Cf4Uz179DT14F8WbYHtRkBYRLNqDtdaPZLObuLs1NGlXcWDJZGbey+x7QUEQgIqT07PL5WKhHL5H46J+22q22vsWpbWwdnR4oJ80LNiz2czGUjENhvj4ctIE4Wrj8XmPUlKL9nCYcOFzE9j1OKSTCdjdrtiLdr7KhVgzEvwW6krC92E6k4Kd9bJt57JV5vFK2KfRQRV+RAMkzxglYI1RaDy2dW1rpWRjQo5VGicYIorWVooFvQVCCAjG8Omw1MgG8AM0uSBUDSnCfk/IGCHwf3DCD/7UhOLBrFkDuep/hDUSSCv1iYo4rIfqGwmUSNJjfYbBcQKhZw0aBMA4B48LwBhBt/cON80HmM9NQ6fXg/Wlku4TwmNWDzaQqzHG+0PSKod5cH5Vh2RiAhYKc8DlV1UPSyuFMGygVlMg1/P6BC6DqXQK8jNZDXAYA1f21V34wMXYFaiyVw0rJyzLgs3VMkxOjGtix/V0XWChZ0cI2i/dzvXdfTd0Qf91BMPrhyNzgKfOmxaWypqaDXHfAgwAtCL8XOfF47gAAAAASUVORK5CYII=",
  "hpp": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAehJREFUeNqEUk1v00AUHK/XKf1yZdESVRBXjRSRFqMQVBA5Ic5I3DnwE/gpnLnyG3LgXglx4UDDLZS0RWkDLiRxSusk9u6GXSembmLgWZbX7+2bnZl92mg0goo3b3ffO/ncdvyfjHef6q2Dlvs8Q2ktzr16+SL60jhhZ69bO8X8ClLC7w9XdKJVG8fuM0r1WrJG4gXjgqU1D0MGc2kBTytl+7a9XmWcl1IB/hZKEhccq5aJJ/e3bTu7Wg1CVo7rNLlRhUh4oMnXoDoyhoHGyWmUe+QUbELIa7W8CjAFlMzdzeckCwFN06ATAn8QmDMMMGlMuwWucpoCHNe4jBkAMenjYvRPTyi53JvuwX8AplleAeBcRFrH6rXIxLim9I/pi3QA1RhKaYxdjkN8IwalCMIwWs9ljMkh0wzk+9M7w179C3LZNXxve2h+c3Hu91HeKmD/6zHOLnw83ilB1/V0CeqU3Q81LC/O41b2Btx2N2JVP2riR8eTUxmi0TzBwrKZMsqMoz8MsDh/DWuWhUBKURLKxQIeOMWoptYPnS1c+INZBkwISomOSsmBZS7B+3WOzZvrKGzkMAiGqNy7g+LmRkRfekBnANy2163PZXrSbrQ6vch19Xz8fPDHyL39QzkHBKedXjfu+y3AAGU37INBJto1AAAAAElFTkSuQmCC",
  "html": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmBJREFUeNqEUktPE1EU/mY605a+hhZTBNKRDApNrWIRA4nEBUZdmCgLNi4MK5f+FNdu3bFv1J1EXODCR1JJSMTwpqUP6NiCpe10Zjz3hj5Mm3iSybl37jnf+c53jmDbNpi9eb+6Ftcisea909bWNzNb6dwzSXKkhIt/r14+515qBqmDA8HpqKagh53XaopblpIbe+knDpFAhPab2Dw0TKvRK7lmNODzePBgZlK9oUWSpmVNdpIU8T+jaMsyMaD4MDcZVa+NhJMN00w0n6V2nN3yQgdHWZag+LzYPTomIAtT0THVtPGanmb/BbjwLFkvn2IttYGYplKyDzsHh7gdmyAWfh5zVq0Guhg4RAHFUhmfvq3j134aXo8bd+ITnMFOOovU5jbGRoZwNxFn1cxuAIcDW/sZDjA/c4u+BNxOJyxqaenpI3z88gMfPn9Hv98HQZS6RazW6kjExvFi8TGdDSy/W0Emf4LS6R8sv11BmfzSwkPcm74Jo9Ei0GZgmkw8QCOao8OXcaz/5vSZnPdnp3ApqBBLkWJE0Ci7ASzbIhCLLQ1E0iOkBDh9NpUgiUejo8oNuJwyn0YPABtn51UYFFivG3yBGCNZkuDtc/MW+ZQI3OrYpBaARCKufk3B5XIiWyhiL5ODp8+FfFHH+KiKSqWKUL8fC/NznGlPBmz+24dZjKnD0CJDcMoyW0SqXuMtHBFw7rhIAD1ErNUNafxKBNevapwu65NpEQ4FqXIA+RMd6VwBP3cPSERb6gLIFIq61+UqGWaFdcrVt/lmAuWjAi2aiMFwmOYuIJ/N6M28vwIMAMoNDyg4rcU9AAAAAElFTkSuQmCC",
  "ics": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhRJREFUeNqEUkFPE0EU/mZ2dra7bLNpi2AxQFKalkJrohICiYkXPagXrx78Df4K48GDBzmQePLMhUODNxQ5ciEkJVqDtJGmMWrCATRbd2ecoS5u3aovmezsvu9973vfPiKlhI4XL7c2r5YL81LIELEghLA3u/udxmHnPmfGW/Wuv+LpwwdneRYBx7PeWK0wOYYhcXxyckGV1fdbnbuMsXcklqPRJQxFMKz4RxDCtVO4s3xlRjWoB0FYjlQPEEBieChwKCRGMx5uLtaKs1P5ei8IKlGa/YkXMXYtlTEDlsnw/mMXhBJcqxSK6vlcpa4PEpCooUyIqs5M6hG1o2CUwqA091cFcYLf/sjzcX75EiQIojI9779CTYR4jwTBf+r7GAwh0AxCiL6JMT/04vQ79u8aI2O/7Jzg69o6Go8ewycUahtBpADhHKLnK/eVbkMdtROWIv80NQ2sPhncA9Htwn+9hZG0rY6DzFwJl+7dhs0ZstUy8rduwPS/wd/ehmi3kwq4zTHiWUgXp+EuL8FvNvFl5Rn4xAS86iyI2kY3n0Mv48ByrOQmancdi8I0Kcj3U5iuA29xAelKCUHrEIayzltagG2E4IwkFaQgSC6lYI09iN0d8It5uNV5nG5sgJdKYC0G8WoTOZvBISFNEBxnsuzD3GX4vfDsszzqAu0jkJQDedCGbB6AWg54pYbPo+NGVPdTgAEAqQq70PytIL0AAAAASUVORK5CYII=",
  "iso": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjlJREFUeNp0kstrU0EUxr/k5qbJzdPYpGkpsUJoA2q1oLjTdiGiIC5cuXHlxv9BEOrStTvBnQvRrSAIsejCrlqpsURq2hCJNQ+TNLm5uc/x3MmzJh34mDNnvvnNzOE4GGOwx8+t9XQkfn0VE0Y5/7Z+kHm+dvOhtd3P9c/xwNZh7nWaMYtNUmX/Fct/vlN7/8J5aRRgyzm8xzpRDjGE2aVH4VTqdnoUYg/XkEhmy+Cx3DhA5tMzdFolvg5Mx3Fx9SmH0JIg79Zo3j4GADMIokJTKtjbfAKXU4Y/2NvSfyH75TFOxa9Cmr0XnlPFl5ReOQ6wNMDsoFX6AElqQlNV1KsOuNwS/AGFjEUIDhmn5+/DMM16/9igBowAzFKIswPJr6MjlxFP3sV04gaP7RzMPe6xvWM1gNUBM2UKYlBau3QghGphg29J3gDlLLilWNdD3gkvIIDRhD9yGe2mCV0V4HFXuCxT5Dlv8Dz3sIkAs03FalDxBMQSt9BRBMhNncuO7dyU28c9tnf8C/Q0ZtR4GImeQSj8APLRH772BWcgiFODffCv/t8H9tO0v3RjV7VqkeeXLlzDfvYjj88uXhl4JwIsrYxmLY/M1gYclIvGE9jZfNPrSCD3/QgLyeWTADV6wW9AryIcCkB0u1Aq/oCPumlufoF72vIheaLDr4wCLIOqrYnULA14PSoqpSJEAUilZrD77Sv3LK+cI0+Be8cAbbmAOrob0agtD491LYfkoqvnyZLsWRkA/gkwABL4S3L78XYyAAAAAElFTkSuQmCC",
  "java": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjxJREFUeNp8U01v00AUnNiOEyepQyhQobRBSlVIoRCBEPTAjQsSEneE+An8FM5cuXLNoQduIAE3qopKNJAIIppA2jrOR93aa6/N8yZuUxyxkrXr3ffmzczbTQRBgHC83nj3ca28dD36nx6fvnzrNNrdp4oibyUmey9fPBezEgWVFuYLdyvlPGaMY4fl1aRS+9pqP5ElAkmcnknRwuO+Nyt5u/ETYfyj9WrpZnmpxn2/Ok1Swn/GvtnH5k4TLue4kNfxoFoprRQv1TzOb8cAIu3+ZD7oD/Hm7XuxzqRUNDtdkuLiTmW5tFxceBXlnXgQTAORSMt2oGezUJJJrK9dFWdEH7Ik4dB29LiESeUEJXd7/dAT3L+1ivlCHr8NEzutXTBvbJPPSdO/AH5wysChwM/1HzCGlmAzOrKxu2eCud6Z2Jke2MwThpUXL6Nn2ZAVFTlNw70bK0iRnGAq9qwHtOmTRpsx1NsHyKRVnNPnoMoK9kc2BjbD4vk5JGV5NkBoEPM4FFnCteJFWOS4ntHEfphQyKaFTWFLw704AJ26ZFx/ZEEi3YyY0O1Dmr4EKTUHA8hUnS6siI0DEHLYog+b28RCRuNXR/iQUpPUEQ+NVht6Lodnjx+GXYgDSFRnq97Ed2pXSlXhUSeGhxYc5sKlNXM5DGLR2TMwfZVPAIi+otGNWy1fEZUKeo4qc4ysI+F8VksLIJfYcD9QYgB/DNPMptWBlsnBIS86xmDMTBo/PWd0LB6VZfdEbJT3V4ABAA5HIzlv9dtdAAAAAElFTkSuQmCC",
  "jpg": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU8luUlEY/s4dmMpkWxRopGJNNbiwhk1tItbGtXHr0hcwmvgOdWld6Bu4coXumtREE3ZKu8FgOlC1kIoXtC3jPfdc/8PUIpzkBM7wf+f/hsts24YczuerGUc0moBlYTAYA+i8sbdXtAzjITRtq39kr73s/Gr9DTUYPOeamwvYnHdrdR0SnDebuCbswJGqpX+Uf92Hqm7hzFAG/4TgNr1uCwEJ0trcBC8U0Kb1/PQkHt9JxSLnL6TB+Y2zAIMOJBGLXmtsbEAYBsx8HnqCGKVScAX8uHf5EpqmGXv18VO6VDEe0PXsKABN8+AAgiabmYFNNJTDQ2RUFc8+Z9G0OPR4PKYwvKari0MAgiY/OQGCAajhMNR4nDZMaInrKBGl70SPMScck1NQG3X/CAWLE3/dAWV5hRRVIJxOWNksrP19sFgMqqAebUGYHMI6teq0A9oTVAhqu2sfbYYjsL7lCZ3683gA70T3TK7/B4BNoO020GwB9TpwfAz8LgMtWn/NkV8EHgoB81c7nYwCyBZlEVkHcqMTKFnkmehJTOPvEfCnKi0fAyADJKfXC/h83TaZTJjaa5lANLpOFqAXtlEAorAwO9u5syT5UxLfU0e3o1FMu1x4u7ODYq02BKAMAVSrSNLrK1MhLPj8mNF0vFm+C1ZvwKBwXXE4AGn1WAASazESwUW3BzUSMeJ2o1Aq4sPurvQYSRLwlhRR6mSaYyi0WlpAJrFRx3ouh5/lMt5lv8BLwXp0M4lSpYL17e2uK5wP6lj/c2ZPn2RI+YT8fDvqoyegVLyfG5kBKaQQOfvF2pLc+ifAABiQH3PEc1i/AAAAAElFTkSuQmCC",
  "js": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RUQ5ODY5Q0NGMTE4MTFFMTlDRjlDN0VBQTY3QTk0MTEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RUQ5ODY5Q0RGMTE4MTFFMTlDRjlDN0VBQTY3QTk0MTEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFRDk4NjlDQUYxMTgxMUUxOUNGOUM3RUFBNjdBOTQxMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFRDk4NjlDQkYxMTgxMUUxOUNGOUM3RUFBNjdBOTQxMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoT8zQ8AAAJdSURBVHjadFNbTxNREP52t7S0bktbKFAvTUVaw60YqkExUTD6oD74qC/yD/wp/gh885XEEI0RAyYQUiMpIBGMkYR6o23abi+73e2uc04v1LROMtnZPTPffvPNHMGyLDB7sbJ2ciUSli3U35smkK9t7x9v7n2dD/g8KUkUwWqeP3vKz23NxJGzgwOx0RC6mSgIo+WKuvP56MeUzy2nJEk8PWsGJVVTuhWbpgmHw47FB7d98Wg4mVWK52o1sxOg3Va3PmFp+Q2PdUquaFUM9/vw+O6cP3bxwm46Xwh1ALR3/vL1e+hGjcc9koScUsTSq3coVDQsXJ3wzo5HEs3clgZNMTVdx1T0Ep7cn6//QRQwMhzA6uZHLD5cIFEFSKIU+G8LK+tb0KsGZKcTJoEyP08AbpcLy6sbPKdQrigdAGaDwWxsDH1uGbliCYIgcM8WFPg8Mq5Pjzdyu4jYbCE44EepXMHuwXe+A8x3KKYxYsjvbUzmlPGpBmYdgI1oYjSMbL4Ao1YXMkcM2Dd2xnbAamPQAqg1GORLZdycmYTdJqFKk2DPR3fmwI4zBDrg9RADqxPAbPBif2WTSB584/3/TGegEOit+DRcvQ4OZJi1LgwIQKVCg2i6nb1I7H3Br3QWqT9pBAP9uDY5xjdSM3RqxeoUkfVnEOW8UkLykERTNXjkM7h3Iw6NNvHw6JjuhAhVrba0+QeALozcI9nQR0VvNxJc/ZmxCNGvIBQcpDG6udA22kyW29HC72wu8yG579ZoiSYuR/ly2+y9CA4NceWLmo717T1i5ULqJNtapL8CDACskxPFZRxLwQAAAABJRU5ErkJggg==",
  "key": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlZJREFUeNpsU11PE0EUPbM7u/2AtJUWU6qiiSYYo5EmmPDCD9AH46sx8cEnja/+CB989z+Y+MKPgMiDsYQACcbaWBBogYD92t2Zud7ZlQZsbzKZ3bl3zj3n3IwgItjYeDO3MlWme0bjUth8e8/fO2tHzx3XqUEk50uft+Ndnhdmc3SlfNPkVZT8Cy600DoIISvVfKYtlvfX1p66XmoIYsMZdjJQWvEFbbsC/S5g2QhSkKUK7rx6OzvzqLpsovAhaAxA3DUBQn2TUFsl7KwTfm4Z9DoO5LW7uPXi9Wxpfn7ZKF09vyPxX2iWcNRkKGZz0mQWKoNs8AVB6x1yRY2pYnc2LLofuXTxMgAlmlXIfngCxNxEzM+DPv6NQa2BygLgZyX6JT83ngHTN5GAL0WSoUQkSQnXkyBh/k0GegTAaldM20sTKvet+yyhIZApECamL0jUSe3oFChx3TopM4TeEQP2gc6BgGIwb4KGNXRhCkMGxgg2kJeybRiZM45D8W61qEAknSmpHStBhywu0nFVupSCTAcM4ECwqapv+NQ6LS9JGALoMIIoPYDjZiEL1xHtbyO39AQUDaA7R1AH23DSeSA4hv5RG/VAhxomPYP8sw9A4TaC9iHkjUWmrtGvbyC18BLe3GP0m3WW4I5hEBEnPIStXzyuFIxb4EkMEJ79Qa/xHbKxCdM7xeCwzUZOjgEwnuzt7qLz6T3cySmQP43uzjeIiTJM6io6W19B/NLCKMVGCzkCoLR/0lrfOI2fNy/huKC1FTsK/rbGNeMRC8dHpHByfu+vAAMAL/0jvAVZQl0AAAAASUVORK5CYII=",
  "less": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjZERjZENTJGMTE4MTFFMUIwOEVERjQ5MTZEMkVBREUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjZERjZENTNGMTE4MTFFMUIwOEVERjQ5MTZEMkVBREUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGNkRGNkQ1MEYxMTgxMUUxQjA4RURGNDkxNkQyRUFERSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGNkRGNkQ1MUYxMTgxMUUxQjA4RURGNDkxNkQyRUFERSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pl1w97IAAAJhSURBVHjahJNLbxJRFMf/wPAIMIxMkUI7tS0VYqlGDLGhjdKkqyZ24cJFN925de+XcONHaHRj4k7TND6SGo1VWwmp2kSLhlqMDbQ87gzPYcY7k4GgoJ6bmdw598zvnvM/95pUVYVma+svcovx8yMnFZHAMJPJBJfDzq5vpX6+/vD5qo/z7DOMBdo/d26t6jFMJ3iY51jBz4M+LP6wxEw40Gy23qYzB3HO7fpmpZCOmfEfa7Xb4NxOrC4lvbPToe2yKE3K1PdPwNOtHdx79ESfq4qKkijB5/XgevIyHxEC24USmewDqD2ABxubaLRkfW6zMqjWGlh7/ByyAtxYnOPnL0Q2+gGGmKRaw8zUBJaTiS5QOO1FJnuIAM8hciaIWHgi8NcSNt+loVDY8JBXh2ojJAR1HbTSNFMUpV8Dxcjg0nSYBrtBxdLbqI1iheCUh9XXNGurAwCdEkb9QyBSFam9TDfoPZ1LUg1BH28IiwEARTVAQOzcFKRaHZpLoa9avY6L1Gfs0c32t4PU6W2lWsV8LAorw0Cs1nXftYWE3qZGqwWHzYp2zzlgetuolVFvtiDLbRRKFTAWCxx2G/KlMtXFhWPqOzsWHJwBx7rxKv2R7mwFz3lw9/5DLC/M4Us2RwV0g3U58XJnF7dvrsBOoX0Abbej/DFKRMKI30fTVGC32WA2m5H9cQQvhYi0vE/7Wdgczn6ARA9QPBrBszcp/XvpyqxebzQ0Tlsq6llxLhe9bD4cFMr9XdjLHpLv+SLGBYHAYiVu1kNOpAaRTWbCejgiw0zGhFGSK1aw+zXbvfK/BBgAPwADAs5GpGsAAAAASUVORK5CYII=",
  "mid": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpsU01PE1EUPdOZKWUotKUKFLEWkQ1EASGGxGBi4sIVrt27IixN/Cn+CxfVnQsXJiz8IAoqRBGEaMUUWzofnXkz781436QDkjKTyXuZe96595x3rxJFEeTzaKW6dmdpfIoxjuRRFECGn7/4Utvarj/syWgflU5s891qvGoJePJasfBgeSpnW+yEIJVS4DEBx3FzGT2qfvh0tJxOE4mCU0yy8X3BLdODRQTJZ5oMzYaD0UuDePzkbnnx1mjV9/lMp+izBKEIwQMOzvnJGoYhhBDgFKtMjmBl9XZ54WapSjLnknMnEkQYgflCVhKXLt+/dRMy2d5OHdVnPoxeHUtLV8u2w5/S78UzBJwLMC8gAsosIqy9/ga37WNmvgKVKmEkb7JSwI3pIdRq1kBXBZJAUKkb6wd49fIzbJthdn6cIhE0XUWbyP4cmshmdZAE0eUBD6gCN0DtZwM7Xw+RUlVEJCui7CmyPaS94zC06ZMedREERNA6djBWHsS9+9fRS3p9AraOXbhELMlUQju2G2O7JAQENk0XhpHG3MIVlEZzaDbdOKO8jWy/TraGsMmL4L8KTgnIfcfy4JBWeQNp0j10MQtB4EJOg6qFMI/bEH3pGNtF4LOAjHMxO1dGvW4jXzDi7Iw60TB0jJRyONhv4MdunbDneMA6BMPDA6iMFzExcQH9AxkUiwby+QzevtnF2OU8lBT1i8fOa2UO1/FwdGTHE2STHM/14+vlPOz0RxibKPfn9AHXZHBzYx866ZdTKkuVndhHuqenS1h/v4ffvxqyvbUuAtPizZ0Dp7X1fTs+FA9cMnWd4ZG90NOjomVFzeTcPwEGACDGeYddZX86AAAAAElFTkSuQmCC",
  "mp3": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNp0U89PE0EU/ra7XWxpSsFYIbVQf9REFBHkYBRIPJh4wrN3DsZ4MPGP8b/wUCIHEw5EY0w04o9ILcREGmwVgaXbbXdnd2bXNxPahGyczebtzrz3ve99740WRRHkWn5cebu4cH6SMY7e0jRAHr9c3WxsVvcemmbys9yT6+uHJ8oaPefypdPDD5Ymh5w26wMkEho8JtDtuEOZFCrvN/4uJZNGH0T59D58X/C27aFNAL3Xthmsww5GCyN4+uzu+OLtQsUPxPQx6ZMAoQjBAw7O+bEVCMMQgqygs+LFs1h+dGd8bna0QmXO9OL6JYgwAvOFZKKoy3V44CgNfv7Yx8oLH+lUEgvzF8Ydhz+n41snAGRG5gUEwClzhHdvttFxfNyYK0EnJozKK5eGcf1qHo1GOxtjwI+pfvm4g/W1qtJgerYE2SXJSIL9+W0jk0mCShAxDXgQKgbNXxZq35vQKCiKQkSUXdc1+gcch1FHGPmKuIgBCdc66qJQHMG9+1NIpUylxxHtuW6gEiTIu+N4yjdWgty0yTmdNjFzcwKjY0MU7MLt+IjoSad16FoIx3b/A0DZ7FYXnsdpAjUMDOjI5zPgfoBsRodhhGhZHfBBU/nGAGRtxWIOg5lT2NtrI5dL0SB5KJzLodloqXaOEatPGztKq5gG3S5DNjuAK5NjKJfPYKI0okBkSdemCiSgS/rkQNLSePtxBj4LSCwfFtE0krqqX7ZVMnu9XlMXy2l7ME0dzA3iANQyY6vWxC61UY41zTyNcYh6/QCNXQvzi5dR39nHVq1BUyuMGAARsF6tbbe4iKD1r7Om5iFBdmW1SsDflLiuB6sX90+AAQDHAW7dW0YnzgAAAABJRU5ErkJggg==",
  "mp4": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnBJREFUeNpsk99r01AUx79psrTrujVtbceabnZs4DYRHSoMh6Dgq77rn+AfoA/+If4Bok+C0CfxVRDBh+I2NqZzrpS1DVvbtU3SJPcm8SSlsJlecsn9dT73nO85V/B9H0H78OLdt/LDlQ1uMYybIAgI9n99OWxoe83nkiz9hDDae330JvxL48O51Xxm/enNtKPbVwAh0Ec6kYpXat9Pnl2GBC02HrjM5Y7h4P8+7FtIFVJ49OrxUnl7ucIdfhv+BIDv+fBcj7p/tXMPrs2RXVTw4OX2UnFTrXCbbY7tpMsA13FDSDAOQ4gJEGUJLs0PPh9CkESsPrmxxEz2lra3rnpAt3G6adgdQhBpmeLkFodNmsjpOPoXBrQTDcmFFNS7i3MRDzzPCw/vva8ikU+COQxm14BBhvJcHLGpGPTOAJxxeLbrRgAkYujBdH4G5oWJWXUW19YL4XqunAMFhnq1BqWYgaY1MAHASQOiU96zKzkU76mwehaOvx6h9uMv7KFN3RopL4oTAI4HRh4wSl399xla+00YbR3yrIzM9SzSqgJJnoKcklGrH08CcJjnBtLLCsSEGGpSWJvHtDKNoFippsJ0ulIsDDUCCATMlBQkNuahEyiZTcLsmFBKaQxaOk53TlHeKkM70AjAooCghBOk9sKtIvqtPqS4FBaRnJSRX8tj2DOh3lFB5Qw2ZNFK5LRo6w4sKt2ggAzywidAMN/9uIPSZglBLDO5FF3mRD3wHE9qVRvoHrUpfn+UEQK0/7ShtwboHJ6jdH8RZxSC57hSVETb7e5/2u0FxqPHJow+8iZ4lYY2QGu3idhIxO7Y7p8AAwALCGZKEPBGCgAAAABJRU5ErkJggg==",
  "mpg": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNpsU0tPE1EU/ubRdlqmnUBboa0UeUQDiUGCC1+JmrhxoXt/gBvXJi74If4AV0Y3sNKF0YUaICqoIfjgVShEiGF4tDOdO/fOeOaSKtie5GZu7pzz3e/c7ztKGIaI4vn9p+/P3h4e4a6Pv6EoQBDiy7P5rc1P1Xt6XP8M5ejXo6UJ+dWbuemeTGdpvNdiNe9YvQLe4Bi4PmTpRmyq8m71rp74BxKF2twIHvAo+f/l1T2Yp0zceHizfOZa/xRnfBRhG4CQqAYioBWeXDyA8Di6ei1ceXC1XBwrTXHPH2vW6ccBBBMI6BsSUEQzakGL6xB0tvjyBxRNxdCtc2Xf8R9TyaWTDOg2TjfVdw6hqIoE9B2GxkEDWlLH7s4ette2kSp0oDRezrQwCIIA3oGHr0/mKMmE53qo23W4+w5S+Q5ohob9X3tgHgO8ULQACC7gMx9mKQP30EW6mEHpYi8xcJEdzMucjfkKcrTfmqmiFYBxCF/Id+gayKJwoQjHdrA5v4HK7Cq44KjZNWpagaqp7QACks0H9znW365ia24DzoEDozOJbH8eVtGShXHTwNracnsG7q6LzsEuaAlNPm9h7DSSVjLyCMkppDI+GS2StQWA1RlKo0X56n2X+6QHkmkDakxF9WMVqWyK+s/BrthYfvWz1Ug+zUDcjMPMm0h3pxEjFma3CbIuCud7oMc0LL1ZgmElpGJtW3B+15HIGNITrMYIlOH7i0U41NrInREylYbu4R5qQbQBaAh95fVKZCnpQCnb9DrWZyrRERS6NDeUw+yHaXh7rt4C4B8y+9vkwn7kwKNRpDoa9aiFKBYnF+RcREqQ2e1m3R8BBgAy9kz9ysCE6QAAAABJRU5ErkJggg==",
  "odf": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAi5JREFUeNp0UktrU0EU/mbu3FfE1KRRUpWYheALNBURUVy7cy9UkO6KW/+Lbt0IPsFui4gLBbUqFaUuXETUKCYa0jS5yZ2ZO557b5MmTXpgmDPnfOc7jznMGINYPi0de5UvmpORxpjE/kbNqW005DVu8TWw1H758ZfkFgNgJmtyxSPRjJIj0QTW/RDiYGXGb7Dl32/eXrVsd0gSCx9miqC0ooCdp69g5Q/h6OLN0ty5ynIkwzMwUwh2FwMdcbDiCZQXlkqFCpEoPT/wih1YjLInANcD+/Ua9bu3wJlGvrBZCmet2+S6ME5g4oGlZ9A/I70XCDhhDexPNTFmswJBwcnuXkF86VSNZxVu0ukLSGnBcqlnN4HoCQIaIuIv7LUooMOgQ7q75LAAb59B9gCBHSKgqemRr94mMKmD24CfM8nb7THYGQNLpAkUkcb66JyGBFFEWRVL57gFEH5qj8Lxwca2qS3EZaugmzAw24dR/XQgwtsCSBjPIdWbUoE2UJLBnV8Ac/ciWHsK9/glWLnD6K2vgPszsOdOQdfeQ1c/ThKoTgDn9A3KUED/52d45xchZsvorD6Bf/Z60riV3Q9Z/0bbGU1uopYGkfERSQ3VbsMwl0qlqoIARmSoPYXWy0dor79LfBMEEd8jGs/uQ3Yl7PJFNFbuEXiV2riCf88fovXhBbo/vqP3t02/ZYmJFqTkzY160Go9uEMbFK8hR/NrdXtFuUVmnmySVGgO4v4LMAAjRgmO+SJJiQAAAABJRU5ErkJggg==",
  "ods": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAetJREFUeNqMUj1IHEEU/i7u7Z23e8tGgneGQPw3hZDkkhQiSuwMQREba4uUgpVlCrvEQhurkCoWqcQQ0oTAaYKNqJygGEwgHCSB6Knn7eXcdX/GmdHVPWYFP3gw78173/vmvYkQQsAwNvckq96UnyIEh7/d4t7uUd/8y+85P+bXSX4grkhI6nJYPW7LrXpBK2YxiSoShhu4Buq1NPofDeqdrZ3Z4cl7D4J3UtA5VyVAlmJoru9Af2ZAp1lcCQ3nqgiuKmbY3l/BH+MnHM9GVLP0Ww3KNA33CQoQQnL834Fj74PUGkANEIkCSSsa8gQqgYTIcB0PVsXB318GInRiCVWCkpRFAs+j5gKlA4t29Ggh4d0t04FKt9PQqF4UFgumSEA8ApeaElilWbYRVy/lsns/N1QBkxtENF4jxPxcgcB1CZVOrvMteK5IQDtJJIGh++PcX9iYwWjXK37+vP0WdYk0Ht99jtX8JywWFkQChw4tc+cZcvlF7rMze+ubbxN40fMalRMDP/6twaiUeK7wlZ0TD0a5hLTWxo2d45KKprqHKJslTsy209s2wnMFBTYNZjc/oLt9gPvLOx+hxVJIKS2YW5pCbSyJTGMK775O8VyBwDJd2LTDl/X5i8v3S7NVw9vJb51tITDEUwEGANCx2/rXEEFFAAAAAElFTkSuQmCC",
  "odt": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAepJREFUeNqMkz1II1EQx/+7Ca6JkqyYiJ8cKEpAQbBQFDm0sVOsFBS9wt5KOTgEG5twxVlZ+XEnKNiIghYKxx5nwEpIIXaiSAgKGmMi0d23u8+3T7OaZJEMLG9mmPnN/w1vBUopLPNNhRWXHOyDg0nx82TiJtZPlPVoNpftc2cTotcHtxx06kdXpSQ/BvzKESZzIDmAz6y+NojOjpDMZiqRPIgNoFyWM8DrKUV7axO+gcp4g7AzmquAdVNqOgL2z2I4id1B0wgeygOyt/rLL5buLwAIDgA9dY+L+DkuDQOCrkMgBsRglcMOqAGwIstMg8AkGsuZMNUMRMkLqE+QGloglvlA7uIOAKvZajR0qJkUj/XHe0BTIclVKKlrfKsj9qA8gA6wqSJzPaXlr7ky//tdLEUfawsBjExUFGVWbT7AxSa42H2LMfODmvd3wKb7RAMLYwM8nts8xJ/pEe7/3PmP2eGv3D+9usb35W0bINoA7RmjXSHsH0f5Z/mUSZ0Ir2JmsBtD80s8/rGyzWsLFTD5yUQCbfUBHl9d38LvkdDTXIuHVBo0k+bbt06qO+yAPGXwe/cA4wO9PN44jKDG70GougIzi2tQ00ms7/3lpwnBBgjZ37Kkd1Shht5XzBIFl/ufFtniT/lFgAEAU//g6kvdGBMAAAAASUVORK5CYII=",
  "otp": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcJJREFUeNqMkssvA1EUxr+ZjkdbrfFKVD12ErYSRELY2fkH+BMsLcQaSwsrSzZi47EjJEQkEhYkFlhYSVtFpdqOqpk717l3jKZmiC+5mZlzv/s795wzCuccQncz3YeRBj4KHz0/RrOZe2NsZPP20o255zQ3EAxzEAC+6uzTw13G4TFQAakA/CWtIYbY0KBOrx7IvwDQqlHV1o3YxKTOvyAUvfQCfqmA3e4ikyS/zRAKvOot7eoSHEgZIHrCfQAfBqBaKQQDKScQAExd8emBANg+2U2CvNMkkgSqBmrCxFB8mujeoJBWwEqARcssKTAJEGrmaGrjqK1zvNknH4BtyxKl2VUpRxmj5W+x73q9AEaZrR/ND1EJluIpS3i9JQiA+a+hSq8HwJjTsLrRaWitPTCOlhEZn5N75sM1qigmlN+dB3u++Qao5W4TtbEXXIsiszGL4PA00itTsu6XnQWo0TjMTAJqfMDx/ryBJcaVzSNSH4fW0Q+rkIf5rsjRiid7yyN7uoXS3Zn0egE0NiORAN9bQ017D1Lri7CLlP2EDr3Rf7C/itzV2bfXA/igLDaRixfngFhSCooH2xVPCWBlwKcAAwBX1suA6te+hAAAAABJRU5ErkJggg==",
  "ots": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfZJREFUeNqMUk1rE1EUPS8zmabJdDKB2glEwY9ExJYiBUEQpV25qgtBXfgbpEtXuujKf+AfEKRddOdOGHClbYVCvyKWaijT2mhjphk7Sd7Me76ZONp0EsiBYWbOvfe88+69hHOOAE9f3zTVnDKNHvhlsfqPw/rM0ovyWsRFdXJEpDIyRnSlVz0KSkmvabaJeXSJBEhgAJzTDNybmtUnS5Pmg/lrN07H5NM/f13FoMgpXDSuhiIiK3Qi6LUugX7FAbaPPsJqfIHHKCStqRsXVFPQuZgD9BBxjikSiRq41AAkgCQBzVf0+BWEBX7GBm0xgHHUqk1UbBuEcIydzyCZlOI9YEGuDxwduCCitS3Xh3viCZ4jrcq4PJ6DLHd67tjtuAAXib54dCPVEfQ5XIcik/0/2iDeOYz3ceCxrisMi904y0XiMQFfkB7lg6xFHwFxEqUMV0anUNBLWKm8xd3i4zBWOzmASx0UsiW831mA59Xjm+h7HCOygduXHqJatzA7Poey9QnXjTuoVD/j/sRcmDOWLgqnLC5A2wwST+Pn8T629lahSCo291bwu9XA7vcy3m2+gTaUR14thrk9BXasbdiOjSe3nmPpwys0xSi/HpbDd3bIQC6dx/q3ZbRb/j8BEi3Po5cTJpHI9CBNDEa++GyDBN9/BBgAwfDlCVUQaNAAAAAASUVORK5CYII=",
  "ott": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdFJREFUeNqMU89r02AYfpJ0iVm7EqhVOxw7dDBEdpiCE1RoEZRddvUgbIex/Rs7eehppyF4LOzQu4MxwYp0HgShIuwwUVSCVtl0s13afl+SzzcpyZYmyF74eN583/s+PO+PSEIIeJZdrtQVI19Cgmk/Ph39bpllXq82g7sgLxVcyKNZpIx8Uj5u5zSjc9Gov8ZihCRC8D+7On4JczevGeTGSEIC4ctKJtB1DTPXi1iCCEkIm1EFlC2Em0iwtWfinXkIzjiO0jljtDC5TtflGIGUQMB+mfja/oPv2Rx9MMjpMdJxOXyXTwkcwIkewfqQ1QtQNB385zcI14FrtQexsSb6SRysZ4Fbf+F6eHwATc9gJGNAm5iCTL5n/LCVRGADNoeaGoHqyaXj5gqQlTODovcwNk5Aj6wXqV8eCo7EDhMonEHpW+dZC7gUG98D3geo7vkb01h9cAvPdt76OGy1xntUd3bjUxAk3+l2sHJ/FgtrT0MUJNfDSm0bjQ/72Hzxxo+NK+h3B7XRNO4UrwymQtMIkdTBU0m+sBOayLsn8Ka78mQDjx/e87HXPkb1+UsfP37+AmZ1fP/suknBb6nefVQXjl06TxMlJfWKNWr+Kv8TYAAkUueexJF47QAAAABJRU5ErkJggg==",
  "pdf": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNp0U0trU0EYPTP35qYxaW6TlDapNKWGbgo2FkF8rARB6rboXusf0F/hyq2U4krFqugqSBeuAyL4SERBstHa0iR9JKZJ7mvu+M0tqZGkH3x8987jzDnnm2FSSqh4ns0VU1ybFzj674Wa3uWiWbfsFQb+jrGj8Xvbm0HlvYVRxhJprpmTlGmum+OMm5uNPZNbtjk3l82ey8++8oW4Jv/H/wdA456g2kvH99FyHNiuAz2dwflbN8YW8zMK5Go/CMfQkAhpGsyQgRCtlpE4jIULyC9fHzu7MPPEl/5ib6WOE0JJNRiHHg6j86mMjw/2gG4bkbY4PW4Yj2j64skA5FTHdaEMPiAJszt1sK0d4suJmY4k0+IDDGRfqmh0u5gejQc+fG8eYCIahRQCEfgQnIuhEkgtONE+dGxYxEDj1DhiEycZ+1YXdUpHCqTMJIYyEES5aXXQsi2kYlGEia5GtHVKn+amPBeCutPgfLALPuVu+xDVPw2EQyFEjHDghbpYNm1yKVVnYjTOerepn4E6XQmLGSPkPkOXWATMSDcjQEkAaqOu6+i/rccALtFL53LI3r0Nq1ZD4/MXZJaWYFer+PXiJc6s3IEgY3+uPYZHTAcAHM+DTE8gnM1CSyaCulv+GrRy8uYyElcu4XfhLVpkpNtn/DGA5Uu0abFH36WnzzCayWAkmYJvWeCkfb9SwY+NDbSoOx4bYqJF8rZqVRRXV/HhzWtUSmWwmWl0RmN4v76OUqGASrmMOkntSHF8MOs954dT08W248wzYsJDOujRBAaqqikTpRo/qqd0/dv97c3Lat9fAQYA4z8bX9nTsb8AAAAASUVORK5CYII=",
  "php": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhNJREFUeNqMkltrE0EUx//ZbDaXNrvZzdIkbYOXGgxYQlCK2IIY6EufxGdB8Av44AdR8AP44JOPBR+Ego0PClUKTTXQSmkTYtOkmubSJrQ1e3H2yJSEJNIDs3PmP+f89pyZcdm2DcdWvn7LzkxFHmCIra7nm9ulg8yLZ09yXON55Dgjt1PM2iPs0+aW/frdh8bzV2/SvQBnCLiEqcFxLKSSodlrU9leiGPihWePBkgeEZO6ShC2dCAZNuf6ADb+ldQ5PUPx4BCFcgXfdwq4Ph1Dtd5CZi4Nw7SQiMdCXkl6yVIy/QBWgcU+yx/XsLK2cdHndqlK/lZxH/OpJO7fnsWY3z/YAq+g0TmHpoUH2vB5PXi8RD9Fo10aAmDJTgWyIuOupmK38rsPcOvqJO33XWEvwLJsmKxHRVEwf/MKWl/yUMf8mIloWN8rw+sP0D6PHQmYuzGNgCRiMZVA17IQV4OIaTI8buH/AJMFd02Tkp05PO4jnWvc57EDAINt7u1X8Pb9KgI+Lxbv3cFR8xjx6AQ+b+Txs/qL9KePlih2CMBCq92hg2qzt1AoV7H5YxdhdqhHzRbgcpFeqdUplpvQW4FhmAixZ/sws4BoWCM/qmsE5XqE3dDQCrqGAYWdejqZgK6GUD8+IV9VghBFN1RZJv3sT5diBwC15gncggCPJKF0WCPN8dun55jQdVpz3Ynl9leAAQAJhiGatD9AOgAAAABJRU5ErkJggg==",
  "png": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpsU9tOE1EUXXPp0CAUWmJbC04xBANNTF+kKhG8fID6aqL/gPEj9E0lIf6Dj30HL03wxQtVIC0QKrWxNG1Dk9Z2Oj1zxn1m0oIZTnIyZ8/ee+211z5Hsm0bYg29fLGpxWIJWBYGS5IA8ncKhT9Wvf4Yqprtu+w3q85X7f9QxseD/pmZMZsxN9fnc5JNw0ACGGv6tPSvyvEDKEoWZ5Y8OHHObKpucw4B0t3agnl4CJPs2YkQVu4s61ORaBqMJc8CDBiIRhhVM9bXYdVqYAcH8M3NgS0tQQsFcfdKHEbvlr6WyaR/V6uPKPy7B4DT7lUq4MUipMlJ2MPDUKtVfKZ2nn/5BoNbkONxXeb8LYXe/A9AJLNWCxgdhZJagDI9DZg9qIkEytRSkdqTSFQtGILSbgc8LViM+tc0yPfukzIyOJ359k9YR0eQdB2KmBbpwXoM3Dod1SkD+scpEapCI5DdpsJhIJcjajQZagcjI+5oLe4VkeQnyiZgdIH2X6BJ7dSqQLfrggjw0AQwP+/GegCIHppNoFAgEMO1RZKo7BQgRi3yN05cnwdA0BQMAgF3C6pnbuNg92M9AFT1diSCh6kb+FGvo2MxnBB9ocZxp4Mns1cde213B81e7xwAcl4jkaa0IUSjUdLJwkL0Ej6VSvArCt7l81iku6GrKnYEU89VJlSJRmR0Dax+fI9suYxSo4HlWIw6M3FBlnD9YhiXabyOsOeIqG7TzDeIYo6EDGp+ZPb2kKKqH8h+mkxiI5/D1/19J3bwYPvPWXq2skkiJVxesqt0XzghpKM8nRVV2Lv2q9eLIvSfAAMAaacnllcFBmYAAAAASUVORK5CYII=",
  "ppt": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkhJREFUeNpsU11rE0EUPTM7ySZpmzT9DNamWAtFfSiCigr+AxF9zKtv/hvf/Aki+FEi6ov4ItWHPGiwiBUKoUqqTUJImmR3M7Mz3t0kNe1m4LIwc+65595zlxljEJzdR5uf5nLmsvZx6gSvtd9W9bjhF7jg5dH9nRc/wq8YXaTSJptb0xklx7IZoKUEz1zJ2DUU69/37vFYrDxegJ9U0lC+AoIIVGg9CL+vIObP48KDQn7x0sWiVnJrnEDg7KGk+i/Ac4iUM/R7BsmrSSxtXMfa3X7el8+Kjf3KfUJ+iRJQw4w0Tc8BRyWGRAZY3rBR/VlC+XED2ayDhZyXl03+hNA3TxNQshlGLAnE44zCIL1goXZwiMNvB1i6zbC0KuAsxNITWwgNMYPeLVJiFEO9ArjHAivrAjNzBr4f4vwIgdGD4YUACsZCE8AtYGWT5jCsGQw5wEYJzP/pj5RwYTA1b07eQmfZ8P0sgdaM2FlYwWkMgMpl6NQAO33GKM0wsQWflkh1uqGVmVWblsiDkQyqxwfag35SqcktaEWTUTHYNx4iGU/C29+BvX4Lpu/C7zYgFjegSY63WySsHyXwpYHU00ieu0bAOuJbBTArBkiXKiaAmTzcvRJUV9E8rOgqBwqlY8ASs/AadbRLb8CzeTjVClqft6FdB17tL7yeCbFRBYoLr6vR/PiSEl5BZJaBD0/R2nkOZqfQ2fsKt+0SEQ+GLSIEUvJm+6jbah2+pS2aon+4g/afd4SYJVuA7vvXdC/IHQtSoTnK+yfAAIEaId1m+vudAAAAAElFTkSuQmCC",
  "psd": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAqxJREFUeNpsU01ME0EYfbtdKKWGtoItRWgJHApCBE2I0YuoiSaaeDJeOJh41YN3TfTixcRwMfEk8eDJGA+Eg0YTTRRMg02KKFooCBbTlkJLS7f7P+u3K9Xo8iWT3Zn55s173/uGM00TVlwZfzJztD92iKO5ouvQGQPHcQDN380vlDPr65fdLj4Oa41i9sFt+ytgN7o7woGOrqgvvpLBaF8vWj1NUAwGTVNRM3mf5vU/zaU+XySQuTqIFXz9hxmGLkoS7r+YxvVnrzGzlgXPDOzUZPT4m3Dt/KlIuH9oUjXYEHZZ/wOgGQZi4TZcGI5hLb+FO++TSOSKcLtcMA0dI0EPrp4+HtnfG5skiUecDGwQE2MjAwiGWlFVNDz+tIyCokJhPKYSX7Gdz2I01hOJdnY9rJ/7UwPGTEiqjtbmJtw4MYx78S/4Wa3h5UoOYwPdIOp2Xi/t18rlFgcDw6o+ydiWVRwOBnCpL0oOAMmNEhLZIgSeoxwGSWcERon/M9DoBknTIdNQNAMnO4PIVGpIFXcwndlA2OtGc4MAxml27p4AIulWSIa9QVadiYSoJxhqBJivKgh5ad3k9gaw6JdlDaqq7q5wINY4F22HaLHSDZQkBW72O9cBYFEviBIURQH7a7MN0uDisUW12ZZcaGlmdq4DwCqeTo1zNtZuW7hUqGIw7MNqSUS2ImNsKEpSdEwt5lGhfQdAkQBEoub3NNrDJfAIeBuRrcrY5xGQ2RFJAjl00I8PCckJUCB9q1URBnk38XEJEuk41tmGwZAf66s1VOh2keqwoUnYpFxHH4iKIixkN3HzVQKP3iQR/5GDKMuYmE3h+fx3MHqh1sMafztHLuiCg0FAk0uFdLqcpGY5QEXbTC/j7mIaVjc18DxufUtBJ/vcggs+3ijVz/0SYABsJHPUtu/OYwAAAABJRU5ErkJggg==",
  "py": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlVJREFUeNpsUktvEmEUPTPzTUFmgJK2UqXQFG3pA6OBLrQxamJcaYwuu3Dp0l9iXLvVtRuDpgt3JIYaTVSaxtRHsJq2xEJBHgXmifebMhECXzKZme+ee+65516h2+2Cn2cb2VwyHl12//vP2/zOQaF4uD7GWN69e/LogfNm7kUsPBFaXYwHMeK0OlpQEJApHJTuykzK98dE98O0bLM/UNgr4v32Dj1fwSQRt9dSsfmZcMa0rIv9ODaqYrPVxuPnL1Cu1aEbJu7fvIZUIo4bqeVYRzcyv/8c3SPYpwECt/dmu4ON3Ed4TymI+hQc1ZqoE+F+uQLDsnHlwkKMscJTgl4eJOi9fxZLePNhGx6ZQRRFqH4VjZaGSv0Y6cQcJLpra0ZguIWegqDiw7lYBBZV6xiGk9DQDLzK5bEyF4Hi9VLMsoYI7J6Es5PjeHjnOl5ubqHaaJGBEkzbxplQAKIgDmBHekDTgI+qKKqKLvNApgmEgyquLs1CoFn2Y4cIeLJpkjoCLkWnUSIF3JxISIUsCjAoxhWNJLBIJs3YeXj/08oYZkOKY65HllE/bkMmY504YUd40HUq2JSSyW6iVPmLiXE/ZMYQCU+hXK3h1toqdNN0sEObyKtqtDQ6kXDwcadDS2TBryp4nX2HxXjsJK6bDnZIAZem6Tp5YMMmicn5OC4lztNWtvB9cg+hQABtWjKL2jH/T3GgBcYDXEE6mcDM6SlaJAGMWkivLBC54ZgniZaDHSI4rNSqn7/t1vgkGJPwZXffSeCjk2iUWz9+nSTQN8e6ef8EGAClUi/qoiOc3wAAAABJRU5ErkJggg==",
  "qt": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNpsU8tu00AUPU5sp41NkzRxpfSZqi0VIIQqEEJUZYXECvbwCWxYsuBD+ABUFrDrCnWBQEJdIWigBSr6pqRJ1ebhxrE9M7aZmSrQ4o505fHMnXPPPWdGiaIIYrx89GKpNDdxmXkU3aEoCsT+z8W1Sm21+jCpJctQTvaerj+TX7WbnJ+0cpfuX8mQtn8GgJ4AZtIFY2Hz3foDVRcgyt+cRHcS0IARh+D/8G0PpmVi7smd0dLs+AIjwTVEiANEYYQwCHlEZyJgIQKfoX84g9uPZ0cHZ4YWmE9nuufU0wABCSSImMsWEgqSuoqA/39/swZFTWLy7vQo7dDnfPvWWQa8GuOV3IYLJXmyzDzG2/ChZ3pwbHdQ267BKJoYuj7SF2MQhiF8LuDK/Gf0DKTBKINz1IbTbEMzU1ANDW7LAfEIQKIgBsBFlAx6LYOz6MAcvoDCtAVGGPKlAiIu/F55F33FDA6W93EOAOMaMOl7biKPwRtD8Foetj5sYPfTDtxjl1f3Ubo5jkQieQ4ACSUD2iE4XDpAdbUiW9D7UsiN9WNkZgxajwbd0LGzt3keAJPUc1N5SVeENT0Ao2BKV6QzwlZeRBSKAYhe3aYHcZWn7l1EfjyPypcK9LQGa8qCvW9j9+MvaasQOHaRhGWdhsNLR8hwodYWf6B4tYjDjSOovRqq32rSYq/lytw4A77o1V2ERiAtzY5kkUrrsH+3QF2KY87ArTtQuQ6nAf4x6FCV1D001+vYersBM2vA4y1Rm2D7/Rac/TZIw4d/6MrcGAPf9htN0miJh7Lyuoyvr8rQeP9iVJcrSKgJ+TrFcyYebXTP/RFgAFQobmIOBxbsAAAAAElFTkSuQmCC",
  "rar": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpsUktPE1EU/u68OgylZXi0hZACQU1LEKKCMcat7jTRnQsXxsQtv4E/4M74P1iriUaNCw1FgxpjCJQKKAU60+m8mJnrmSll4XCTc8+959zz3e88GOcc8aq9evChOHl/lvMoubvWX/z4+BwTlbvw7bXdg8b7h6LE1gGW+O88CRMt4XTlR6/rYxce5Xv3jlHH19fPkBu+gWy5mlcFb3Wn/umeKOEMJF5C7xCFbtA9dRXjFoYKGiTRAlPGUV1aKU9O3VwNQ74A8DQAIZxqAuAhBPIMFYpQVAVB4CPSZjEzv1weH5tbDQN+JQ2Abu488mnzIbAAA3o/VK2PwDJo7r5Fy7ZRuvi4PFS6+qIXdVYD8Jg6BUcuOD8BozSLlRWyicgVKkTMQWwUlFF0Ooe5FIPk57BD7G0SiywyjD8bCDyHsOkeeeR3SUxEkROmU6BfQYFJMHfhWXV8efkUrb13VPMTsrcTQSzxZ/+n0GVA6EGbSGdgG9vo15fg2nFgbO8k70SRdd+mahDT81vUxTZRlJBRMsjq89C0EXCvSf7TIBZ136YZUJEiE7LgJ2dN01BZuE0dkIhxE7KcQTK1QUj+cwAEyrPZ+IydzRoyah+mLy2isbWBweESJEnB9q+1RM9Ub9GQOWkABg8HjRr2d9Yh0hTlBlRsfn+D4vg0BvUC9rZqECUJuk7Tzr1zahCYlB6HJAREPwfbbMBzLBzsbUKVI0qBgQkc+SxgWUYaIAqOpKwKXJ6bgGlaaDV/YvHaFNrtDsKTfVSrJeqIg/bRNwjclFIALeP3saybhu8SC4VBHwnhBXXIKocYRXD9QzBi4Xgchmkd9+L+CTAAMqwy+ZzluBgAAAAASUVORK5CYII=",
  "rb": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAixJREFUeNqEUktvElEU/mag5f2yJhXLwxIt0kiqsVEXujP+A925cu1Pce3WtXVtYuJCF7KtTY0NrVQIpRVKeXTkMcO9F8+9ZVooJJ5kcmbmfOe733fO1YbDIWS8+/g1dycVX7W/xyO3vdsuVKqvnE7HZ230783rlyo7bVBicSGyfjsVwozomVbIPe/c+FmsPHfoRKJd1HT7hXHBZjVbA4aA14NnD9bC2VR8gwuxPi5Sx39Cp+M0XUP0ahhP1jLhW7HFD4zze3b93ILtXYyyVKlR8/5hFbnvO9gtlrGSjOF+OpXkYviWyo8mCS4R6bqO4p86vm3v4fC4DrPfw4unj1XN6JvBaQtjChzUXK43sVU4wNFJA43Tv/B73edQwTmfIhAjCVL6UdPAj1IVFSKhCdAcAI9rnjBiAjtBYEu3GEeh1sKJ0YXR68sVIujzIhzwY8DEBHZqiLRKkicQDfvABxaiQTc4Y/C65pCOXwcjcmlvJgHtlwi4epYifiQWgmoLZwPW6HQG07LgcOgKO0UglAKOTt/E+09fwAiUWU7QAE9xUK3jbvomsispZVHMVEDSZdHo9rCZ/4VIMKAu0XGjpU7d2S8hk0pCELHEzrjKnCQOYJoD+Dxu1RyiwUm5LaMDo9NFt2cqDLvY4oQFp/QpfT/MrmI5FkWebt+NpWto0j2QmQkOjZ9hpwhqjXZzM/+7LU+cc7lRrjXh8/lVLRK5ovLWXglOsiOxdt8/AQYAzv8qbmu6vgEAAAAASUVORK5CYII=",
  "rtf": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe5JREFUeNqEU01PE0EYfnZmd5FSvgLYFuwWt9EgHyEaox68eDJevHvwJ/hTPHv1N/QgZ2NC4g3kUAQKFKGhjVKqRrvbnRlnht262FHfy+y8877PPM8z71pCCKh4/ebt+rJfXEz26Vjf2mnsN5rPKKWbVpx7+eK5Xu2kyMtNTd5d8MdhiJ9BOO7atFI9ajy1UyAqSPIRMR6ZmoNehNHMMB7fX/UWvEKFMbYKE8DfQnAhwRmmJkbx6M6S5+WmK2Evup2c9yUk2nnKA0XVcSiGXAe1k5beP1i+4RFCXqnPywB/AKVzK34RjHNYlgVKCH50w7EBBogbTa/AVM5SgBdn0gc2AMDjPsbFPz2xye9asweS6n+NTbG8BCCfUtLjff2WoVnVpAH6z6hMUtJE3EykYfpF4vUiL3QNS7FMeSAQRBHW3r1Hq91B+VoBQRji4+ExFsvz6Hz7jm7Yw5OH92AcJKW9G4SoHhzhy/lXbB98Qmm2oCXN5WawsV2TACEoJXqwTKOsb3BtR2ucmZxANpPB8JUhyPnHWDaDpfJ1eZFALzJJ4MKO5MEtv4TSXB7V/br8iQLMz+almRZWbvoo5q9qRlxwewCgeXbe3qrVO5ZkUD/9jJGRLPaOm6COi92TU1DbxYe9umRD0DrrtJO+XwIMABWp9nS+FgaoAAAAAElFTkSuQmCC",
  "sass": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDNDMTBBM0JGMTE5MTFFMTg3N0NFOTIyMTQ2QzhBNkQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDNDMTBBM0NGMTE5MTFFMTg3N0NFOTIyMTQ2QzhBNkQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowM0MxMEEzOUYxMTkxMUUxODc3Q0U5MjIxNDZDOEE2RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowM0MxMEEzQUYxMTkxMUUxODc3Q0U5MjIxNDZDOEE2RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Po72XUcAAAJcSURBVHjahFJdTxNBFD1bykc/ttvdtttWGgI0bYrUgDZoNYqRJ014kMRXHvwB/hQTH/wFhMREJfFBQxBjhMRIFEQSCAlQxKYGggiU3e3HbnfX2bFt1EU9k9m9mblz5p4zlzFNExYmpue/jmTSZw5PZAl1MAwDT0c7O72wvPdudeNakPNtOZ0tsM7cvzdOc5yN5LDAsTFRAJks/kC2PxFRVe39Si6f4byez62EpAEH/gNN18F53Ri/Ocxf7OtdLMpKT42s/ZPg1cISJp/P0tg0TBzLCoK8D7eHh4RkLLJ4cCz12AjMXwgez8yhqtVo3NbqRKlcxcSL16gZwJ2Ry8KVc8kZO0HdTKlURn+8G6PD2SZhLMQj96WAiMAh2RXFYKI78lcJcx9WYBCycICnpNbojUWpD5Y0C4Zh2D0w6hWc70uQZC+IWfQZrXF0IsHvY+meBd08haAhoVMMQFJKWF7PNZM+klhRyogGhbqxOIXAMOtEwGAqDqVcgbVkkE+5UsEAWavf0az2t0ZqvK2qabh6IU3joizDwTgwej1LdVfJXkdbK8mt2QkayO99A0/0trQ46I1lVcX+UREhnsP34yLp1AD1xibBMuntpzU8mJyi3Tc1O4+l9U06n7x8Q/8PHz1DrrALt8tlr0CrkbJMHTop9Sk5sLa1g8L+ARJdnShKClY3tunN69t5iGLYTlCtakjFY7gxNABdN3B37BaqqoYT8pyX0in4ORbRkIA46YlDRbUTbBZ2Jb/Pw4qiKFnapcpPo9pdbrg8DjAOBsFgELJmsGs7eWkkc5bu/xBgAHkWC6UPADTOAAAAAElFTkSuQmCC",
  "scss": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkM4QjYyNDVGMTE4MTFFMTlBREZCNDNEM0ExMTk0MUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkM4QjYyNDZGMTE4MTFFMTlBREZCNDNEM0ExMTk0MUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGQzhCNjI0M0YxMTgxMUUxOUFERkI0M0QzQTExOTQxQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGQzhCNjI0NEYxMTgxMUUxOUFERkI0M0QzQTExOTQxQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pkf1yeMAAAJbSURBVHjahFNdTxNBFD0tLULpB91uodVWPmorUIxo0VSiNSExMYYHE33l0Ud/in+C+OSjYgjRGDBRCKJIUkIEWi0WKlja0ul22+5219lJ26gLeiezuXvn7rnnnrlrUFUVms3Mvd2bjIyezRVLBA0zGAzo6jhjm1te+7EU37rFO+w7JlMbtG+ePJ5mOaZmci/nsPl6ONBtw18WDQc9tZq0sp7YjTisXV/NFKRpRvzHpHodDqsF03djzuvDg6vHJWFAprF/Arxe/oins6+YryoqCiUBvNOO+7FrXMjnWc0WyIAOQP0N4Nn8IqqSzPx2swllsYqZl28gK8DDyRvcxKXQvB6gISYpiwgH+jEVi7YAfW4nEqk0PJwDofNejAX7Pae2sPhhHQoF63U5Gai2Bn1epoPWmmaKoug1UBoMrgwHabIVVCx2jdrKFwm67TZ2plldPQGg2cK5HheIUMbaZqKV9In6giDCy3MNYXECgKI2gICxoQAEsQItpNCHWKngMo01arTY/jFIzbutShJuXh1Fm9FImYiM7tTtKOtbO+toN9Nc+fQ5SGUOIVYl7HzPIH2YRZ0y2KZ+sVzBHn2v1mpMGx0DTaR3nzfwfGEJdybGkdo/wEigDyvxLzg4yiESvojZhfd49OAeLJ2degaSLIPOO6vwgiYaaRErTRREEdn8MeJbSVZ5M7nLdNExqFLaQwEfFfACQn1+HBWKSKb3MT4Sgstuh9vVDa+bQ4DORE6o6RlspzMk9TOPfr+fiLJCLFYr3TZSKNcI7+aJwWQmPM+TkqRg49tu65f/JcAAMwMas6WUKd8AAAAASUVORK5CYII=",
  "sql": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAh5JREFUeNp8kctrE1EUxr+ZyXMkoa1NBROaSkpTBE23PhZ25cql2y5duvAPUdGFS1FxIRRBXZlFQ9GVdDENIhGJxkDsw2mneZnM83ruNZlOmNoDhzlzz3d/9zv3Sowx8Ch/qlYK2XM3cEJsbH0+qjV/rd6/u6aN18b7RMFT+9aosP/Ex+0ae/puw7j36PlKEMAzctKJ3aGFamMHjV0d+wcGitkMrpWWp6hVIciEk2MAOwbUWjosx0UiFoWqJpGMx5DNzODq5aIPoa82AWBg/lyKLMH1PMp/a9XvLXLzG1cuFlBaWpiKxaIPSLY6CaC93ggQjyiQZRkeQSzLRovGaPciWLt5faSWEBoh6KBvOhiaNga0+Y9pwaFxvu7rfp8F5pWDt+qNMp2IijHGwddWCvN+33/CoAOP5nVdT9SdoQ1JkggiQ6Yvr7V60+9z7akA2gfH9cRF8hO5F5Ve4lQAF9uuK+qFsylkzsQxrcaQm04hdWkR83Mzfp9rQ3fAFzu9Ph6+WMfjl6/pGBdb2jbKmx8QlRjWy5vkyhUZBPgOeGNHN9AbDLGUz6He2hVj3Ll9C8/evsdgaMK0HV8bcmDTU0UUBYXcedR+NLGnH0I3jvDk1Rsy46FP4C/1BtrdntCGHNiOAzWZgEKQ5Qt5lIqLojbaXSQTcRy2OwT4SZqk0IYAOgkVWUE+lxX/zb0DpFNpkTzmZmfFtzewhHYcfwUYAMZmVaZQlLFHAAAAAElFTkSuQmCC",
  "tga": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnxJREFUeNp0U89PE0EU/ra725K22ILRGipb22pMG6JcSEQTbUIwnozxpBcvepeEP0KPogcT/wlNT17kIKbEmChFUYKGVtL0R2gLtNCl3Z1Z3+zSAlonmezOe/O+973vvZEsy4JYnqdPMu6RkSQYQ29JEkB+PZcrslrtPhQl23VZc8/tr9I1yMHg0EA8HrBM04lVFAhoY38fSSDQVN3pfKV8G7KcxZHl6v1xblqU3eLc3p2VFZjr6+gQgwsnhzGTuq6Nhs6kYZqXjwL0GFhEl3U60OfnwWs1GGtrUKNRsKkpeIIBpKIRtI1J7cX7hXRhc/MOhXw5DkCZGG2zXAajzFIoBMvng1ypIKOqmP30GW3OIEcimovzlxRy5RgAFwDEAIODkCcmIMdiQLsNdWwMZdJlg8pzEUt1aBhKq3XinxKYqF9yQbqRIqsMy+0Gyy47bKgUWXSLtDENE5wdtuqQATm50F1VnPbRGeEw8HXZbiV8fsDvI9ldju9vADAyihLEbrWAZhOoVp3z6iqBUiB1A4nEfwCEsbkL/M4TgE5n5jDx+oTEzp1d8m9tC8H6MaAB0imzx0NU/WKUYE+loEyawDBo2ui6TGfT6ANAxrvx87gYCGCxXEKVJvCWFsG3eh1vN/J4OD6Od4UC8o0G3TX7TGLHwI9iEQmvF9X6Fh7F4/iYy+GcLOMSlfEgGsP0qdNOmX0BiGKpVkV1bw/1nW2b/gCpf1PTcI+Y7eg6ps+G4bG4PR99SjAVo9HE4q+fKNE0vl5awuSohjeijbRefVjAtUgEQRK7Yhi9OKn7nKWZxxlSPWl3QwgnaIrW8QMhD542vUbx/W49m7sq4v4IMABOqi3Ej7bAEAAAAABJRU5ErkJggg==",
  "tgz": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpsU1trE0EYPbMzSTfdtInFtkkpiaXVWou2FRUEn/so6JugL/oH/Af+B1988if40jcFERQURNBSQdDWlLQN2lsue8neZsZvc7FoOrDszM75znfOmVmmtUYyvry++36yfOeS1qqzDtvH2P76ApPlW3Drb2sHex/uccHWAdbZX30kO2+B3siN3zhTnHuQ66+95i423jzFzOVljBdKOZNHazvVT7e5wF+SZBj9iZJ+3J11mbW2kR8T4LwFli5i4fqTUvnczTUp9RLtDhKgJx0q4dEwWAxrREKICHEsoYYXMXvlcWmquLgmY71yCkG/c0AkARgLMZpnMDMpGNzEYe0dGp6HwvmHpbHC1Wf9MnFCkHQOyYEPzSJwQ2B65Tm5NZG3Fshim6wbMNJn4bpHowMKtIqo2COgR2IcAptwjvcgo6i77igjEmVDqbY8xQJ1VwRULhiBI6+G9Zf3cbTziuzIDkmHSNqECTFgQScEcYuc2NA8TcdYwXD+GkK/TYVN+u72WrIudiAD8o6oAR2RRCmQMjis3CIy1iSpPySCXhFTXeyAgh4BR+JVw8pauLi0Cp4yCX9A90FQhnSBYtnF/k+Q+HYam9itfIZB3QvT8zj8XSW5EhNTs9ivbSLwPUzPLNPJBIMEKnaQYg6aB9+RGR5F5VsNgnNKXMI1NdJGG5WfHzFVLJ7k8c8xUngpVodlDSGbFYj8Y4yMpOG09lHf3yIFPzA3fwHZTAQVtU4JUTeFDrdgDdlI8wAz5Qy2KxswReI7QODZcOr0ZH3q2hIDBI7zq16tuk3FNPxAI4wN+pkoccYoE4YJU5EdUtM4Qst26v26PwIMAKj3P/2YUKgYAAAAAElFTkSuQmCC",
  "tiff": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmRJREFUeNp0UktPE1EU/qYzHWstlrYJNcWUElyUJsaNGh9B0g1Lo0v9Ey78EbrVxBhXuHShm25YGBJRQpAYBDEWpaEPEhksdVpbyjzveO4MfZDCTWbauefc736PIziOA77OPH2yJCcSGdg2uksQAKofFou/7VrtASRpvVNynj13f6XOhjg8HAlMTIQdy/LO+v3uYUPTkAHCTb+cK+0pdyGK6+hbvu4/xiyHbncYAwfR19ZgbG/DoO9LsSgeTd9JXoxfyMG2rvQDdBlwIZauQ5ufh12twioU4E+nYU1NIRCNIDs+Bt28mXzx8VNuZ796j9q/DgAwomwqClilAmF0FE4wCInAlkjO4y+r0JgNX2os6XPYS2q/cQyAcQatFjA0BPH6NYipccAwIGUy2CVJFZInkKlyJAqx3T4/IMGmJkeWIWSz5KgI5pdhb3yDXS5DSCYh8rTID8s0wexeVD0GtMd85KkkefFxUfE47M1NokbJkByEQl6tL+ouAI+MUwbFhnYbaJKc/Sqg0x4H4eDRGDA56fUOABA9/GsCpaIHwr8FOhQ823O5RfW66tUGADhNy3RNRDjcN41HLxdQ8J6jYTsOQLfOJBK4f+s2/uoathoNGKT1MtFeVHZxdWTEZfEq/wMKl3rCJOIzTV6ADs2R5ulYDDNkYjp0DhrF+zCVgkw31+v1UxjQZkNV0SADd2o1MIuc9gmY+/kLxb0/UFoHePd9A1qzeUoKpilx9xcLWzgg+u/zeVfuQqkM9bCN1ysrWKXxdtPgvScwUAm58XZ52W16QyPtifRUzi588GbEi1ztHPsvwAC4uC9qhnsZvwAAAABJRU5ErkJggg==",
  "txt": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAeJJREFUeNp8UrtOG1EQPfsyXiyzBguIJSyChZBBEFCKpKHLo6egpErNn8CHgH8gkZIiTSIXLhJAWCgkoMgRMSiRBSK29z4y9+I1d/HCrFb3MTPnnjkzlpQSynY+fP70fGF2gQuByCz6lfdd9Uurfvrrjes6762eb3tzQ69uFJwPsqOPC+MBEmxxphi4tlU5OGmsOzaBWLc+O9oIIVhScidkyGZ8vH62nHtSKlaI4cse6TjAfSaFBBcco0EWqyvzubmpyQrj/FXk75cQaSEMeMXU8xykPA/Hjd/6/LRcyjEpt2i7HAe4A2TeLZWKUOJaVLxj27j813EHGKCXaAJExu/4BOdiAED08riQD2riOrexyRoYc3CvsAbLGAAjZga7vgZG23WMCdBvoxKJc36TRBlMiaa2JByjNqqD8qkYc1pjDK7abey+/YhrWlfKswhpiCR96aEU9o5+QE3g2ovVWDm2Sc22bBQm8vrVpbkS9r+doPr1EOWZaQ0yFoxg2PcREosEAI4uvZhJpzFMP+cSXRbq+043RManez+tNWKMI6GN0g0Z04HFR+NoNC/0yx717efZOSbzY3AcR4Op2AGA5p/W31r9e0vNgSrh9OwCrpeCkqvZuqTybnpRqx/r2CjvvwADAJC/7lzAzQmwAAAAAElFTkSuQmCC",
  "wav": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApFJREFUeNpsU1tPE0EYPXtpKbX0wqUQKVQMFdIXQBNCQBs06KP+B8ODGh+Mf4b/4IsGE54kxhcMBrkp7YOQgBRvSKG73fvsrt8Otoask0xmd+b7zpxzvm8E3/cRjPkniyulW0NFy2JoDkEAguOlpXJ9p3L8MBqVl4O9YHxae8pXuRlcGO7KPLhfTDVUqwUgigJMy4Whm6lEXHjxYf3XnByRN0QB/2KaH7btMlUxoRJAcyqKhdOaht7+DJ49n+2cvTnwynXcsb+kLwJ4rgfmMDDGWqvneXCZS9ND7mov5h9ND85M9y86Dpto5rUkuJ4Py3YDJpy6QGJPayqB+Njf+43XL220t0cwOZkfrNXsBUqZugDA6CbLdAiAwaek1ZU9LmP8Rh6S78GsGxjOp9FdzKJaVZIhBgGASzK21w/wbrnCk8euX+EMAjaaZuPHdwUdHVFYluuGPGCORwwYjg5rqOwccRk+3Ux0IEvntmsNG4ZmUayL/wAwKHUNfZfTKN0ZRaw9Cof8qJ/pMAyHy5KkAMTksSEJtnMenM7EMVMawbejMzJRh67bXEYiIXEAVTW50SEAhzqwfqrBcXx4VOhYm4RsNgHbsJFOyZTsQ1MN+hcohoUlkFiMT+TQFpMwXOjGpXgE+XwGk1N5pFJtKNCequgYGupCRBbCDOp0KBJc4VoP3dyBONW8uydBgBHUThqQKCk3mEZ/LoUG+RBioJO7VarAwEAntjYPiUUW9Hh4b2R7k9j98hN37xWx8fGAt3eIAdVMLn+uUv+b2KReSCZjZJiB9bV9jIz2ofr1BKvvd7G9dRC80lae0HzOt+cWVnrSKDrMJykifwNBpCgE/UAllEXufmDu8Zlffvvm8XSQ90eAAQA0pF7c08o4PAAAAABJRU5ErkJggg==",
  "xls": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmxJREFUeNpsU0trFEEQ/mamZ3Y2+0zIC2MmITEkUYgERFQErx5E8KTi1b/h79A/4SW3nCNeYggBYZVEMU/y3N3Z7M7OTD/G6lk2ruw20zRdU/XV91VVG0mSQK/3n1a/jky6d6Xs3G8WXS+Pw5N6LXjLLGuna/78oZKerGsYKtrDE16uJGL1L9gEOOcYd2dL1fNwrbL//aXN7J1efPMmkUqEFAk0A0VZNbFEaQCBscIkXj975y3NLq9xye8PBkAniHOFph+j2eC4rsdoB4LsFubGl/Hq8RtvYWpxTQi52o1jvWiGYaRZL0/auDgOkC/Z8BYL2Pqxidp1FZkhoDxpeaXA/Ujuj/4HoOxKKjiOiek7RUShRNQWaNYFQuMafrYCxiw4ozZKfqbYJ0EvRdl1DQyyTs8XCNTA6UELMwvDyLpZWIZNNlNLlQOK2LMJRJ+5AkuZ1S7CFFzJzk56GnUjQWlYkqCoBWFbonEVYcLLA4dNnB624GQsDBWIgfZJEgxkoChzSFWvn4VpQemDm2VwXQsXJwF1h6c+gxlQ5jgSiEUEt0wdIe7tMES+nEG2aCLiJMOIIWIr9e0DEELAMUrwRuchVAyTKimUwO75Jm6VF3Bv7imOaj+xd7UFKVS/BPJF1b/E4tgTrE49J60O5kceoNqowiuuYKa8ghHXA48U9MT2AQgyRvTThE30bQiaSGa4yLMJNFo+Dq/2cHt4CYlwyFf2S6BHwwrMw/avDbR5C1k7h1YQ4KH3Amf+AcZyEbZPv9CItzQD1l9EbtYOjv74v/d3O9RMPTDrsEwGIWN8q2yk7XNYRs9JrRv3V4ABADSGR6eQ0/NQAAAAAElFTkSuQmCC",
  "xlsx": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU8tqFEEUPVXdPY/ueWZIoiYZiSYKYhJc6EbduHOhgijo3t/wH1z6B0JAhOyMILhxo4kJGk1ASTAxwWF0Mpp5dHc9vFUzYwidaoqmq+8959xzbzGtNcx69PTS26ETmQtS9r4Hy/xv7MW7jV+th5yzVcaYPX/++It9u4NAv+CVR6tBUUTqMJsDcRzjZOZM8W9ZLKx+/XDb4e5/kH5In0lpIYWGUaC0YTZnBCAEKoVR3L36oDo7NbsglZwbqD6iQKOXFMcKUVfBkBAoQhlD5xxMDp/HrSv3q1JgYW3z0x0KXzkCYJaRZljru23aHWTzLiamAyytv0O9UYdf5PArqlppBfMUfu4oALErqZBKcUxMFRCHEp0DgW5Lo4N9NIN1dF0XXsVFOUyPJTzo+WBANDidjp8tgHGG3c0DnJ4uIRf4cOCBaW5KjY8xkZL72xpJ9QcFz5bVqHUJGHZL2YtNmKi06YCyiVFb4s/vEKMTAf1p4edOG6mMi1zR6wEpdUwX+vLDtkCzHoK7ptcM6ayLmGajvtex4PliyoIkFRjmUEASelB2rXQRSfjUCT9PlWpmW21iTGzCAyEkUixPRqXhe2V4zKczbdmybgkpJ0cGOuA6Y2MTCsKoi5HsNK7N3MN+uwYaWbxYfoLLkzdxcew6lrYWaZhm8PHHG3zffp1UwJSHz9vvkU8PodbcQYYYS5lxYkxTkGdVDQdV1Js1qPgYD6JIuIE7gsXVefIhIuM05k7dwMbeMmh87a18ufIMaVYyprrJLgje2Nr+1tzYXANnDnr3zRhHj37Vvy2wpXHtNAd5/wQYAD6WMuT2CwoVAAAAAElFTkSuQmCC",
  "xml": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAilJREFUeNqMks1PE0EYxh+g3W2t1G0sEqyISynUFJsSOShNwCamiYZED3LgIkcuxoN/iCZePZiYGD2aGD+i0F5KMChxlVaakAK2ykcAt+WzdLu7zkxo3WZL4pu8mXfmeeY3885ug67roPFh5nvc62m9hjoR+5LMp7MrkYf370qVtco+VtCUFpbj+jGR+JbWn76OyQ8ePwsZATQb8R/hanZgINgj9IqeuBFCw1Kt9OMBnNWCs24XwkG/QKYUEiGjVAPQof/rq0783pShET3ULQo8xz0iS5FaANmrHQH2DoqY+DSLSz6RzecWlnD9ymU47LYjd4O5BXqDTG4FM3NpTEkpdJ5rw0AowLRMbhUfp58gTOaD/UHmNQPI6YmvKWRX1zESHUJ/oBs2nmPa+Mgw0ZIM3tZyGoJwygzQNB2jNyJIZX7iB0lpPoM70UGmPX8zCU+rG8NDVxHwdiC5mKsPUFUN/gvtLLf39sFzVqaN3YrC6TjBauqhXhNA1TQoqloV7Da+pjZq1FsXUCamF29j6LvYhf3iISamZ3Fv9DZevouhRzzPfOG+3hpA9U9UyioOlTJ7pFeTCQS6RGzIebyf+oz5pSzWtmSW1EO9phvQ00slBRt/8qR3DoWdXbiczUiTzd52D+tdLmyTB14mx1rMAKVcRpEATjrsuElee/HXGmnFRyBOGD30C/nEDjNgs7CDpsYmnHG3YPegBCvHs9oYfm8nG9dJa5X4K8AAQzQX4KSN3wcAAAAASUVORK5CYII=",
  "yml": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdxJREFUeNqMUl1rE0EUPbM7m5Y0Zptu21AwWwhYpfSDFh+kvvRd8N0Hf4I/xWdf/Q158F0QoQ+CVsFKaLSQpt/dpmvztTOzzky6cetOpWcZZvbO3MO5514SxzEU3r57/3GpWllM/tP4sL3TarROXuSo/SWJvX71Uu80Cfhlr/T4UdWFAVfdnmsTUtvdP35OUyQKVnJgXDBTcj9icAsTeLax7j/052qM81UjwW1QJXEhMF0qYnN90fdnvdogYmvJPU0/VBApD4hcDrWRcyikfB17srzgW7b9Rh1vEvxDlI4tVytaBSEEtmWh0xsUMwpwnWjqAlcxogiHd1wiQyCu87iI/+sJtf6+NXsgpd7FWCMB50KvkYMGMbLdZgLlfj+K9K4+FnFQ2x7WntIs50AbmiGwLILt+k+EvzvSNIHzdigdJ/AmXQRhiHv5POSwYmG+cqPVo0HqDxj8uTK2vn1Hfa+JmdIkvtZ/4fOPXU3WPDpFeNWVyUKryCiIGMN4zsH98gym3CIcOTwT+XHdXrdQQHAZotE8kBPpSqPNHtBOr48HUmLOcXRJT9dWNMGYJFby91pHOAvaykSaITg+bwefdhrteDRTMSwyrFCgI88E056Hy+4Ah2cXQZL3R4ABALUe7fqXWFN6AAAAAElFTkSuQmCC",
  "zip": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm9JREFUeNpsk0tv00AUhc+MY6dOmgeFJg1FoVVpUWlFC0s2IFF1jxBbhKj4BSxYdscPYcEmQmIDq0gsERIViy4TpD7VFzF1Ho5je2a4thOqNhlp5Mz4zudzzp0wpRTC8fPrk0/TC6+fDtYicLH97T1Kc2vQDcs+rH3eUAxVznn0fn1DRM8E+iOdv5ct3XmZG6yVlNj6solUbgVTt0q5FGtX6vXqC6VklTE+KAO/OODHSIQPRQpsXC+kkEz2ELA0ystv84tLzyucsbWByisAGf+QAS2CCDRRLMJMmxC+i8C4jdLCm/zM7OOKFGptcO6/BTpJ0yeQB0Y+mfKQuZZG0jQgeRbW8Xdomobs9LN8scc+UPHNy4Dwq8IljotIIQEm59/RoSyM1CKkXKZNBm7kIVgyM6wgAnSgRK9vqQfHPiMFDHqyFVsLR9Cm0o4YzoAASrSjCelQfRPb1Vc4qn0EY5L2W9GEaBLcxQgFHpGbkMIDJ69e+wjJ8VXqRgKid0r7ftQdxkRs9SqA2kgAm14SSIQh9uhuLGPMnKJs/5KquL1x0N0RCsizigoDaLqBdHoMiyvrlBsHVx1wphD4BCewoqxGKKDwAgtOy8JufYuk+5golGGaGZwc1sIGoDz3AOPZSVLaHgVwydoJDM1H4DbQODughB3YpOD44HfoHgnu4e7So0uAi0stHLJ3Aud8B9bpHu6vPoSu9TtDl6tUuoFiIYOgu0+158MKmOxomtyD3Qi/3MTR7i8K0EDG1GHO5DE3X4DvNahZlJOwEkOATvdPc2//hx3mXJ5lFJaF8K8bStd0YGfnOJbMGex21x6c+yfAAOlIPDJzr7cLAAAAAElFTkSuQmCC"
}


================================================
FILE: lib/core/show-dir/index.js
================================================
'use strict';

const styles = require('./styles');
const lastModifiedToString = require('./last-modified-to-string');
const permsToString = require('./perms-to-string');
const sizeToString = require('./size-to-string');
const sortFiles = require('./sort-files');
const fs = require('fs');
const path = require('path');
const he = require('he');
const etag = require('../etag');
const url = require('url');
const status = require('../status-handlers');

const supportedIcons = styles.icons;
const css = styles.css;

module.exports = (opts) => {
  // opts are parsed by opts.js, defaults already applied
  const cache = opts.cache;
  const root = path.resolve(opts.root);
  const baseDir = opts.baseDir;
  const humanReadable = opts.humanReadable;
  const hidePermissions = opts.hidePermissions;
  const handleError = opts.handleError;
  const showDotfiles = opts.showDotfiles;
  const si = opts.si;
  const weakEtags = opts.weakEtags;

  return function middleware(req, res, next) {
    // Figure out the path for the file from the given url
    const parsed = url.parse(req.url);
    const pathname = decodeURIComponent(parsed.pathname);
    const dir = path.normalize(
      path.join(
        root,
        path.relative(
          path.join('/', baseDir),
          pathname
        )
      )
    );

    fs.stat(dir, (statErr, stat) => {
      if (statErr) {
        if (handleError) {
          status[500](res, next, { error: statErr });
        } else {
          next();
        }
        return;
      }

      // files are the listing of dir
      fs.readdir(dir, (readErr, _files) => {
        let files = _files;

        if (readErr) {
          if (handleError) {
            status[500](res, next, { error: readErr });
          } else {
            next();
          }
          return;
        }

        // Optionally exclude dotfiles from directory listing.
        if (!showDotfiles) {
          files = files.filter(filename => filename.slice(0, 1) !== '.');
        }

        res.setHeader('content-type', 'text/html');
        res.setHeader('etag', etag(stat, weakEtags));
        res.setHeader('last-modified', (new Date(stat.mtime)).toUTCString());
        res.setHeader('cache-control', cache);

        // A step before render() is called to gives items additional
        // information so that render() can deliver the best user experience
        // possible.
        function prerender(dirs, renderFiles, errs) {
          const filenamesThatExist = new Set();

          // Putting filenames in a set first keeps us in O(n) time complexity
          for (let i=0; i < renderFiles.length; i++) {
            const [name, stat] = renderFiles[i];
            filenamesThatExist.add(name);
            const renderOptions = {};
            renderFiles[i] = [name, stat, renderOptions];
          }

          // Set render options for compressed files
          for (const [name, _stat, renderOptions] of renderFiles) {
            if (
              opts.brotli &&
              ! opts.forceContentEncoding &&
              name.endsWith('.br')
            ) {
              const uncompressedName = name.slice(0, -'.br'.length);
              if (filenamesThatExist.has(uncompressedName)) {
                continue;
              }
              renderOptions.uncompressedName = uncompressedName;
            }
          }
          for (const [name, _stat, renderOptions] of renderFiles) {
            if (
              opts.gzip &&
              ! opts.forceContentEncoding &&
              name.endsWith('.gz')
            ) {
              const uncompressedName = name.slice(0, -'.gz'.length);
              if (filenamesThatExist.has(uncompressedName)) {
                continue;
              }
              renderOptions.uncompressedName = uncompressedName;
            }
          }
          render(dirs, renderFiles, errs);
        }

        function render(dirs, renderFiles, errs) {
          // each entry in the array is a [name, stat] tuple

          let html = `${[
            '<!doctype html>',
            '<html>',
            '  <head>',
            '    <meta charset="utf-8">',
            '    <meta name="viewport" content="width=device-width">',
            `    <title>Index of ${he.encode(pathname)}</title>`,
            `    <style type="text/css">${css}</style>`,
            '  </head>',
            '  <body>',
            `<h1>Index of ${he.encode(pathname)}</h1>`,
          ].join('\n')}\n`;

          html += '<table>';

          const failed = false;
          const writeRow = (file) => {
            // render a row given a [name, stat, renderOptions] tuple
            const isDir = file[1].isDirectory && file[1].isDirectory();
            let href = `./${encodeURIComponent(file[0])}`;

            // append trailing slash and query for dir entry
            if (isDir) {
              href += `/${he.encode((parsed.search) ? parsed.search : '')}`;
            }

            // Handle compressed files with uncompressed names
            let displayNameHTML;
            let fileSize = sizeToString(file[1], humanReadable, si);

            if (file[2] && file[2].uncompressedName) {
              // This is a compressed file, show both names with separate links
              const uncompressedName = he.encode(file[2].uncompressedName);
              const compressedName = he.encode(file[0]);
              const uncompressedHref = `./${encodeURIComponent(file[2].uncompressedName)}`;
              const asterisk = `<span title="served from compressed file">*</span>`;
              displayNameHTML = `<a href="${uncompressedHref}">${uncompressedName}</a>` +
                `${asterisk} (<a href="${href}">${compressedName}</a>)`;
              fileSize += '*';
            } else {
              // Regular file or directory
              displayNameHTML = `<a href="${href}">${he.encode(file[0]) + ((isDir) ? '/' : '')}</a>`;
            }

            const ext = file[0].split('.').pop();
            const classForNonDir = supportedIcons[ext] ? ext : '_page';
            const iconClass = `icon-${isDir ? '_blank' : classForNonDir}`;

            // TODO: use stylessheets?
            html += `${'<tr>' +
              '<td><i class="icon '}${iconClass}"></i></td>`;
            if (!hidePermissions) {
              html += `<td class="perms"><code>(${permsToString(file[1])})</code></td>`;
            }
            html +=
              `<td class="last-modified">${lastModifiedToString(file[1])}</td>` +
              `<td class="file-size"><code>${fileSize}</code></td>` +
              `<td class="display-name">${displayNameHTML}</td>` +
              '</tr>\n';
          };

          dirs.sort((a, b) => a[0].toString().localeCompare(b[0].toString())).forEach(writeRow);
          renderFiles.sort((a, b) => a.toString().localeCompare(b.toString())).forEach(writeRow);
          errs.sort((a, b) => a[0].toString().localeCompare(b[0].toString())).forEach(writeRow);

          html += '</table>\n';
          html += `<br><address>Node.js ${
            process.version
            }/ <a href="https://github.com/http-party/http-server">http-server</a> ` +
            `server running @ ${
            he.encode(req.headers.host || '')}</address>\n` +
            '</body></html>'
          ;

          if (!failed) {
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(html);
          }
        }

        sortFiles(dir, files, (errs, dirs, sortedFiles) => {
          // It's possible to get stat errors for all sorts of reasons here.
          // Unfortunately, our two choices are to either bail completely,
          // or just truck along as though everything's cool. In this case,
          // I decided to just tack them on as "??!?" items along with dirs
          // and files.
          //
          // Whatever.

          // if it makes sense to, add a .. link
          if (path.resolve(dir, '..').slice(0, root.length) === root) {
            fs.stat(path.join(dir, '..'), (err, s) => {
              if (err) {
                if (handleError) {
                  status[500](res, next, { error: err });
                } else {
                  next();
                }
                return;
              }
              dirs.unshift(['..', s]);
              prerender(dirs, sortedFiles, errs);
            });
          } else {
            prerender(dirs, sortedFiles, errs);
          }
        });
      });
    });
  };
};


================================================
FILE: lib/core/show-dir/last-modified-to-string.js
================================================
'use strict';

module.exports = function lastModifiedToString(stat) {
  if (!stat.mtime) {
    // stat error (eg, broken symlink)
    return 'Unknown Date';
  }
  const t = new Date(stat.mtime);
  return (('0' + (t.getDate())).slice(-2) + '-' +
          t.toLocaleString('default', { month: 'short' }) + '-' +
          t.getFullYear() + ' ' +
          ('0' + t.getHours()).slice(-2) + ':' +
          ('0' + t.getMinutes()).slice(-2));
};


================================================
FILE: lib/core/show-dir/perms-to-string.js
================================================
'use strict';

module.exports = function permsToString(stat) {
  if (!stat.isDirectory || !stat.mode) {
    return '????!!!???';
  }

  const dir = stat.isDirectory() ? 'd' : '-';
  const mode = stat.mode.toString(8);

  return dir + mode.slice(-3).split('').map(n => [
    '---',
    '--x',
    '-w-',
    '-wx',
    'r--',
    'r-x',
    'rw-',
    'rwx',
  ][parseInt(n, 10)]).join('');
};


================================================
FILE: lib/core/show-dir/size-to-string.js
================================================
'use strict';

// given a file's stat, return the size of it in string
// humanReadable: (boolean) whether to result is human readable
// si: (boolean) whether to use si (1k = 1000), otherwise 1k = 1024
// adopted from http://stackoverflow.com/a/14919494/665507
module.exports = function sizeToString(stat, humanReadable, si) {
  if (stat.isDirectory && stat.isDirectory()) {
    return '';
  }

  let bytes = stat.size;
  const threshold = si ? 1000 : 1024;

  if (!humanReadable || bytes < threshold) {
    return `${bytes}B`;
  }

  const units = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
  let u = -1;
  do {
    bytes /= threshold;
    u += 1;
  } while (bytes >= threshold);

  let b = bytes.toFixed(1);
  if (isNaN(b)) b = '??';

  return b + units[u];
};


================================================
FILE: lib/core/show-dir/sort-files.js
================================================
'use strict';

const fs = require('fs');
const path = require('path');

module.exports = function sortByIsDirectory(dir, paths, cb) {
  // take the listing file names in `dir`
  // returns directory and file array, each entry is
  // of the array a [name, stat] tuple
  let pending = paths.length;
  const errs = [];
  const dirs = [];
  const files = [];

  if (!pending) {
    cb(errs, dirs, files);
    return;
  }

  paths.forEach((file) => {
    fs.stat(path.join(dir, file), (err, s) => {
      if (err) {
        errs.push([file, err]);
      } else if (s.isDirectory()) {
        dirs.push([file, s]);
      } else {
        files.push([file, s]);
      }

      pending -= 1;
      if (pending === 0) {
        cb(errs, dirs, files);
      }
    });
  });
};


================================================
FILE: lib/core/show-dir/styles.js
================================================
'use strict';

const icons = require('./icons.json');

const IMG_SIZE = 16;

let css = `i.icon { display: block; height: ${IMG_SIZE}px; width: ${IMG_SIZE}px; background: no-repeat center; }\n`;
css += 'table tr { white-space: nowrap; }\n';
css += 'td.perms {}\n';
css += 'td.file-size { text-align: right; padding-left: 1em; }\n';
css += 'td.display-name { padding-left: 1em; }\n';
css += `
@media (prefers-color-scheme: dark) {
  body {
    background-color: #303030;
    color: #efefef;
  }
  a {
    color: #ffff11;
  }
}
`;

Object.keys(icons).forEach((key) => {
  css += `i.icon-${key} {\n`;
  css += `  background-image: url("data:image/png;base64,${icons[key]}");\n`;
  css += '}\n\n';
});

exports.icons = icons;
exports.css = css;


================================================
FILE: lib/core/status-handlers.js
================================================
'use strict';

const he = require('he');

// not modified
exports['304'] = (res) => {
  res.statusCode = 304;
  res.end();
};

// access denied
exports['403'] = (res, next) => {
  res.statusCode = 403;
  if (typeof next === 'function') {
    next();
  } else if (res.writable) {
    res.setHeader('content-type', 'text/plain');
    res.end('ACCESS DENIED');
  }
};

// disallowed method
exports['405'] = (res, next, opts) => {
  res.statusCode = 405;
  if (typeof next === 'function') {
    next();
  } else {
    res.setHeader('allow', (opts && opts.allow) || 'GET, HEAD');
    res.end();
  }
};

// not found
exports['404'] = (res, next) => {
  res.statusCode = 404;
  if (typeof next === 'function') {
    next();
  } else if (res.writable) {
    res.setHeader('content-type', 'text/plain');
    res.end('File not found. :(');
  }
};

exports['416'] = (res, next, opts) => {
  res.statusCode = 416;
  res.setHeader('content-range', 'bytes */' + opts.size)
  if (typeof next === 'function') {
    next();
  } else if (res.writable) {
    res.setHeader('content-type', 'text/plain');
    res.end('Requested range not satisfiable');
  }
};

// flagrant error
exports['500'] = (res, next, opts) => {
  res.statusCode = 500;
  try {
    res.setHeader('content-type', 'text/html');
  } catch (e) {
    // errors may have triggered headers being sent already, make sure we don't hide the underlying error
  }
  const error = String(opts.error.stack || opts.error || 'No specified error');
  const html = `${[
    '<!doctype html>',
    '<html>',
    '  <head>',
    '    <meta charset="utf-8">',
    '    <title>500 Internal Server Error</title>',
    '  </head>',
    '  <body>',
    '    <p>',
    `      ${he.encode(error)}`,
    '    </p>',
    '  </body>',
    '</html>',
  ].join('\n')}\n`;
  res.end(html);
};

// bad request
exports['400'] = (res, next, opts) => {
  res.statusCode = 400;
  res.setHeader('content-type', 'text/html');
  const error = opts && opts.error ? String(opts.error) : 'Malformed request.';
  const html = `${[
    '<!doctype html>',
    '<html>',
    '  <head>',
    '    <meta charset="utf-8">',
    '    <title>400 Bad Request</title>',
    '  </head>',
    '  <body>',
    '    <p>',
    `      ${he.encode(error)}`,
    '    </p>',
    '  </body>',
    '</html>',
  ].join('\n')}\n`;
  res.end(html);
};


================================================
FILE: lib/http-server.js
================================================
'use strict';

var fs = require('fs'),
  union = require('union'),
  httpServerCore = require('./core'),
  auth = require('basic-auth'),
  httpProxy = require('http-proxy'),
  corser = require('corser'),
  secureCompare = require('secure-compare');
var { minimatch } = require('minimatch');

//
// Remark: backwards compatibility for previous
// case convention of HTTP
//
exports.HttpServer = exports.HTTPServer = HttpServer;

/**
 * Returns a new instance of HttpServer with the
 * specified `options`.
 */
exports.createServer = function (options) {
  return new HttpServer(options);
};

/**
 * Constructor function for the HttpServer object
 * which is responsible for serving static files along
 * with other HTTP-related features.
 */
function HttpServer(options) {
  options = options || {};
  var proxyAll = options.proxyAll === true || options.proxyAll === 'true';

  if (proxyAll && typeof options.proxy !== 'string') {
    throw new Error('proxyAll option requires "proxy" to be configured');
  }

  if (options.root) {
    this.root = options.root;
  } else {
    try {
      // eslint-disable-next-line no-sync
      fs.lstatSync('./public');
      this.root = './public';
    } catch (err) {
      this.root = './';
    }
  }

  // CRLF injection prevention
  for ( const [key, value] of Object.entries(options.headers || {}) ) {
    if (typeof key !== 'string' || typeof value !== 'string') {
      throw new Error('Header is not a string or contains CRLF');
    }
    if (key.includes('\r') || key.includes('\n') || value.includes('\r') || value.includes('\n')) {
      throw new Error('Header is not a string or contains CRLF');
    }
  }

  this.headers = options.headers || {};
  this.headers['Accept-Ranges'] = 'bytes';

  this.cache = (
    // eslint-disable-next-line no-nested-ternary
    options.cache === undefined ? 3600 :
    // -1 is a special case to turn off caching.
    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Preventing_caching
      options.cache === -1 ? 'no-cache, no-store, must-revalidate' :
        options.cache // in seconds.
  );
  this.showDir = options.showDir !== 'false';
  this.dirOverrides404 = options.dirOverrides404;
  this.autoIndex = options.autoIndex !== 'false';
  this.showDotfiles = options.showDotfiles;
  this.hidePermissions = options.hidePermissions;
  this.gzip = options.gzip === true;
  this.brotli = options.brotli === true;
  this.forceContentEncoding = options.forceContentEncoding === true;
  if (options.ext) {
    this.ext = options.ext === true
      ? 'html'
      : options.ext;
  }
  this.contentType = options.contentType ||
    (this.ext === 'html' ? 'text/html' : 'application/octet-stream');

  var before = options.before ? options.before.slice() : [];

  if (options.logFn) {
    before.push(function (req, res) {
      options.logFn(req, res);
      res.emit('next');
    });
  }

  if (options.username || options.password) {
    if (!options.username || !options.password) {
      throw new Error('Basic authentication requires both username and password to be specified');
    }

    before.push(function (req, res) {
      var credentials = auth(req);

      // We perform these outside the if to avoid short-circuiting and giving
      // an attacker knowledge of whether the username is correct via a timing
      // attack.
      if (credentials) {
        // if credentials is defined, name and pass are guaranteed to be string
        // type
        var usernameEqual = secureCompare(options.username.toString(), credentials.name);
        var passwordEqual = secureCompare(options.password.toString(), credentials.pass);
        if (usernameEqual && passwordEqual) {
          return res.emit('next');
        }
      }

      res.statusCode = 401;
      res.setHeader('WWW-Authenticate', 'Basic realm=""');
      res.end('Access denied');
    });
  }

  if (options.allowedHosts) {
    before.push(function (req, res) {
      let host = req.headers && req.headers.host;
      if (host) {
        // don't include port number in host check
        host = host.split(':')[0];
      }

      if (!host || !options.allowedHosts.includes(host)) {
        res.statusCode = 403;
        res.end('Access denied');
        return;
      }

      return res.emit('next');
    });
  }

  if (options.coop) {
    this.headers['Cross-Origin-Opener-Policy'] = options.coopHeader || 'same-origin';
    this.headers['Cross-Origin-Embedder-Policy'] = 'require-corp';
  }

  // CORS configuration:
  // --cors enables CORS by setting Access-Control-Allow-Origin to '*'
  // --cors=header1,header2 also adds custom headers to Access-Control-Allow-Headers
  if (options.cors) {
    this.headers['Access-Control-Allow-Origin'] = '*';
    this.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Range';
    if (options.corsHeaders) {
      options.corsHeaders.split(/\s*,\s*/)
        .forEach(function (h) { this.headers['Access-Control-Allow-Headers'] += ', ' + h; }, this);
    }
    before.push(corser.create(options.corsHeaders ? {
      requestHeaders: this.headers['Access-Control-Allow-Headers'].split(/\s*,\s*/)
    } : null));
  }

  if (options.privateNetworkAccess) {
    this.headers['Access-Control-Allow-Private-Network'] = true;
  }

  if (options.robots) {
    before.push(function (req, res) {
      if (req.url === '/robots.txt') {
        res.setHeader('Content-Type', 'text/plain');
        var robots = options.robots === true
          ? 'User-agent: *\nDisallow: /'
          : options.robots.replace(/\\n/, '\n');

        return res.end(robots);
      }

      res.emit('next');
    });
  }

  if (typeof options.proxyConfig === 'object') {
    var proxy = httpProxy.createProxyServer();
    before.push(function (req, res, next) {
      for (var key of Object.keys(options.proxyConfig)) {
        if (!minimatch(req.url, key)) continue;
        req.proxy ??= {};
        var matchConfig = options.proxyConfig[key];
        
        if (matchConfig.pathRewrite) {
          Object.entries(matchConfig.pathRewrite).forEach(rewrite => {
            req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]);
          });
        }
        
        var configEntries = Object.entries(matchConfig).filter(entry => entry[0] !== "pathRewrite");
        configEntries.forEach(entry => req.proxy[entry[0]] = entry[1]);
        break;
      }

      if (req.proxy) {
        if (options.logFn) {
          options.logFn(req, res);
        }
        proxy.web(req, res, req.proxy, function (err, req, res) {
          if (options.logFn) {
            options.logFn(req, res, {
              message: err.message,
              status: res.statusCode });
          }
          res.emit('next');
        });
      } else {
        next();
      }
    });
  }

  if (!proxyAll) {
    before.push(httpServerCore({
      root: this.root,
      baseDir: options.baseDir,
      cache: this.cache,
      showDir: this.showDir,
      showDotfiles: this.showDotfiles,
      hidePermissions: this.hidePermissions,
      autoIndex: this.autoIndex,
      defaultExt: this.ext,
      dirOverrides404: this.dirOverrides404,
      gzip: this.gzip,
      brotli: this.brotli,
      forceContentEncoding: this.forceContentEncoding,
      contentType: this.contentType,
      mimetypes: options.mimetypes,
      handleError: typeof options.proxy !== 'string'
    }));
  }

  if (typeof options.proxy === 'string') {
    var proxyOptions = options.proxyOptions || {};

    if (proxyOptions.changeOrigin == null) {
        proxyOptions.changeOrigin = true;
    }

    var proxy = httpProxy.createProxyServer({
      ...proxyOptions,
      target: options.proxy,
    });
    before.push(function (req, res) {
      proxy.web(req, res, {}, function (err, req, res) {
        if (options.logFn) {
          options.logFn(req, res, {
            message: err.message,
            status: res.statusCode });
        }
        res.emit('next');
      });
    });
  }

  var serverOptions = {
    before: before,
    headers: this.headers,
    onError: function (err, req, res) {
      if (options.logFn) {
        options.logFn(req, res, err);
      }

      res.end();
    }
  };

  if (options.https) {
    serverOptions.https = options.https;
  }

  this.server = serverOptions.https && serverOptions.https.passphrase
    // if passphrase is set, shim must be used as union does not support
    ? require('./shims/https-server-shim')(serverOptions)
    : union.createServer(serverOptions);

  if (isNaN(options.timeout) || isNaN(parseFloat(options.timeout))) {
    this.server.setTimeout(120);
  } else {
    // set custom timeout only if options.timeout is a numeric string
    this.server.setTimeout(Math.max(0, Number(options.timeout)));
  }

  if (typeof options.proxy === 'string' && options.websocket) {
    this.server.on('upgrade', function (request, socket, head) {
      proxy.ws(request, socket, head, {
        target: options.proxy,
        changeOrigin: true
      }, function (err, req, res) {
        if (options.logFn) {
          options.logFn(req, res, {
            message: err?.message,
            status: res?.statusCode });
        }
        res.emit('next');
      });
    });
  }
}

HttpServer.prototype.listen = function () {
  this.server.listen.apply(this.server, arguments);
};

HttpServer.prototype.close = function () {
  return this.server.close();
};

HttpServer.prototype.address = function () {
  return this.server.address();
};


================================================
FILE: lib/shims/https-server-shim.js
================================================
/* eslint-disable no-process-env */
/* eslint-disable no-sync */
var https = require('https');
var fs = require('fs');
var core = require('union/lib/core');
var RoutingStream = require('union/lib/routing-stream');

module.exports = function (options) {
  var isArray = Array.isArray(options.after);
  var credentials;

  if (!options) {
    throw new Error('options is required to create a server');
  }

  function requestHandler(req, res) {
    var routingStream = new RoutingStream({
      before: options.before,
      buffer: options.buffer,
      after:
        isArray &&
        options.after.map(function (After) {
          return new After();
        }),
      request: req,
      response: res,
      limit: options.limit,
      headers: options.headers
    });

    routingStream.on('error', function (err) {
      var fn = options.onError || core.errorHandler;
      fn(err, routingStream, routingStream.target, function () {
        routingStream.target.emit('next');
      });
    });

    req.pipe(routingStream);
  }

  var serverOptions;

  serverOptions = options.https;
  if (!serverOptions.key || !serverOptions.cert) {
    throw new Error(
      'Both options key and cert are required.'
    );
  }

  credentials = {
    key: fs.readFileSync(serverOptions.key),
    cert: fs.readFileSync(serverOptions.cert),
    passphrase: process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE
  };

  if (serverOptions.ca) {
    serverOptions.ca = !Array.isArray(serverOptions.ca)
      ? [serverOptions.ca]
      : serverOptions.ca;

    credentials.ca = serverOptions.ca.map(function (ca) {
      return fs.readFileSync(ca);
    });
  }

  return https.createServer(credentials, requestHandler);
};


================================================
FILE: package.json
================================================
{
  "name": "http-server",
  "version": "14.1.2",
  "description": "A simple zero-configuration command-line http server",
  "main": "./lib/http-server",
  "repository": {
    "type": "git",
    "url": "git://github.com/http-party/http-server.git"
  },
  "keywords": [
    "cli",
    "command",
    "static",
    "http",
    "https",
    "http-server",
    "https-server",
    "server"
  ],
  "scripts": {
    "start": "node ./bin/http-server",
    "test": "tap --reporter=terse --allow-incomplete-coverage test/*.test.js",
    "test-watch": "tap --reporter=terse --allow-incomplete-coverage --watch test/*.test.js"
  },
  "files": [
    "lib",
    "bin",
    "doc"
  ],
  "man": "./doc/http-server.1",
  "engines": {
    "node": ">=16.20.2"
  },
  "contributors": [
    {
      "name": "Charlie Robbins",
      "email": "charlie.robbins@gmail.com"
    },
    {
      "name": "Marak Squires",
      "email": "marak.squires@gmail.com"
    },
    {
      "name": "Charlie McConnell",
      "email": "charlie@charlieistheman.com"
    },
    {
      "name": "Joshua Holbrook",
      "email": "josh.holbrook@gmail.com"
    },
    {
      "name": "Maciej Małecki",
      "email": "maciej.malecki@notimplemented.org"
    },
    {
      "name": "Matthew Bergman",
      "email": "mzbphoto@gmail.com"
    },
    {
      "name": "brad dunbar",
      "email": "dunbarb2@gmail.com"
    },
    {
      "name": "Dominic Tarr"
    },
    {
      "name": "Travis Person",
      "email": "travis.person@gmail.com"
    },
    {
      "name": "Jinkwon Lee",
      "email": "master@bdyne.net"
    },
    {
      "name": "BigBlueHat",
      "email": "byoung@bigbluehat.com"
    },
    {
      "name": "Daniel Dalton",
      "email": "daltond2@hawkmail.newpaltz.edu"
    },
    {
      "name": "Jade Michael Thornton",
      "email": "jademichael@jmthornton.net"
    },
    {
      "name": "Jorens Merenjanu",
      "email": "jorensmerenjanu@gmail.com"
    }
  ],
  "dependencies": {
    "basic-auth": "^2.0.1",
    "chalk": "^4.1.2",
    "corser": "^2.0.1",
    "he": "^1.2.0",
    "html-encoding-sniffer": "^3.0.0",
    "http-proxy": "^1.18.1",
    "mime": "^1.6.0",
    "minimatch": "^10.1.1",
    "minimist": "^1.2.6",
    "opener": "^1.5.1",
    "portfinder": "^1.0.28",
    "secure-compare": "3.0.1",
    "union": "~0.5.0",
    "url-join": "^4.0.1"
  },
  "devDependencies": {
    "eol": "^0.9.1",
    "eslint": "^4.19.1",
    "eslint-config-populist": "^4.2.0",
    "express": "^4.17.1",
    "request": "^2.88.2",
    "tap": "^21.0.1"
  },
  "bugs": {
    "url": "https://github.com/http-party/http-server/issues"
  },
  "license": "MIT",
  "preferGlobal": true,
  "bin": {
    "http-server": "./bin/http-server"
  }
}


================================================
FILE: public/404.html
================================================
<html>
  <head>
    <title>404</title>
  </head>
  <body>
    <h1>404</h1>

    <p>Were you just making up filenames or what?</p>
  </body>
</html>


================================================
FILE: public/index.html
================================================
<html>
  <head>
    <title>node.js http server</title>
  </head>
  <body>

    <h1>Serving up static files like they were turtles strapped to rockets.</h1>

    <img src="./img/turtle.png"/>
  </body>
</html>



================================================
FILE: test/304.test.js
================================================
'use strict';

const test = require('tap').test;
const ecstatic = require('../lib/core');
const http = require('http');
const request = require('request');
const path = require('path');
const portfinder = require('portfinder');

const root = `${__dirname}/public`;
const baseDir = 'base';

test('304_not_modified_strong', (t) => {
  const file = 'a.txt';

  const server = http.createServer(
    ecstatic({
      root,
      gzip: true,
      baseDir,
      autoIndex: true,
      showDir: true,
      weakEtags: false,
      weakCompare: false,
    })
  );

  server.listen(0, () => {
    const port = server.address().port;
    const uri = `http://localhost:${port}${path.join('/', baseDir, file)}`;

    request.get({
      uri,
      followRedirect: false,
    }, (err, res) => {
      if (err) {
        t.fail(err);
      }

      t.equal(res.statusCode, 200, 'first request should be a 200');

      request.get({
        uri,
        followRedirect: false,
        headers: { 'if-modified-since': res.headers['last-modified'] },
      }, (err2, res2) => {
        if (err2) {
          t.fail(err2);
        }

        t.equal(res2.statusCode, 304, 'second request should be a 304');
        t.equal(res2.headers.etag.indexOf('"'), 0, 'should return a strong etag');
        server.close();
        setTimeout(() => { t.end(); }, 0);
      });
    });
  });
});

test('304_not_modified_weak', (t) => {
  const file = 'b.txt';

  const server = http.createServer(
    ecstatic({
      root,
      gzip: true,
      baseDir,
      autoIndex: true,
      showDir: true,
      weakEtags: true,
      weakCompare: false,
    })
  );

  server.listen(0, () => {
    const port = server.address().port;
    const uri = `http://localhost:${port}${path.join('/', baseDir, file)}`;
    const now = (new Date()).toString();

    request.get({
      uri,
      followRedirect: false,
    }, (err, res) => {
      if (err) {
        t.fail(err);
      }

      t.equal(res.statusCode, 200, 'first request should be a 200');

      request.get({
        uri,
        followRedirect: false,
        headers: { 'if-modified-since': now },
      }, (err2, res2) => {
        if (err2) t.fail(err2);

        t.equal(res2.statusCode, 304, 'second request should be a 304');
        t.equal(res2.headers.etag.indexOf('W/'), 0, 'should return a weak etag');
        server.close();
        setTimeout(() => { t.end(); }, 0);
      });
    });
  });
});

test('304_not_modified_strong_compare', (t) => {
  const file = 'b.txt';

  const server = http.createServer(
    ecstatic({
      root,
      gzip: true,
      baseDir,
      autoIndex: true,
      showDir: true,
      weakEtags: false,
      weakCompare: false,
    })
  );

  server.listen(0, () => {
    const port = server.address().port;
    const uri = `http://localhost:${port}${path.join('/', baseDir, file)}`;
    const now = (new Date()).toString();
    let etag = null;

    request.get({
      uri,
      followRedirect: false,
    }, (err, res) => {
      if (err) {
        t.fail(err);
      }

      t.equal(res.statusCode, 200, 'first request should be a 200');

      etag = res.headers.etag;

      request.get({
        uri,
        followRedirect: false,
        headers: { 'if-modified-since': now, 'if-none-match': etag },
      }, (err2, res2) => {
        if (err2) {
          t.fail(err2);
        }

        t.equal(res2.statusCode, 304, 'second request with a strong etag should be 304');

        request.get({
          uri,
          followRedirect: false,
          headers: { 'if-modified-since': now, 'if-none-match': `W/${etag}` },
        }, (err3, res3) => {
          if (err3) {
            t.fail(err3);
          }

          // Note that if both if-modified-since and if-none-match are
          // provided, the server MUST NOT return a response status of 304
          // unless doing so is consistent with all of the conditional
          // header fields in the request
          // https://www.ietf.org/rfc/rfc2616.txt
          t.equal(res3.statusCode, 200, 'third request with a weak etag should be 200');
          server.close();
          setTimeout(() => { t.end(); }, 0);
        });
      });
    });
  });
});


test('304_not_modified_weak_compare', (t) => {
  const file = 'c.js';

  const server = http.createServer(
    ecstatic({
      root,
      gzip: true,
      baseDir,
      autoIndex: true,
      showDir: true,
      weakEtags: false,
    })
  );

  server.listen(0, () => {
    const port = server.address().port;
    const uri = `http://localhost:${port}${path.join('/', baseDir, file)}`;
    const now = (new Date()).toString();
    let etag = null;

    request.get({
      uri,
      followRedirect: false,
    }, (err, res) => {
      if (err) {
        t.fail(err);
      }

      t.equal(res.statusCode, 200, 'first request should be a 200');

      etag = res.headers.etag;

      request.get({
        uri,
        followRedirect: false,
        headers: { 'if-modified-since': now, 'if-none-match': etag },
      }, (err2, res2) => {
        if (err2) {
          t.fail(err2);
        }

        t.equal(res2.statusCode, 304, 'second request with a strong etag should be 304');

        request.get({
          uri,
          followRedirect: false,
          headers: { 'if-modified-since': now, 'if-none-match': `W/${etag}` },
        }, (err3, res3) => {
          if (err3) {
            t.fail(err3);
          }

          t.equal(res3.statusCode, 304, 'third request with a weak etag should be 304');
          server.close();
          setTimeout(() => { t.end(); }, 0);
        });
      });
    });
  });
});


================================================
FILE: test/accept-encoding.test.js
================================================
'use strict';

const test = require('tap').test;
const ecstatic = require('../lib/core');
const http = require('http');
const request = require('request');

const root = `${__dirname}/public`;

test('properly handles whitespace in accept-encoding', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    autoIndex: true,
    gzip: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/gzip`,
      headers: {
        'accept-encoding': ' gzip, deflate'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], 'gzip');
    });
  });
  t.once('end', () => {
    server.close();
  });
});

test('properly handles single accept-encoding entry', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    autoIndex: true,
    gzip: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/gzip`,
      headers: {
        'accept-encoding': 'gzip'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], 'gzip');
    });
  });
  t.once('end', () => {
    server.close();
  });
});


================================================
FILE: test/allowed-hosts.test.js
================================================
const test = require('tap').test
const path = require('path')
const httpServer = require('../lib/http-server')
const request = require('request');

// Prevent errors from being swallowed
process.on('uncaughtException', console.error)

test('allowed hosts functionality', (t) => {
    t.plan(4);
    new Promise((resolve) => {
        const server = httpServer.createServer({
            root: path.join(__dirname, 'fixtures'),
            allowedHosts: ['localhost'],
        })

        server.listen(0, async () => {
            console.log('server listening on port', server.address().port)
            const port = server.address().port
            const url = `http://localhost:${port}`

            try {
                await new Promise((resolve, reject) => {
                    request.get({ 
                        url,
                        headers: {
                            Host: 'example.com'
                        }
                    }, (err, res) => {
                        console.log('response', err)
                        t.error(err);
                        t.equal(res.statusCode, 403);
                        resolve();
                    })
                })

                await new Promise((resolve, reject) => {
                    request.get({ 
                        url,
                        headers: {
                            Host: 'localhost'
                        }
                    }, (err, res) => {
                        console.log('response', err)
                        t.error(err);
                        t.equal(res.statusCode, 200);
                        resolve();
                    })
                })
            } catch (err) {
                t.fail(`allowed hosts test failed: ${err.message}`)
            } finally {
                server.close()
            }
        })
    })
})

================================================
FILE: test/cache.test.js
================================================
'use strict';

const test = require('tap').test;
const http = require('http');
const request = require('request');
const ecstatic = require('../lib/core');

test('custom cache option number', (t) => {
  let server = null;
  try {
    server = http.createServer(ecstatic({
      root: `${__dirname}/public/`,
      cache: 3600,
    }));
  } catch (e) {
    t.fail(e.message);
    t.end();
  }

  t.plan(3);

  server.listen(0, () => {
    const port = server.address().port;
    request.get(`http://localhost:${port}/a.txt`, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200, 'a.txt should be found');
      t.equal(res.headers['cache-control'], 'max-age=3600');
      server.close(() => { t.end(); });
    });
  });
});

test('custom cache option string', (t) => {
  let server = null;
  try {
    server = http.createServer(ecstatic({
      root: `${__dirname}/public/`,
      cache: 'max-whatever=3600',
    }));
  } catch (e) {
    t.fail(e.message);
    t.end();
  }

  t.plan(3);

  server.listen(0, () => {
    const port = server.address().port;
    request.get(`http://localhost:${port}/a.txt`, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200, 'a.txt should be found');
      t.equal(res.headers['cache-control'], 'max-whatever=3600');
      server.close(() => { t.end(); });
    });
  });
});

test('custom cache option function returning a number', (t) => {
  let i = 0;
  let server = null;
  try {
    server = http.createServer(ecstatic({
      root: `${__dirname}/public/`,
      cache() {
        i += 1;
        return i;
      },
    }));
  } catch (e) {
    t.fail(e.message);
    t.end();
  }

  t.plan(6);

  server.listen(0, () => {
    const port = server.address().port;
    request.get(`http://localhost:${port}/a.txt`, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200, 'a.txt should be found');
      t.equal(res.headers['cache-control'], 'max-age=1');

      request.get(`http://localhost:${port}/a.txt`, (err2, res2) => {
        t.error(err2);
        t.equal(res2.statusCode, 200, 'a.txt should be found');
        t.equal(res2.headers['cache-control'], 'max-age=2');
        server.close(() => { t.end(); });
      });
    });
  });
});

test('custom cache option function returning a string', (t) => {
  let i = 0;
  let server = null;
  try {
    server = http.createServer(ecstatic({
      root: `${__dirname}/public/`,
      cache() {
        i += 1;
        return `max-meh=${i}`;
      },
    }));
  } catch (e) {
    t.fail(e.message);
    t.end();
  }

  t.plan(6);

  server.listen(0, () => {
    const port = server.address().port;
    request.get(`http://localhost:${port}/a.txt`, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200, 'a.txt should be found');
      t.equal(res.headers['cache-control'], 'max-meh=1');

      request.get(`http://localhost:${port}/a.txt`, (err2, res2) => {
        t.error(err2);
        t.equal(res2.statusCode, 200, 'a.txt should be found');
        t.equal(res2.headers['cache-control'], 'max-meh=2');
        server.close(() => { t.end(); });
      });
    });
  });
});


================================================
FILE: test/check-headers.js
================================================
const request = require('request');

module.exports = (t, server, path, check) => {
  server.listen(() => {
    const port = server.address().port;
    const uri = `http://localhost:${port}/${path}`;

    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      check(t, res.headers);
    });
  });
  t.once('end', () => {
    server.close();
  });
}


================================================
FILE: test/cli.test.js
================================================
'use strict';

/* this test suit is incomplete  2015-12-18 */

const test = require('tap').test;
const request = require('request');
const spawn = require('child_process').spawn;
const path = require('path');
const portfinder = require('portfinder');
const httpServer = require('../lib/http-server');

const node = process.execPath;

function startServer(args) {
  return spawn(node, [require.resolve('../bin/http-server')].concat(args));
}

function checkServerIsRunning(url, msg, t, _cb) {
  if (!msg.toString().match(/Starting up/)) {
    return;
  }
  t.pass('http-server started');
  const cb = _cb || (() => {});

  request(url, (err, res) => {
    if (!err && res.statusCode !== 500) {
      t.pass('a successful request from the server was made');
      cb(null, res);
    } else {
      t.fail(`the server could not be reached @ ${url}`);
      cb(err);
    }
  });
}

function tearDown(ps, t) {
  t.teardown(() => {
    ps.kill('SIGTERM');
  });
}

const getPort = () => new Promise((resolve, reject) => {
  portfinder.getPort((err, port) => {
    if (err) reject(err);
    resolve(port);
  });
});

const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, '');

test('setting port via cli - custom port', (t) => {
  t.plan(2);

  getPort().then((port) => {
    const options = ['.', '--port', port];
    const server = startServer(options);

    tearDown(server, t);

    server.stdout.on('data', (msg) => {
      checkServerIsRunning(`http://localhost:${port}`, msg, t);
    });
  });
});

test('setting mimeTypes via cli - .types file', (t) => {
  t.plan(4);

  getPort().then((port) => {
    const root = path.resolve(__dirname, 'public/');
    const pathMimetypeFile = path.resolve(__dirname, 'fixtures/custom_mime_type.types');
    const options = [root, '--port', port, '--mimetypes', pathMimetypeFile];
    const server = startServer(options);

    tearDown(server, t);

    server.stdout.on('data', (msg) => {
      checkServerIsRunning(`http://localhost:${port}/custom_mime_type.opml`, msg, t, (err, res) => {
        t.error(err);
        t.equal(res.headers['content-type'], 'application/secret');
      });
    });
  });
});

test('setting mimeTypes via cli - directly', (t) => {
  t.plan(4);

  getPort().then((port) => {
    const root = path.resolve(__dirname, 'public/');
    const mimeType = ['--mimetypes', '{ "application/x-my-type": ["opml"] }'];
    const options = [root, '--port', port].concat(mimeType);
    const server = startServer(options);

    // TODO: remove error handler
    tearDown(server, t);

    server.stdout.on('data', (msg) => {
      checkServerIsRunning(`http://localhost:${port}/custom_mime_type.opml`, msg, t, (err, res) => {
        t.error(err);
        t.equal(res.headers['content-type'], 'application/x-my-type');
      });
    });
  });
});

test('--proxy requires you to specify a protocol', (t) => {
  t.plan(1);
  
  const options = ['.', '--proxy', 'google.com'];
  const server = startServer(options);

  tearDown(server, t);

  server.on('exit', (code) => {
    t.equal(code, 1);
  });
});

test('--proxy-all requires --proxy', (t) => {
  t.plan(1);

  const options = ['.', '--proxy-all', 'true'];
  const server = startServer(options);

  tearDown(server, t);

  server.on('exit', (code) => {
    t.equal(code, 1);
  });
});

test('--proxy-all does not consume following positional args', (t) => {
  t.plan(4);

  const root = path.resolve(__dirname, 'fixtures', 'root');
  const targetServer = httpServer.createServer({ root });

  targetServer.listen(0, () => {
    const targetPort = targetServer.address().port;
    getPort().then((port) => {
      const options = [
        '--proxy', `http://localhost:${targetPort}`,
        '--proxy-all',
        root,
        '--port', port
      ];
      const server = startServer(options);

      tearDown(server, t);
      t.teardown(() => targetServer.close());

      let sawRootLog = false;

      server.stdout.on('data', (msg) => {
        const text = stripAnsi(msg.toString());
        if (text.includes(root)) {
          sawRootLog = true;
        }
        checkServerIsRunning(`http://localhost:${port}`, msg, t, (err, res) => {
          if (err) {
            t.fail(err.toString());
            return;
          }

          t.ok(sawRootLog, 'root path should remain positional argument');
          t.equal(res.statusCode, 200, 'proxied request should succeed');
        });
      });
    });
  });
});

function doHeaderOptionTest(t, argv, obj) {
  getPort().then((port) => {
    const options = ['.', '--port', port].concat(argv);
    const server = startServer(options);

    tearDown(server, t);

    server.stdout.on('data', (msg) => {
      checkServerIsRunning(`http://localhost:${port}`, msg, t, (err, res) => {
        t.error(err);

        for (const [k, v] of Object.entries(obj)) {
          t.equal(res.headers[k], v, 'expected header value matches in response');
        }
      });
    });
  });
}

test('single --header option is applied', (t) => {
  t.plan(4);

  doHeaderOptionTest(t,
    ['--header=X-http-server-test-A: hello'],
    { 'x-http-server-test-a': 'hello' }
  );
});

test('single -H option is applied', (t) => {
  t.plan(4);

  doHeaderOptionTest(t,
    ['-H', 'X-http-server-test-A: hello'],
    { 'x-http-server-test-a': 'hello' }
  );
});

test('mix of multiple --header and -H options are applied', (t) => {
  t.plan(7);

  doHeaderOptionTest(t,
    [
      '--header=X-http-server-test-A: Lorem ipsum dolor sit amet',
      '-H', 'X-http-server-test-B: consectetur=adipiscing; elit',
      '-H', 'X-http-server-test-C: c',
      '--header=X-http-server-test-D: d'
    ],
    {
      'x-http-server-test-a': 'Lorem ipsum dolor sit amet',
      'x-http-server-test-b': 'consectetur=adipiscing; elit',
      'x-http-server-test-c': 'c',
      'x-http-server-test-d': 'd'
    }
  );
});

test('empty header value is allowed (RFC 7230)', (t) => {
  t.plan(5);

  doHeaderOptionTest(t,
    ['-H', 'X-http-server-test-empty-a:', '-H', 'X-http-server-test-empty-b'],
    { 'x-http-server-test-empty-a': '', 'x-http-server-test-empty-b': '' }
  );
});

test('setting default content-type via cli', (t) => {
  t.plan(4);

  getPort().then((port) => {
    const root = path.resolve(__dirname, 'public/');
    const options = [root, '--port', port, '--content-type', 'text/custom'];
    const server = startServer(options);

    tearDown(server, t);

    server.stdout.on('data', (msg) => {
      checkServerIsRunning(`http://localhost:${port}/f_f`, msg, t, (err, res) => {
        t.error(err);
        t.equal(res.headers['content-type'], 'text/custom; charset=UTF-8');
      });
    });
  });
});


================================================
FILE: test/compression.test.js
================================================
'use strict';

const test = require('tap').test;
const ecstatic = require('../lib/core');
const http = require('http');
const request = require('request');

const root = `${__dirname}/public`;

test('serves brotli-encoded file when available', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    brotli: true,
    autoIndex: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/brotli`,
      headers: {
        'accept-encoding': 'gzip, deflate, br'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], 'br');
    });
  });
  t.once('end', () => {
    server.close();
  });
});

test('serves gzip-encoded file when brotli not available', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    brotli: true,
    gzip: true,
    autoIndex: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/gzip`,
      headers: {
        'accept-encoding': 'gzip, deflate, br'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], 'gzip');
    });
  });
  t.once('end', () => {
    server.close();
  });
});

test('serves gzip-encoded file when brotli not accepted', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    brotli: true,
    gzip: true,
    autoIndex: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/brotli`,
      headers: {
        'accept-encoding': 'gzip, deflate'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], 'gzip');
    });
  });
  t.once('end', () => {
    server.close();
  });
});

test('serves gzip-encoded file when brotli not enabled', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    brotli: false,
    gzip: true,
    autoIndex: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/brotli`,
      headers: {
        'accept-encoding': 'gzip, deflate, br'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], 'gzip');
    });
  });
  t.once('end', () => {
    server.close();
  });
});

test('serves unencoded file when compression not accepted', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    brotli: true,
    gzip: true,
    autoIndex: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/brotli`,
      headers: {
        'accept-encoding': ''
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], undefined);
    });
  });
  t.once('end', () => {
    server.close();
  });
});

test('serves unencoded file when compression not enabled', (t) => {
  t.plan(3);

  const server = http.createServer(ecstatic({
    root,
    brotli: false,
    gzip: false,
    autoIndex: true
  }));

  server.listen(() => {
    const port = server.address().port;
    const options = {
      uri: `http://localhost:${port}/brotli`,
      headers: {
        'accept-encoding': 'gzip, deflate, br'
      }
    };

    request.get(options, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['content-encoding'], undefined);
    });
  });
  t.once('end', () => {
    server.close();
  });
});


================================================
FILE: test/content-type.test.js
================================================
'use strict';

const test = require('tap').test;
const http = require('http');
const ecstatic = require('../lib/core');
const checkHeaders = require('./check-headers.js');

const root = `${__dirname}/public/`;

test('global default contentType', (t) => {
  let server = null;
  try {
    server = http.createServer(ecstatic({
      root,
      contentType: 'text/plain',
    }));
  } catch (e) {
    t.fail(e.message);
    t.end();
  }

  t.plan(3);

  
  checkHeaders(t, server, 'f_f', (t, headers) => {
    t.equal(headers['content-type'], 'text/plain; charset=UTF-8');
  });
});

test('content type text', (t) => {
  t.plan(3);

  const server = http.createServer(
    ecstatic({root})
  );

  checkHeaders(t, server, 'subdir/e.html', (t, headers) => {
    t.equal(headers['content-type'], 'text/html; charset=UTF-8');
  });
});

test('content type binary', (t) => {
  t.plan(3);

  const server = http.createServer(
    ecstatic({root})
  );

  checkHeaders(t, server, 'subdir/app.wasm', (t, headers) => {
    t.equal(headers['content-type'], 'application/wasm');
  });
});

test('charset arabic', (t) => {
  t.plan(3);

  const server = http.createServer(
    ecstatic({root})
  );

  checkHeaders(t, server, 'charset/arabic.html', (t, headers) => {
    t.equal(headers['content-type'], 'text/html; charset=ISO-8859-6');
  });
});

test('charset Shift_JIS', (t) => {
  t.plan(3);

  const server = http.createServer(
    ecstatic({root})
  );

  checkHeaders(t, server, 'charset/shift_jis.html', (t, headers) => {
    t.equal(headers['content-type'], 'text/html; charset=Shift_JIS');
  });
});


================================================
FILE: test/coop.test.js
================================================
'use strict';

const test = require('tap').test;
const server = require('../lib/core');
const http = require('http');
const path = require('path');
const request = require('request');

const root = path.join(__dirname, 'public');

test('coop defaults to false', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;

    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.type(res.headers['cross-origin-opener-policy'], 'undefined');
      t.type(res.headers['cross-origin-embedder-policy'], 'undefined');
    });
  });
  t.once('end', () => {
    httpServer.close();
  });
});

test('coop set to false', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      coop: false,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;

    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.type(res.headers['cross-origin-opener-policy'], 'undefined');
      t.type(res.headers['cross-origin-embedder-policy'], 'undefined');
    });
  });
  t.once('end', () => {
    httpServer.close();
  });
});

test('coop set to true', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      coop: true,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;
    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['cross-origin-opener-policy'], 'same-origin');
      t.equal(res.headers['cross-origin-embedder-policy'], 'require-corp');
    });
  });
  t.once('end', () => {
    httpServer.close();
  });
});

test('COOP set to true', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      COOP: true,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;
    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['cross-origin-opener-policy'], 'same-origin');
      t.equal(res.headers['cross-origin-embedder-policy'], 'require-corp');
    });
  });
  t.once('end', () => {
    httpServer.close();
  });
});


================================================
FILE: test/core-error.test.js
================================================
'use strict';

const test = require('tap').test;
const ecstatic = require('../lib/core');
const http = require('http');
const request = require('request');
const path = require('path');

const root = `${__dirname}/public`;
const baseDir = 'base';

require('fs').mkdirSync(`${root}/emptyDir`, {recursive: true});

const cases = require('./fixtures/common-cases-error');

test('core', (t) => {
  require('portfinder').getPort((err, port) => {
    const filenames = Object.keys(cases);

    const server = http.createServer(
      ecstatic({
        root,
        gzip: true,
        baseDir,
        autoIndex: true,
        showDir: true,
        handleError: false,
      })
    );

    server.listen(port, () => {
      let pending = filenames.length;
      filenames.forEach((file) => {
        const uri = `http://localhost:${port}${path.join('/', baseDir, file)}`;
        const headers = cases[file].headers || {};

        request.get({
          uri,
          followRedirect: false,
          headers,
        }, (err, res, body) => {
          if (err) {
            t.fail(err);
          }
          const r = cases[file];
          t.equal(res.statusCode, r.code, `status code for \`${file}\``);

          if (r.type !== undefined) {
            t.equal(
              res.headers['content-type'].split(';')[0], r.type,
              `content-type for \`${file}\``
            );
          }

          if (r.body !== undefined) {
            t.equal(body, r.body, `body for \`${file}\``);
          }

          if (r.location !== undefined) {
            t.equal(res.headers.location, path.join('/', baseDir, r.location), `location for \`${file}\``);
          }

          pending -= 1;
          if (pending === 0) {
            server.close();
            t.end();
          }
        });
      });
    });
  });
});


================================================
FILE: test/core.test.js
================================================
'use strict';

const test = require('tap').test;
const ecstatic = require('../lib/core');
const http = require('http');
const request = require('request');
const path = require('path');
const eol = require('eol');

const root = `${__dirname}/public`;
const baseDir = 'base';

require('fs').mkdirSync(`${root}/emptyDir`, {recursive: true});

const cases = require('./fixtures/common-cases');

test('core', (t) => {
  const filenames = Object.keys(cases);

  const server = http.createServer(
    ecstatic({
      root,
      gzip: true,
      baseDir,
      autoIndex: true,
      showDir: true,
      defaultExt: 'html',
      handleError: true,
    })
  );

  server.listen(0, () => {
    const port = server.address().port;
    let pending = filenames.length;
    filenames.forEach((file) => {
      const uri = `http://localhost:${port}${path.join('/', baseDir, file)}`;
      const headers = cases[file].headers || {};

      request.get({
        uri,
        followRedirect: false,
        headers,
      }, (err, res, body) => {
        if (err) {
          t.fail(err);
        }
        const r = cases[file];
        t.equal(res.statusCode, r.code, `status code for \`${file}\``);

        if (r.type !== undefined) {
          t.equal(
            res.headers['content-type'].split(';')[0], r.type,
            `content-type for \`${file}\``
          );
        }

        if (r.body !== undefined) {
          t.equal(eol.lf(body), r.body, `body for \`${file}\``);
        }

        if (r.location !== undefined) {
          t.equal(path.normalize(res.headers.location), path.join('/', baseDir, r.location), `location for \`${file}\``);
        }

        pending -= 1;
        if (pending === 0) {
          server.close();
          t.end();
        }
      });
    });
  });
});


================================================
FILE: test/cors.test.js
================================================
'use strict';

const test = require('tap').test;
const server = require('../lib/core');
const http = require('http');
const path = require('path');
const request = require('request');

const root = path.join(__dirname, 'public');

test('cors defaults to false', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;

    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.type(res.headers['access-control-allow-origin'], 'undefined');
      t.type(res.headers['access-control-allow-headers'], 'undefined');
    });
  });
  t.once('end', () => {
    httpServer.close();
  });
});

test('cors set to false', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      cors: false,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;

    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.type(res.headers['access-control-allow-origin'], 'undefined');
      t.type(res.headers['access-control-allow-headers'], 'undefined');
    });
  });
  t.once('end', () => {
    httpServer.close();
  });
});

test('cors set to true', (t) => {
  t.plan(4);

  const httpServer = http.createServer(
    server({
      root,
      cors: true,
      autoIndex: true,
      defaultExt: 'html',
    })
  );

  httpServer.listen(() => {
    const port = httpServer.address().port;
    const uri = `http://localhost:${port}/subdir/index.html`;
    request.get({ uri }, (err, res) => {
      t.error(err);
      t.equal(res.statusCode, 200);
      t.equal(res.headers['access-control-allow-origin'], '*');
      t.equal(res.headers['access-control-allow-headers'], 'Authorization, Content-Type, If-Matc
Download .txt
gitextract_g8irr2o3/

├── .dockerignore
├── .eslintrc.json
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.md
│   │   └── feature-request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── node.js.yml
│       ├── release-drafter.yml
│       └── stale.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│   └── http-server
├── doc/
│   └── http-server.1
├── lib/
│   ├── core/
│   │   ├── aliases.json
│   │   ├── etag.js
│   │   ├── index.js
│   │   ├── opts.js
│   │   ├── show-dir/
│   │   │   ├── icons.json
│   │   │   ├── index.js
│   │   │   ├── last-modified-to-string.js
│   │   │   ├── perms-to-string.js
│   │   │   ├── size-to-string.js
│   │   │   ├── sort-files.js
│   │   │   └── styles.js
│   │   └── status-handlers.js
│   ├── http-server.js
│   └── shims/
│       └── https-server-shim.js
├── package.json
├── public/
│   ├── 404.html
│   └── index.html
└── test/
    ├── 304.test.js
    ├── accept-encoding.test.js
    ├── allowed-hosts.test.js
    ├── cache.test.js
    ├── check-headers.js
    ├── cli.test.js
    ├── compression.test.js
    ├── content-type.test.js
    ├── coop.test.js
    ├── core-error.test.js
    ├── core.test.js
    ├── cors.test.js
    ├── custom-content-type-file-secret.test.js
    ├── custom-content-type-file.test.js
    ├── custom-content-type.test.js
    ├── default-default-ext.test.js
    ├── dir-overrides-404.test.js
    ├── enotdir.test.js
    ├── escaping.test.js
    ├── express-error.test.js
    ├── express.test.js
    ├── fixtures/
    │   ├── common-cases-error.js
    │   ├── common-cases.js
    │   ├── custom_mime_type.types
    │   ├── https/
    │   │   ├── agent2-cert.pem
    │   │   └── agent2-key.pem
    │   ├── proxy-all-local/
    │   │   ├── does-not-exist
    │   │   └── file
    │   └── root/
    │       ├── canYouSeeMe
    │       ├── compression/
    │       │   ├── index.html
    │       │   └── index.html.br
    │       ├── file
    │       └── htmlButNot
    ├── force-content-encoding.test.js
    ├── headers.test.js
    ├── illegal-access-date.test.js
    ├── localhost.test.js
    ├── main.test.js
    ├── malformed-dir.test.js
    ├── malformed.test.js
    ├── mime.test.js
    ├── network-interfaces.test.js
    ├── pathname-encoding.test.js
    ├── private-network-access.test.js
    ├── process-env-port.test.js
    ├── proxy-all.test.js
    ├── proxy-config.test.js
    ├── proxy-options.test.js
    ├── public/
    │   ├── 404.html
    │   ├── a.txt
    │   ├── another-subdir/
    │   │   └── scripts.js
    │   ├── b.txt
    │   ├── brotli/
    │   │   ├── fake_ecstatic
    │   │   ├── fake_ecstatic.br
    │   │   ├── index.html
    │   │   ├── index.html.br
    │   │   ├── not_actually_brotli.br
    │   │   ├── real_ecstatic
    │   │   └── real_ecstatic.br
    │   ├── c.js
    │   ├── charset/
    │   │   ├── arabic.html
    │   │   └── shift_jis.html
    │   ├── compress/
    │   │   ├── foo.js
    │   │   └── foo_2.js
    │   ├── curimit@gmail.com (40%)/
    │   │   └── index.html
    │   ├── custom_mime_type.opml
    │   ├── custom_mime_type.types
    │   ├── d.js
    │   ├── dir-overrides-404/
    │   │   ├── 404.html
    │   │   └── directory/
    │   │       └── file.txt
    │   ├── e.js
    │   ├── f_f
    │   ├── gzip/
    │   │   ├── fake_ecstatic
    │   │   ├── index.html
    │   │   └── real_ecstatic
    │   ├── show-dir$$href_encoding$$/
    │   │   └── aname+aplus.txt
    │   ├── subdir/
    │   │   ├── app.wasm
    │   │   ├── e.html
    │   │   └── index.html
    │   ├── subdir_with space/
    │   │   ├── file_with space.html
    │   │   └── index.html
    │   └── 中文/
    │       └── 檔案.html
    ├── range.test.js
    ├── showdir-href-encoding.test.js
    ├── showdir-search-encoding.test.js
    ├── showdir-with-spaces.test.js
    ├── timeout.test.js
    ├── trailing-slash.test.js
    └── websocket-proxy.test.js
Download .txt
SYMBOL INDEX (35 symbols across 12 files)

FILE: lib/core/index.js
  function decodePathname (line 21) | function decodePathname(pathname) {
  function ensureUriEncoded (line 38) | function ensureUriEncoded(text) {
  function shouldCompressGzip (line 44) | function shouldCompressGzip(req) {
  function shouldCompressBrotli (line 54) | function shouldCompressBrotli(req) {
  function hasGzipId12 (line 64) | function hasGzipId12(gzipped, cb) {
  function shouldReturn304 (line 125) | function shouldReturn304(req, serverLastModified, serverEtag) {
  function serve (line 224) | function serve(stat) {
  function statWithAccess (line 341) | function statWithAccess (file, cb) {
  function statFile (line 355) | function statFile() {
  function isTextFile (line 443) | function isTextFile(mimeType) {
  function tryServeWithGzip (line 448) | function tryServeWithGzip() {
  function tryServeWithBrotli (line 470) | function tryServeWithBrotli(shouldTryGzip) {

FILE: lib/core/opts.js
  function isDeclared (line 64) | function isDeclared(k) {
  function validateNoCRLF (line 68) | function validateNoCRLF(str) {
  function addHeader (line 74) | function addHeader(key, value) {
  function setHeader (line 80) | function setHeader(str) {

FILE: lib/core/show-dir/index.js
  function prerender (line 80) | function prerender(dirs, renderFiles, errs) {
  function render (line 121) | function render(dirs, renderFiles, errs) {

FILE: lib/core/show-dir/styles.js
  constant IMG_SIZE (line 5) | const IMG_SIZE = 16;

FILE: lib/http-server.js
  function HttpServer (line 31) | function HttpServer(options) {

FILE: lib/shims/https-server-shim.js
  function requestHandler (line 16) | function requestHandler(req, res) {

FILE: test/cache.test.js
  method cache (line 64) | cache() {
  method cache (line 99) | cache() {

FILE: test/cli.test.js
  function startServer (line 14) | function startServer(args) {
  function checkServerIsRunning (line 18) | function checkServerIsRunning(url, msg, t, _cb) {
  function tearDown (line 36) | function tearDown(ps, t) {
  function doHeaderOptionTest (line 174) | function doHeaderOptionTest(t, argv, obj) {

FILE: test/custom-content-type-file.test.js
  function setup (line 8) | function setup(opts) {

FILE: test/network-interfaces.test.js
  function getIPv4Addresses (line 35) | function getIPv4Addresses(interfaces) {

FILE: test/process-env-port.test.js
  function getRandomInt (line 7) | function getRandomInt(min, max) {
  function checkServerIsRunning (line 15) | function checkServerIsRunning(url, msg, ps, t) {
  function startServer (line 30) | function startServer(url, port, t) {

FILE: test/proxy-all.test.js
  constant REQUEST_TIMEOUT (line 13) | const REQUEST_TIMEOUT = 5000;
  function listen (line 15) | function listen(server) {
  function requestWithTimeout (line 30) | function requestWithTimeout(url) {
Condensed preview — 125 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (289K chars).
[
  {
    "path": ".dockerignore",
    "chars": 103,
    "preview": "node_modules\nyarn-error.log\nnpm-debug.log\nDockerfile\n.dockerignore\n.git\n.gitignore\ntestdir/privatefile\n"
  },
  {
    "path": ".eslintrc.json",
    "chars": 234,
    "preview": "{\n  \"extends\": \"eslint-config-populist\",\n  \"rules\": {\n    \"strict\": \"warn\",\n    \"indent\": [\"warn\", 2],\n    \"valid-jsdoc\""
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 4348,
    "preview": "# Contributing to http-server\n\n> Please read these guidelines before submitting an issue, filing a feature request, or c"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 17,
    "preview": "github: thornjad\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "chars": 535,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\n---\n\n<!-- Describe the issue briefly here. -->\n\n#### Envi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "chars": 736,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\n---\n\n#### What's the problem this feature will solve?\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 772,
    "preview": "<!--- Describe the changes here. --->\n\n##### Relevant issues\n\n<!--\n    Link to the issue(s) this pull request fixes here"
  },
  {
    "path": ".github/release-drafter.yml",
    "chars": 734,
    "preview": "name-template: 'v$RESOLVED_VERSION'\ntag-template: 'v$RESOLVED_VERSION'\n\nchange-template: '- $TITLE @$AUTHOR (#$NUMBER)'\n"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "chars": 867,
    "preview": "# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests ac"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "chars": 512,
    "preview": "name: release-drafter\n\non:\n  push:\n    # branches to consider in the event; optional, defaults to all\n    branches:\n    "
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 758,
    "preview": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n  - cron: '25 12 * * *'\n\njobs:\n  stale:\n\n    runs-on: ubuntu-"
  },
  {
    "path": ".gitignore",
    "chars": 80,
    "preview": "node_modules/\nnpm-debug.log*\n.nyc_*/\n.dir-locals.el\n.DS_Store\n.httpserver*\n.tap\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5230,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "Dockerfile",
    "chars": 190,
    "preview": "FROM node:16-alpine\nVOLUME /public\nWORKDIR /srv/http-server\nCOPY package.json package-lock.json ./\nRUN npm install --pro"
  },
  {
    "path": "LICENSE",
    "chars": 1124,
    "preview": "Copyright (c) 2011-2026 Charlie Robbins, Marak Squires, Jade Michael Thornton and the Contributors.\n\nPermission is hereb"
  },
  {
    "path": "README.md",
    "chars": 8537,
    "preview": "[![GitHub Workflow Status (master)](https://img.shields.io/github/actions/workflow/status/http-party/http-server/node.js"
  },
  {
    "path": "SECURITY.md",
    "chars": 401,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n|---------|------------------------|\n| 14.x  | ✔️ Yes "
  },
  {
    "path": "bin/http-server",
    "chars": 16145,
    "preview": "#!/usr/bin/env node\n\n'use strict';\n\nvar chalk     = require('chalk'),\n  os         = require('os'),\n  httpServer = requi"
  },
  {
    "path": "doc/http-server.1",
    "chars": 5864,
    "preview": ".TH http-server 1 \"April 2020\" GNU \"http-server man page\"\n\n.SH NAME\nhttp-server \\- a simple zero-configuration command-l"
  },
  {
    "path": "lib/core/aliases.json",
    "chars": 1233,
    "preview": "{\n  \"autoIndex\": [ \"autoIndex\", \"autoindex\" ],\n  \"showDir\": [ \"showDir\", \"showdir\" ],\n  \"dirOverrides404\": [\n    \"dirOve"
  },
  {
    "path": "lib/core/etag.js",
    "chars": 197,
    "preview": "'use strict';\n\nmodule.exports = (stat, weakEtag) => {\n  let etag = `\"${[stat.ino, stat.size, stat.mtime.toISOString()].j"
  },
  {
    "path": "lib/core/index.js",
    "chars": 15091,
    "preview": "#! /usr/bin/env node\n\n'use strict';\n\nconst path = require('path');\nconst fs = require('fs');\nconst url = require('url');"
  },
  {
    "path": "lib/core/opts.js",
    "chars": 6820,
    "preview": "'use strict';\n\nconst aliases = require('./aliases.json');\n\n\n/**\n * @typedef {Object} ParsedOptions\n * @property {boolean"
  },
  {
    "path": "lib/core/show-dir/icons.json",
    "chars": 61907,
    "preview": "{\n  \"_blank\": \"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUe"
  },
  {
    "path": "lib/core/show-dir/index.js",
    "chars": 8494,
    "preview": "'use strict';\n\nconst styles = require('./styles');\nconst lastModifiedToString = require('./last-modified-to-string');\nco"
  },
  {
    "path": "lib/core/show-dir/last-modified-to-string.js",
    "chars": 442,
    "preview": "'use strict';\n\nmodule.exports = function lastModifiedToString(stat) {\n  if (!stat.mtime) {\n    // stat error (eg, broken"
  },
  {
    "path": "lib/core/show-dir/perms-to-string.js",
    "chars": 393,
    "preview": "'use strict';\n\nmodule.exports = function permsToString(stat) {\n  if (!stat.isDirectory || !stat.mode) {\n    return '????"
  },
  {
    "path": "lib/core/show-dir/size-to-string.js",
    "chars": 763,
    "preview": "'use strict';\n\n// given a file's stat, return the size of it in string\n// humanReadable: (boolean) whether to result is "
  },
  {
    "path": "lib/core/show-dir/sort-files.js",
    "chars": 768,
    "preview": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\n\nmodule.exports = function sortByIsDirectory(dir,"
  },
  {
    "path": "lib/core/show-dir/styles.js",
    "chars": 740,
    "preview": "'use strict';\n\nconst icons = require('./icons.json');\n\nconst IMG_SIZE = 16;\n\nlet css = `i.icon { display: block; height:"
  },
  {
    "path": "lib/core/status-handlers.js",
    "chars": 2337,
    "preview": "'use strict';\n\nconst he = require('he');\n\n// not modified\nexports['304'] = (res) => {\n  res.statusCode = 304;\n  res.end("
  },
  {
    "path": "lib/http-server.js",
    "chars": 9510,
    "preview": "'use strict';\n\nvar fs = require('fs'),\n  union = require('union'),\n  httpServerCore = require('./core'),\n  auth = requir"
  },
  {
    "path": "lib/shims/https-server-shim.js",
    "chars": 1701,
    "preview": "/* eslint-disable no-process-env */\n/* eslint-disable no-sync */\nvar https = require('https');\nvar fs = require('fs');\nv"
  },
  {
    "path": "package.json",
    "chars": 2704,
    "preview": "{\n  \"name\": \"http-server\",\n  \"version\": \"14.1.2\",\n  \"description\": \"A simple zero-configuration command-line http server"
  },
  {
    "path": "public/404.html",
    "chars": 148,
    "preview": "<html>\n  <head>\n    <title>404</title>\n  </head>\n  <body>\n    <h1>404</h1>\n\n    <p>Were you just making up filenames or "
  },
  {
    "path": "public/index.html",
    "chars": 210,
    "preview": "<html>\n  <head>\n    <title>node.js http server</title>\n  </head>\n  <body>\n\n    <h1>Serving up static files like they wer"
  },
  {
    "path": "test/304.test.js",
    "chars": 5642,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/accept-encoding.test.js",
    "chars": 1396,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/allowed-hosts.test.js",
    "chars": 1877,
    "preview": "const test = require('tap').test\nconst path = require('path')\nconst httpServer = require('../lib/http-server')\nconst req"
  },
  {
    "path": "test/cache.test.js",
    "chars": 3129,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst request = require('request');\nconst"
  },
  {
    "path": "test/check-headers.js",
    "chars": 393,
    "preview": "const request = require('request');\n\nmodule.exports = (t, server, path, check) => {\n  server.listen(() => {\n    const po"
  },
  {
    "path": "test/cli.test.js",
    "chars": 6662,
    "preview": "'use strict';\n\n/* this test suit is incomplete  2015-12-18 */\n\nconst test = require('tap').test;\nconst request = require"
  },
  {
    "path": "test/compression.test.js",
    "chars": 3954,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/content-type.test.js",
    "chars": 1599,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst ecstatic = require('../lib/core');\n"
  },
  {
    "path": "test/coop.test.js",
    "chars": 2792,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst server = require('../lib/core');\nconst http = require('http');\nco"
  },
  {
    "path": "test/core-error.test.js",
    "chars": 1835,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/core.test.js",
    "chars": 1796,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/cors.test.js",
    "chars": 2936,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst server = require('../lib/core');\nconst http = require('http');\nco"
  },
  {
    "path": "test/custom-content-type-file-secret.test.js",
    "chars": 831,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst request = require('request');\nconst"
  },
  {
    "path": "test/custom-content-type-file.test.js",
    "chars": 1105,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst request = require('request');\nconst"
  },
  {
    "path": "test/custom-content-type.test.js",
    "chars": 811,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst request = require('request');\nconst"
  },
  {
    "path": "test/default-default-ext.test.js",
    "chars": 601,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/dir-overrides-404.test.js",
    "chars": 2090,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst ecstatic = require('../lib/core');\n"
  },
  {
    "path": "test/enotdir.test.js",
    "chars": 601,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/escaping.test.js",
    "chars": 620,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/express-error.test.js",
    "chars": 1871,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/express.test.js",
    "chars": 1817,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/fixtures/common-cases-error.js",
    "chars": 237,
    "preview": "'use strict';\n\nmodule.exports = {\n  404: {\n    code: 200,\n  },\n  'something non-existant': {\n    code: 404,\n  },\n};\n\nif "
  },
  {
    "path": "test/fixtures/common-cases.js",
    "chars": 3895,
    "preview": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst eol = require('eol');\n\nmodule.exports = {\n "
  },
  {
    "path": "test/fixtures/custom_mime_type.types",
    "chars": 214,
    "preview": "# This file is an example of the Apache .types file format for describing mime-types.\n# Other example: http://svn.apache"
  },
  {
    "path": "test/fixtures/https/agent2-cert.pem",
    "chars": 1493,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIEIDCCAggCCQChRDh/XiBF+zANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJ1\nczETMBEGA1UECAwKV2FzaGluZ3R"
  },
  {
    "path": "test/fixtures/https/agent2-key.pem",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJSACvkGCQUCJqOceESbg6\nIHdRzQdoccE4P3sbvNsf9Bl"
  },
  {
    "path": "test/fixtures/proxy-all-local/does-not-exist",
    "chars": 59,
    "preview": "This file should never be served when proxyAll is enabled.\n"
  },
  {
    "path": "test/fixtures/proxy-all-local/file",
    "chars": 29,
    "preview": "Local proxy-all fixture file\n"
  },
  {
    "path": "test/fixtures/root/canYouSeeMe",
    "chars": 35,
    "preview": "I bet you can. I'm in your index.\n\n"
  },
  {
    "path": "test/fixtures/root/compression/index.html",
    "chars": 20,
    "preview": "I'm not compressed!\n"
  },
  {
    "path": "test/fixtures/root/compression/index.html.br",
    "chars": 28,
    "preview": "\u000b\n�im brotli compressed!!\n\u0003\n"
  },
  {
    "path": "test/fixtures/root/file",
    "chars": 25,
    "preview": "hello, I know nodejitsu\n\n"
  },
  {
    "path": "test/fixtures/root/htmlButNot",
    "chars": 65,
    "preview": "<div>\n  <h1>I am HTML?</h1>\n  <small>yeah i guess</small>\n</div>\n"
  },
  {
    "path": "test/force-content-encoding.test.js",
    "chars": 2476,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/headers.test.js",
    "chars": 2082,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/illegal-access-date.test.js",
    "chars": 677,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/localhost.test.js",
    "chars": 723,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/main.test.js",
    "chars": 12418,
    "preview": "const test = require('tap').test;\nconst path = require('path');\nconst fs = require('fs');\nconst request = require('reque"
  },
  {
    "path": "test/malformed-dir.test.js",
    "chars": 508,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/malformed.test.js",
    "chars": 481,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/mime.test.js",
    "chars": 1133,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst mime = require('mime');\n\ntest('mime package lookup', (t) => {\n  t"
  },
  {
    "path": "test/network-interfaces.test.js",
    "chars": 3058,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst os = require('os');\n\ntest('network interface display handles both"
  },
  {
    "path": "test/pathname-encoding.test.js",
    "chars": 1781,
    "preview": "'use strict';\n\nconst tap = require('tap');\nconst ecstatic = require('../lib/core');\nconst http = require('http');\nconst "
  },
  {
    "path": "test/private-network-access.test.js",
    "chars": 2031,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst server = require('../lib/core');\nconst http = require('http');\nco"
  },
  {
    "path": "test/process-env-port.test.js",
    "chars": 1546,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst request = require('request');\nconst spawn = require('child_proces"
  },
  {
    "path": "test/proxy-all.test.js",
    "chars": 2507,
    "preview": "'use strict';\n\nconst http = require('http');\nconst test = require('tap').test;\nconst path = require('path');\nconst fs = "
  },
  {
    "path": "test/proxy-config.test.js",
    "chars": 3154,
    "preview": "const test = require('tap').test\nconst path = require('path')\nconst fs = require('fs')\nconst request = require('request'"
  },
  {
    "path": "test/proxy-options.test.js",
    "chars": 3179,
    "preview": "const test = require('tap').test\nconst path = require('path')\nconst fs = require('fs')\nconst request = require('request'"
  },
  {
    "path": "test/public/404.html",
    "chars": 13,
    "preview": "<h1>404</h1>\n"
  },
  {
    "path": "test/public/a.txt",
    "chars": 5,
    "preview": "A!!!\n"
  },
  {
    "path": "test/public/another-subdir/scripts.js",
    "chars": 15,
    "preview": "var foo = bar;\n"
  },
  {
    "path": "test/public/b.txt",
    "chars": 5,
    "preview": "B!!!\n"
  },
  {
    "path": "test/public/brotli/fake_ecstatic",
    "chars": 9,
    "preview": "ecstatic\n"
  },
  {
    "path": "test/public/brotli/index.html",
    "chars": 34,
    "preview": "brotli, but I'm not compressed!!!\n"
  },
  {
    "path": "test/public/brotli/index.html.br",
    "chars": 24,
    "preview": "\u000b\nbrotli, compressed!!\n\u0003"
  },
  {
    "path": "test/public/brotli/not_actually_brotli.br",
    "chars": 43,
    "preview": "You've been duped! This is not compressed!\n"
  },
  {
    "path": "test/public/brotli/real_ecstatic",
    "chars": 9,
    "preview": "ecstatic\n"
  },
  {
    "path": "test/public/brotli/real_ecstatic.br",
    "chars": 12,
    "preview": "\u000b\u0004ecstatic\n\u0003"
  },
  {
    "path": "test/public/c.js",
    "chars": 21,
    "preview": "console.log('C!!!');\n"
  },
  {
    "path": "test/public/charset/arabic.html",
    "chars": 397,
    "preview": "<!DOCTYPE html>\n<html lang=\"ar\">\n  <head>\n    <meta charset=\"ISO-8859-6\" />\n    <meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "test/public/charset/shift_jis.html",
    "chars": 360,
    "preview": "<!DOCTYPE html>\n<html lang=\"ja\">\n  <head>\n    <meta charset=\"shift_jis\" />\n    <meta name=\"viewport\" content=\"width=devi"
  },
  {
    "path": "test/public/compress/foo.js",
    "chars": 21,
    "preview": "exports.foo = \"baz\";\n"
  },
  {
    "path": "test/public/compress/foo_2.js",
    "chars": 21,
    "preview": "exports.foo = \"baz\";\n"
  },
  {
    "path": "test/public/curimit@gmail.com (40%)/index.html",
    "chars": 9,
    "preview": "index!!!\n"
  },
  {
    "path": "test/public/custom_mime_type.opml",
    "chars": 917,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?xml-stylesheet type=\"text/xsl\" href=\"/hyperscope/src/client/lib/hs/xslt/hypersc"
  },
  {
    "path": "test/public/custom_mime_type.types",
    "chars": 211,
    "preview": "# This file is an example of the Apache .types file format for describing mime-types.\n# Other example: http://svn.apache"
  },
  {
    "path": "test/public/d.js",
    "chars": 5,
    "preview": "d.js\n"
  },
  {
    "path": "test/public/dir-overrides-404/404.html",
    "chars": 7,
    "preview": "404file"
  },
  {
    "path": "test/public/dir-overrides-404/directory/file.txt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/public/e.js",
    "chars": 21,
    "preview": "console.log('π!!!');\n"
  },
  {
    "path": "test/public/f_f",
    "chars": 4,
    "preview": "f!!\n"
  },
  {
    "path": "test/public/gzip/fake_ecstatic",
    "chars": 9,
    "preview": "ecstatic\n"
  },
  {
    "path": "test/public/gzip/index.html",
    "chars": 7,
    "preview": "gzip!!\n"
  },
  {
    "path": "test/public/gzip/real_ecstatic",
    "chars": 9,
    "preview": "ecstatic\n"
  },
  {
    "path": "test/public/show-dir$$href_encoding$$/aname+aplus.txt",
    "chars": 161,
    "preview": "It should generate an href of <a href=\"/show-dir-href-encoding/aname+aplus.txt\">..\n\nhttp://stackoverflow.com/a/1006074/1"
  },
  {
    "path": "test/public/subdir/app.wasm",
    "chars": 22,
    "preview": "Fake WebAssemble file\n"
  },
  {
    "path": "test/public/subdir/e.html",
    "chars": 11,
    "preview": "<b>e!!</b>\n"
  },
  {
    "path": "test/public/subdir/index.html",
    "chars": 9,
    "preview": "index!!!\n"
  },
  {
    "path": "test/public/subdir_with space/file_with space.html",
    "chars": 18,
    "preview": "<b>spacious!!</b>\n"
  },
  {
    "path": "test/public/subdir_with space/index.html",
    "chars": 9,
    "preview": "index :)\n"
  },
  {
    "path": "test/public/中文/檔案.html",
    "chars": 14,
    "preview": "<b>file!!</b>\n"
  },
  {
    "path": "test/range.test.js",
    "chars": 5053,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/showdir-href-encoding.test.js",
    "chars": 789,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/showdir-search-encoding.test.js",
    "chars": 938,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/showdir-with-spaces.test.js",
    "chars": 856,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/timeout.test.js",
    "chars": 4858,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst http = require('http');\nconst httpServer = require('../lib/http-s"
  },
  {
    "path": "test/trailing-slash.test.js",
    "chars": 693,
    "preview": "'use strict';\n\nconst test = require('tap').test;\nconst ecstatic = require('../lib/core');\nconst http = require('http');\n"
  },
  {
    "path": "test/websocket-proxy.test.js",
    "chars": 7798,
    "preview": "const test = require('tap').test\nconst path = require('path')\nconst http = require('http')\nconst httpServer = require('."
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the http-party/http-server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 125 files (267.5 KB), approximately 99.4k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!