diff --git a/CloudronManifest.json b/CloudronManifest.json
new file mode 100644
index 0000000..8c8ded6
--- /dev/null
+++ b/CloudronManifest.json
@@ -0,0 +1,9 @@
+{
+ "version": "0.1.0",
+ "healthCheckPath": "/",
+ "httpPort": 3000,
+ "addons": {
+ "localstorage": {}
+ },
+ "manifestVersion": 2
+}
diff --git a/awesomebox.sty b/awesomebox.sty
new file mode 100644
index 0000000..0d43b92
--- /dev/null
+++ b/awesomebox.sty
@@ -0,0 +1,159 @@
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{awesomebox}[2019/07/27 v0.6]
+
+% Awesome Box has been written by Étienne Deparis and is released under
+% the WTFPL (http://www.wtfpl.net/txt/copying/).
+% A copy of this license is distributed in this package.
+%
+% Contributors:
+% v0.2: Vincent Goulet https://github.com/vigou3
+% v0.3: Gilbert Fuerer https://github.com/foreachthing
+% * Remove xelatex/luatex requirement and add pdflatex compatibility
+% v0.4: Marcel Krüger https://github.com/zauguin
+% * Fontawesome5 compatibility
+% Gilbert Fuerer
+% * Custom vertical rule color
+% v0.5: Georger Araújo https://github.com/georgerbr
+% * Horizontal rules at top and bottom, title
+% v0.6: Vincent Goulet https://github.com/vigou3
+% * Improving table cell rendering
+
+% https://ctan.org/pkg/fontawesome5
+\RequirePackage{fontawesome5}
+
+% Compatibility with old version of this package
+\def\abIconCheck{\faCheck}
+\def\abIconInfoCircle{\faInfoCircle}
+\def\abIconFire{\faBomb}
+\def\abIconExclamationCircle{\faExclamationCircle}
+\def\abIconExclamationTriangle{\faExclamationTriangle}
+\def\abIconCogs{\faCogs}
+\def\abIconThumbsUp{\faThumbsUp}
+\def\abIconThumbsDown{\faThumbsDown}
+\def\abIconCertificate{\faCertificate}
+\def\abIconLightBulb{\faLightbulb[regular]}
+\def\abIconTwitter{\faTwitter}
+\def\abIconGithub{\faGithub}
+
+% https://ctan.org/pkg/xcolor
+\RequirePackage{xcolor}
+% Some basic colors
+\definecolor{abnote}{RGB}{25,64,122}
+\definecolor{abcaution}{RGB}{245,0,0}
+\definecolor{abwarning}{RGB}{255,215,0}
+\definecolor{abimportant}{RGB}{220,0,0}
+\definecolor{abvrulecolor}{RGB}{221,221,216}
+
+% Customizable length
+\newlength{\aweboxleftmargin}
+\newlength{\aweboxcontentwidth}
+\newlength{\aweboxvskip}
+\setlength{\aweboxvskip}{5mm}
+\newlength{\aweboxsignraise}
+\setlength{\aweboxsignraise}{-5mm}
+\newlength{\aweboxrulewidth}
+\setlength{\aweboxrulewidth}{2pt}
+
+\AtBeginDocument{%
+ \setlength{\aweboxleftmargin}{0.12\linewidth}%
+ \setlength{\aweboxcontentwidth}{0.88\linewidth}}
+
+\RequirePackage{array}
+
+% To allow for more than one optional argument
+\RequirePackage{xparse}
+
+% Horizontal rule definitions used with second optional argument [hrule]
+\def\abShortLine{\cline{2-2}}
+\def\abLongLine{\hline}
+
+% The following commands are used to adjust and restore awesome boxes
+% content width in respect to the current environment (e.g. lists).
+\RequirePackage{ifthen}
+\newlength{\aweboxlinewidthvar}
+\setlength{\aweboxlinewidthvar}{0pt}
+\newlength{\aweboxlinewidthref}
+\AtBeginDocument{\setlength{\aweboxlinewidthref}{\linewidth}}
+\newcommand{\awesomeboxadjustcontentwidth}{%
+ \ifthenelse{\lengthtest{\linewidth=\aweboxlinewidthref}}{}{%
+ \setlength{\aweboxlinewidthvar}{\dimexpr\aweboxlinewidthref-\linewidth}%
+ \setlength{\aweboxcontentwidth}{\dimexpr\aweboxcontentwidth-\aweboxlinewidthvar}}}%
+ %\aweboxdebug}}
+\newcommand{\awesomeboxrestorecontentwidth}{%
+ \ifthenelse{\lengthtest{\linewidth=\textwidth}}{}{%
+ \setlength{\aweboxcontentwidth}{\dimexpr\aweboxcontentwidth+\aweboxlinewidthvar}%
+ \setlength{\aweboxlinewidthvar}{0pt}}}
+
+% Commands API
+% \awesomebox[vrulecolor][hrule][title]{vrulewidth}{icon}{iconcolor}{content}
+\NewDocumentCommand \awesomebox { O{abvrulecolor} O{} o m m m +m }{%
+ {\vskip \aweboxvskip}\noindent\awesomeboxadjustcontentwidth%
+ \begin{tabular}%
+ {@{}>{\centering\arraybackslash}%
+ p{\aweboxleftmargin}@{}!{\color{#1}\vrule width #4}%
+ p{\dimexpr\aweboxcontentwidth-#4-\tabcolsep}@{}}
+ \IfValueTF {#3}
+ { & #3 \\ #2 \raisebox{\aweboxsignraise}{\textcolor{#6}{\Huge#5}} & #7 \\ #2}
+ { #2 \raisebox{\aweboxsignraise}{\textcolor{#6}{\Huge#5}} & #7 \\ #2}
+ \end{tabular}{\vskip \aweboxvskip}\awesomeboxrestorecontentwidth}
+
+\newcommand{\notebox}[1]{%
+ \awesomebox[abnote]{\aweboxrulewidth}{\abIconInfoCircle}{abnote}{#1}}
+
+\newcommand{\tipbox}[1]{%
+ \awesomebox{\aweboxrulewidth}{\abIconLightBulb}{black}{#1}}
+
+\newcommand{\warningbox}[1]{%
+ \awesomebox[abwarning]{\aweboxrulewidth}{\abIconExclamationTriangle}{abwarning}{#1}}
+
+\newcommand{\cautionbox}[1]{%
+ \awesomebox[abcaution]{\aweboxrulewidth}{\abIconFire}{abcaution}{#1}}
+
+\newcommand{\importantbox}[1]{%
+ \awesomebox[abimportant]{\aweboxrulewidth}{\abIconExclamationCircle}{abimportant}{#1}}
+
+% Environments API
+% \begin{awesomeblock}[vrulecolor][hrule][title]{vrulewidth}{icon}{iconcolor}
+% content
+% \end{awesomeblock}
+\NewDocumentEnvironment{awesomeblock}{ O{abvrulecolor} O{} o m m m }
+{{\vskip \aweboxvskip}\noindent\awesomeboxadjustcontentwidth%
+ \begin{tabular}%
+ {@{}>{\centering\arraybackslash}%
+ p{\aweboxleftmargin}@{}!{\color{#1}\vrule width #4}%
+ p{\dimexpr\aweboxcontentwidth-#4-\tabcolsep}@{}}
+ \IfValueTF {#3}
+ { & #3 \\ #2 \raisebox{\aweboxsignraise}{\textcolor{#6}{\Huge#5}} &}
+ { #2 \raisebox{\aweboxsignraise}{\textcolor{#6}{\Huge#5}} &}}
+{\\ #2 \end{tabular}{\vskip \aweboxvskip}\awesomeboxrestorecontentwidth}
+
+\newenvironment{noteblock}%
+{\begin{awesomeblock}[abnote]{\aweboxrulewidth}{\abIconInfoCircle}{abnote}}
+ {\end{awesomeblock}}
+
+\newenvironment{tipblock}%
+{\begin{awesomeblock}{\aweboxrulewidth}{\abIconLightBulb}{black}}
+ {\end{awesomeblock}}
+
+\newenvironment{warningblock}%
+{\begin{awesomeblock}[abwarning]{\aweboxrulewidth}{\abIconExclamationTriangle}{abwarning}}
+ {\end{awesomeblock}}
+
+\newenvironment{cautionblock}%
+{\begin{awesomeblock}[abcaution]{\aweboxrulewidth}{\abIconFire}{abcaution}}
+ {\end{awesomeblock}}
+
+\newenvironment{importantblock}%
+{\begin{awesomeblock}[abimportant]{\aweboxrulewidth}{\abIconExclamationCircle}{abimportant}}
+ {\end{awesomeblock}}
+
+\newcommand{\aweboxdebug}{%
+ \typeout{**************************}%
+ \typeout{Line width: \the\linewidth}%
+ \typeout{Reference line width: \the\aweboxlinewidthref\space(text width: \the\textwidth)}%
+ \typeout{Width difference: \the\aweboxlinewidthvar}%
+ \typeout{Margin width: \the\aweboxleftmargin}%
+ \typeout{Content width: \the\aweboxcontentwidth}%
+ \typeout{**************************}}
+
+\endinput
diff --git a/eisvogel.latex b/eisvogel.latex
new file mode 100644
index 0000000..c88e8be
--- /dev/null
+++ b/eisvogel.latex
@@ -0,0 +1,1069 @@
+%%
+% Copyright (c) 2017 - 2021, Pascal Wagler;
+% Copyright (c) 2014 - 2021, John MacFarlane
+%
+% All rights reserved.
+%
+% Redistribution and use in source and binary forms, with or without
+% modification, are permitted provided that the following conditions
+% are met:
+%
+% - Redistributions of source code must retain the above copyright
+% notice, this list of conditions and the following disclaimer.
+%
+% - Redistributions in binary form must reproduce the above copyright
+% notice, this list of conditions and the following disclaimer in the
+% documentation and/or other materials provided with the distribution.
+%
+% - Neither the name of John MacFarlane nor the names of other
+% contributors may be used to endorse or promote products derived
+% from this software without specific prior written permission.
+%
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+% COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+% POSSIBILITY OF SUCH DAMAGE.
+%%
+
+
+%%
+% This is the Eisvogel pandoc LaTeX template.
+%
+% For usage information and examples visit the official GitHub page:
+% https://github.com/Wandmalfarbe/pandoc-latex-template
+%%
+
+% Options for packages loaded elsewhere
+\PassOptionsToPackage{unicode$for(hyperrefoptions)$,$hyperrefoptions$$endfor$}{hyperref}
+\PassOptionsToPackage{hyphens}{url}
+\PassOptionsToPackage{dvipsnames,svgnames*,x11names*,table}{xcolor}
+$if(dir)$
+$if(latex-dir-rtl)$
+\PassOptionsToPackage{RTLdocument}{bidi}
+$endif$
+$endif$
+$if(CJKmainfont)$
+\PassOptionsToPackage{space}{xeCJK}
+$endif$
+%
+\documentclass[
+$if(fontsize)$
+ $fontsize$,
+$endif$
+$if(lang)$
+ $babel-lang$,
+$endif$
+$if(papersize)$
+ $papersize$paper,
+$else$
+ paper=a4,
+$endif$
+$if(beamer)$
+ ignorenonframetext,
+$if(handout)$
+ handout,
+$endif$
+$if(aspectratio)$
+ aspectratio=$aspectratio$,
+$endif$
+$endif$
+$for(classoption)$
+ $classoption$$sep$,
+$endfor$
+ ,captions=tableheading
+]{$if(beamer)$$documentclass$$else$$if(book)$scrbook$else$scrartcl$endif$$endif$}
+$if(beamer)$
+$if(background-image)$
+\usebackgroundtemplate{%
+ \includegraphics[width=\paperwidth]{$background-image$}%
+}
+$endif$
+\usepackage{pgfpages}
+\setbeamertemplate{caption}[numbered]
+\setbeamertemplate{caption label separator}{: }
+\setbeamercolor{caption name}{fg=normal text.fg}
+\beamertemplatenavigationsymbols$if(navigation)$$navigation$$else$empty$endif$
+$for(beameroption)$
+\setbeameroption{$beameroption$}
+$endfor$
+% Prevent slide breaks in the middle of a paragraph
+\widowpenalties 1 10000
+\raggedbottom
+$if(section-titles)$
+\setbeamertemplate{part page}{
+ \centering
+ \begin{beamercolorbox}[sep=16pt,center]{part title}
+ \usebeamerfont{part title}\insertpart\par
+ \end{beamercolorbox}
+}
+\setbeamertemplate{section page}{
+ \centering
+ \begin{beamercolorbox}[sep=12pt,center]{part title}
+ \usebeamerfont{section title}\insertsection\par
+ \end{beamercolorbox}
+}
+\setbeamertemplate{subsection page}{
+ \centering
+ \begin{beamercolorbox}[sep=8pt,center]{part title}
+ \usebeamerfont{subsection title}\insertsubsection\par
+ \end{beamercolorbox}
+}
+\AtBeginPart{
+ \frame{\partpage}
+}
+\AtBeginSection{
+ \ifbibliography
+ \else
+ \frame{\sectionpage}
+ \fi
+}
+\AtBeginSubsection{
+ \frame{\subsectionpage}
+}
+$endif$
+$endif$
+$if(beamerarticle)$
+\usepackage{beamerarticle} % needs to be loaded first
+$endif$
+\usepackage{amsmath,amssymb}
+$if(fontfamily)$
+\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$}
+$else$
+\usepackage{lmodern}
+$endif$
+$if(linestretch)$
+\usepackage{setspace}
+$else$
+\usepackage{setspace}
+\setstretch{1.2}
+$endif$
+\usepackage{ifxetex,ifluatex}
+\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
+ \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc}
+ \usepackage[utf8]{inputenc}
+ \usepackage{textcomp} % provide euro and other symbols
+\else % if luatex or xetex
+$if(mathspec)$
+ \ifxetex
+ \usepackage{mathspec}
+ \else
+ \usepackage{unicode-math}
+ \fi
+$else$
+ \usepackage{unicode-math}
+$endif$
+ \defaultfontfeatures{Scale=MatchLowercase}
+ \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
+$if(mainfont)$
+ \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$}
+$endif$
+$if(sansfont)$
+ \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$}
+$endif$
+$if(monofont)$
+ \setmonofont[$for(monofontoptions)$$monofontoptions$$sep$,$endfor$]{$monofont$}
+$endif$
+$for(fontfamilies)$
+ \newfontfamily{$fontfamilies.name$}[$for(fontfamilies.options)$$fontfamilies.options$$sep$,$endfor$]{$fontfamilies.font$}
+$endfor$
+$if(mathfont)$
+$if(mathspec)$
+ \ifxetex
+ \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$}
+ \else
+ \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$}
+ \fi
+$else$
+ \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$}
+$endif$
+$endif$
+$if(CJKmainfont)$
+ \ifxetex
+ \usepackage{xeCJK}
+ \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$}
+ \fi
+$endif$
+$if(luatexjapresetoptions)$
+ \ifluatex
+ \usepackage[$for(luatexjapresetoptions)$$luatexjapresetoptions$$sep$,$endfor$]{luatexja-preset}
+ \fi
+$endif$
+$if(CJKmainfont)$
+ \ifluatex
+ \usepackage[$for(luatexjafontspecoptions)$$luatexjafontspecoptions$$sep$,$endfor$]{luatexja-fontspec}
+ \setmainjfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$}
+ \fi
+$endif$
+\fi
+$if(beamer)$
+$if(theme)$
+\usetheme[$for(themeoptions)$$themeoptions$$sep$,$endfor$]{$theme$}
+$endif$
+$if(colortheme)$
+\usecolortheme{$colortheme$}
+$endif$
+$if(fonttheme)$
+\usefonttheme{$fonttheme$}
+$endif$
+$if(mainfont)$
+\usefonttheme{serif} % use mainfont rather than sansfont for slide text
+$endif$
+$if(innertheme)$
+\useinnertheme{$innertheme$}
+$endif$
+$if(outertheme)$
+\useoutertheme{$outertheme$}
+$endif$
+$endif$
+% Use upquote if available, for straight quotes in verbatim environments
+\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
+\IfFileExists{microtype.sty}{% use microtype if available
+ \usepackage[$for(microtypeoptions)$$microtypeoptions$$sep$,$endfor$]{microtype}
+ \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
+}{}
+$if(indent)$
+$else$
+\makeatletter
+\@ifundefined{KOMAClassName}{% if non-KOMA class
+ \IfFileExists{parskip.sty}{%
+ \usepackage{parskip}
+ }{% else
+ \setlength{\parindent}{0pt}
+ \setlength{\parskip}{6pt plus 2pt minus 1pt}}
+}{% if KOMA class
+ \KOMAoptions{parskip=half}}
+\makeatother
+$endif$
+$if(verbatim-in-note)$
+\usepackage{fancyvrb}
+$endif$
+\usepackage{xcolor}
+\definecolor{default-linkcolor}{HTML}{A50000}
+\definecolor{default-filecolor}{HTML}{A50000}
+\definecolor{default-citecolor}{HTML}{4077C0}
+\definecolor{default-urlcolor}{HTML}{4077C0}
+\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
+$if(footnotes-pretty)$
+% load footmisc in order to customize footnotes (footmisc has to be loaded before hyperref, cf. https://tex.stackexchange.com/a/169124/144087)
+\usepackage[hang,flushmargin,bottom,multiple]{footmisc}
+\setlength{\footnotemargin}{0.8em} % set space between footnote nr and text
+\setlength{\footnotesep}{\baselineskip} % set space between multiple footnotes
+\setlength{\skip\footins}{0.3cm} % set space between page content and footnote
+\setlength{\footskip}{0.9cm} % set space between footnote and page bottom
+$endif$
+\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}}
+\hypersetup{
+$if(title-meta)$
+ pdftitle={$title-meta$},
+$endif$
+$if(author-meta)$
+ pdfauthor={$author-meta$},
+$endif$
+$if(lang)$
+ pdflang={$lang$},
+$endif$
+$if(subject)$
+ pdfsubject={$subject$},
+$endif$
+$if(keywords)$
+ pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$},
+$endif$
+$if(colorlinks)$
+ colorlinks=true,
+ linkcolor=$if(linkcolor)$$linkcolor$$else$default-linkcolor$endif$,
+ filecolor=$if(filecolor)$$filecolor$$else$default-filecolor$endif$,
+ citecolor=$if(citecolor)$$citecolor$$else$default-citecolor$endif$,
+ urlcolor=$if(urlcolor)$$urlcolor$$else$default-urlcolor$endif$,
+$else$
+ hidelinks,
+$endif$
+ breaklinks=true,
+ pdfcreator={LaTeX via pandoc with the Eisvogel template}}
+\urlstyle{same} % disable monospaced font for URLs
+$if(verbatim-in-note)$
+\VerbatimFootnotes % allow verbatim text in footnotes
+$endif$
+$if(geometry)$
+$if(beamer)$
+\geometry{$for(geometry)$$geometry$$sep$,$endfor$}
+$else$
+\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry}
+$endif$
+$else$
+$if(beamer)$
+$else$
+\usepackage[margin=2cm,includehead=true,includefoot=true,centering,$for(geometry)$$geometry$$sep$,$endfor$]{geometry}
+$endif$
+$endif$
+$if(logo)$
+\usepackage[export]{adjustbox}
+\usepackage{graphicx}
+$endif$
+$if(beamer)$
+\newif\ifbibliography
+$endif$
+$if(listings)$
+\usepackage{listings}
+\newcommand{\passthrough}[1]{#1}
+\lstset{defaultdialect=[5.3]Lua}
+\lstset{defaultdialect=[x86masm]Assembler}
+$endif$
+$if(listings-no-page-break)$
+\usepackage{etoolbox}
+\BeforeBeginEnvironment{lstlisting}{\par\noindent\begin{minipage}{\linewidth}}
+\AfterEndEnvironment{lstlisting}{\end{minipage}\par\addvspace{\topskip}}
+$endif$
+$if(lhs)$
+\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{}
+$endif$
+$if(highlighting-macros)$
+$highlighting-macros$
+
+% Workaround/bugfix from jannick0.
+% See https://github.com/jgm/pandoc/issues/4302#issuecomment-360669013)
+% or https://github.com/Wandmalfarbe/pandoc-latex-template/issues/2
+%
+% Redefine the verbatim environment 'Highlighting' to break long lines (with
+% the help of fvextra). Redefinition is necessary because it is unlikely that
+% pandoc includes fvextra in the default template.
+\usepackage{fvextra}
+\DefineVerbatimEnvironment{Highlighting}{Verbatim}{breaklines,fontsize=$if(code-block-font-size)$$code-block-font-size$$else$\small$endif$,commandchars=\\\{\}}
+
+$endif$
+$if(tables)$
+\usepackage{longtable,booktabs,array}
+$if(multirow)$
+\usepackage{multirow}
+$endif$
+\usepackage{calc} % for calculating minipage widths
+$if(beamer)$
+\usepackage{caption}
+% Make caption package work with longtable
+\makeatletter
+\def\fnum@table{\tablename~\thetable}
+\makeatother
+$else$
+% Correct order of tables after \paragraph or \subparagraph
+\usepackage{etoolbox}
+\makeatletter
+\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
+\makeatother
+% Allow footnotes in longtable head/foot
+\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
+\makesavenoteenv{longtable}
+$endif$
+$endif$
+% add backlinks to footnote references, cf. https://tex.stackexchange.com/questions/302266/make-footnote-clickable-both-ways
+$if(footnotes-disable-backlinks)$
+$else$
+\usepackage{footnotebackref}
+$endif$
+$if(graphics)$
+\usepackage{graphicx}
+\makeatletter
+\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi}
+\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi}
+\makeatother
+% Scale images if necessary, so that they will not overflow the page
+% margins by default, and it is still possible to overwrite the defaults
+% using explicit options in \includegraphics[width, height, ...]{}
+\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio}
+% Set default figure placement to htbp
+\makeatletter
+\def\fps@figure{htbp}
+\makeatother
+$endif$
+$if(links-as-notes)$
+% Make links footnotes instead of hotlinks:
+\DeclareRobustCommand{\href}[2]{#2\footnote{\url{#1}}}
+$endif$
+$if(strikeout)$
+\usepackage[normalem]{ulem}
+% Avoid problems with \sout in headers with hyperref
+\pdfstringdefDisableCommands{\renewcommand{\sout}{}}
+$endif$
+\setlength{\emergencystretch}{3em} % prevent overfull lines
+\providecommand{\tightlist}{%
+ \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
+$if(numbersections)$
+\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$}
+$else$
+\setcounter{secnumdepth}{-\maxdimen} % remove section numbering
+$endif$
+$if(beamer)$
+$else$
+$if(block-headings)$
+% Make \paragraph and \subparagraph free-standing
+\ifx\paragraph\undefined\else
+ \let\oldparagraph\paragraph
+ \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}}
+\fi
+\ifx\subparagraph\undefined\else
+ \let\oldsubparagraph\subparagraph
+ \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}}
+\fi
+$endif$
+$endif$
+$if(pagestyle)$
+\pagestyle{$pagestyle$}
+$endif$
+
+% Make use of float-package and set default placement for figures to H.
+% The option H means 'PUT IT HERE' (as opposed to the standard h option which means 'You may put it here if you like').
+\usepackage{float}
+\floatplacement{figure}{$if(float-placement-figure)$$float-placement-figure$$else$H$endif$}
+
+$for(header-includes)$
+$header-includes$
+$endfor$
+$if(lang)$
+\ifxetex
+ $if(mainfont)$
+ $else$
+ % See issue https://github.com/reutenauer/polyglossia/issues/127
+ \renewcommand*\familydefault{\sfdefault}
+ $endif$
+ % Load polyglossia as late as possible: uses bidi with RTL langages (e.g. Hebrew, Arabic)
+ \usepackage{polyglossia}
+ \setmainlanguage[$for(polyglossia-lang.options)$$polyglossia-lang.options$$sep$,$endfor$]{$polyglossia-lang.name$}
+$for(polyglossia-otherlangs)$
+ \setotherlanguage[$for(polyglossia-otherlangs.options)$$polyglossia-otherlangs.options$$sep$,$endfor$]{$polyglossia-otherlangs.name$}
+$endfor$
+\else
+ \usepackage[$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel}
+% get rid of language-specific shorthands (see #6817):
+\let\LanguageShortHands\languageshorthands
+\def\languageshorthands#1{}
+$if(babel-newcommands)$
+ $babel-newcommands$
+$endif$
+\fi
+$endif$
+\ifluatex
+ \usepackage{selnolig} % disable illegal ligatures
+\fi
+$if(dir)$
+\ifxetex
+ % Load bidi as late as possible as it modifies e.g. graphicx
+ \usepackage{bidi}
+\fi
+\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
+ \TeXXeTstate=1
+ \newcommand{\RL}[1]{\beginR #1\endR}
+ \newcommand{\LR}[1]{\beginL #1\endL}
+ \newenvironment{RTL}{\beginR}{\endR}
+ \newenvironment{LTR}{\beginL}{\endL}
+\fi
+$endif$
+$if(natbib)$
+\usepackage[$natbiboptions$]{natbib}
+\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$}
+$endif$
+$if(biblatex)$
+\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex}
+$for(bibliography)$
+\addbibresource{$bibliography$}
+$endfor$
+$endif$
+$if(csl-refs)$
+\newlength{\cslhangindent}
+\setlength{\cslhangindent}{1.5em}
+\newlength{\csllabelwidth}
+\setlength{\csllabelwidth}{3em}
+\newenvironment{CSLReferences}[2] % #1 hanging-ident, #2 entry spacing
+ {% don't indent paragraphs
+ \setlength{\parindent}{0pt}
+ % turn on hanging indent if param 1 is 1
+ \ifodd #1 \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces\fi
+ % set entry spacing
+ \ifnum #2 > 0
+ \setlength{\parskip}{#2\baselineskip}
+ \fi
+ }%
+ {}
+\usepackage{calc}
+\newcommand{\CSLBlock}[1]{#1\hfill\break}
+\newcommand{\CSLLeftMargin}[1]{\parbox[t]{\csllabelwidth}{#1}}
+\newcommand{\CSLRightInline}[1]{\parbox[t]{\linewidth - \csllabelwidth}{#1}\break}
+\newcommand{\CSLIndent}[1]{\hspace{\cslhangindent}#1}
+$endif$
+$if(csquotes)$
+\usepackage{csquotes}
+$endif$
+
+$if(title)$
+\title{$title$$if(thanks)$\thanks{$thanks$}$endif$}
+$endif$
+$if(subtitle)$
+$if(beamer)$
+$else$
+\usepackage{etoolbox}
+\makeatletter
+\providecommand{\subtitle}[1]{% add subtitle to \maketitle
+ \apptocmd{\@title}{\par {\large #1 \par}}{}{}
+}
+\makeatother
+$endif$
+\subtitle{$subtitle$}
+$endif$
+\author{$for(author)$$author$$sep$ \and $endfor$}
+\date{$date$}
+$if(beamer)$
+$if(institute)$
+\institute{$for(institute)$$institute$$sep$ \and $endfor$}
+$endif$
+$if(titlegraphic)$
+\titlegraphic{\includegraphics{$titlegraphic$}}
+$endif$
+$if(logo)$
+\logo{\includegraphics{$logo$}}
+$endif$
+$endif$
+
+
+
+%%
+%% added
+%%
+
+%
+% language specification
+%
+% If no language is specified, use English as the default main document language.
+%
+$if(lang)$$else$
+\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
+ \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=english]{babel}
+$if(babel-newcommands)$
+ $babel-newcommands$
+$endif$
+\else
+ $if(mainfont)$
+ $else$
+ % Workaround for bug in Polyglossia that breaks `\familydefault` when `\setmainlanguage` is used.
+ % See https://github.com/Wandmalfarbe/pandoc-latex-template/issues/8
+ % See https://github.com/reutenauer/polyglossia/issues/186
+ % See https://github.com/reutenauer/polyglossia/issues/127
+ \renewcommand*\familydefault{\sfdefault}
+ $endif$
+ % load polyglossia as late as possible as it *could* call bidi if RTL lang (e.g. Hebrew or Arabic)
+ \usepackage{polyglossia}
+ \setmainlanguage[]{english}
+$for(polyglossia-otherlangs)$
+ \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$}
+$endfor$
+\fi
+$endif$
+
+$if(page-background)$
+\usepackage[pages=all]{background}
+$endif$
+
+%
+% for the background color of the title page
+%
+$if(titlepage)$
+\usepackage{pagecolor}
+\usepackage{afterpage}
+$if(titlepage-background)$
+\usepackage{tikz}
+$endif$
+$if(geometry)$
+$else$
+\usepackage[margin=2cm,includehead=true,includefoot=true,centering]{geometry}
+$endif$
+$endif$
+
+%
+% break urls
+%
+\PassOptionsToPackage{hyphens}{url}
+
+%
+% When using babel or polyglossia with biblatex, loading csquotes is recommended
+% to ensure that quoted texts are typeset according to the rules of your main language.
+%
+\usepackage{csquotes}
+
+%
+% captions
+%
+\definecolor{caption-color}{HTML}{777777}
+$if(beamer)$
+$else$
+\usepackage[font={stretch=1.2}, textfont={color=caption-color}, position=top, skip=4mm, labelfont=bf, singlelinecheck=false, justification=$if(caption-justification)$$caption-justification$$else$raggedright$endif$]{caption}
+\setcapindent{0em}
+$endif$
+
+%
+% blockquote
+%
+\definecolor{blockquote-border}{RGB}{221,221,221}
+\definecolor{blockquote-text}{RGB}{119,119,119}
+\usepackage{mdframed}
+\newmdenv[rightline=false,bottomline=false,topline=false,linewidth=3pt,linecolor=blockquote-border,skipabove=\parskip]{customblockquote}
+\renewenvironment{quote}{\begin{customblockquote}\list{}{\rightmargin=0em\leftmargin=0em}%
+\item\relax\color{blockquote-text}\ignorespaces}{\unskip\unskip\endlist\end{customblockquote}}
+
+%
+% Source Sans Pro as the default font family
+% Source Code Pro for monospace text
+%
+% 'default' option sets the default
+% font family to Source Sans Pro, not \sfdefault.
+%
+\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
+ $if(fontfamily)$
+ $else$
+ \usepackage[default]{sourcesanspro}
+ \usepackage{sourcecodepro}
+ $endif$
+\else % if not pdftex
+ $if(mainfont)$
+ $else$
+ \usepackage[default]{sourcesanspro}
+ \usepackage{sourcecodepro}
+
+ % XeLaTeX specific adjustments for straight quotes: https://tex.stackexchange.com/a/354887
+ % This issue is already fixed (see https://github.com/silkeh/latex-sourcecodepro/pull/5) but the
+ % fix is still unreleased.
+ % TODO: Remove this workaround when the new version of sourcecodepro is released on CTAN.
+ \ifxetex
+ \makeatletter
+ \defaultfontfeatures[\ttfamily]
+ { Numbers = \sourcecodepro@figurestyle,
+ Scale = \SourceCodePro@scale,
+ Extension = .otf }
+ \setmonofont
+ [ UprightFont = *-\sourcecodepro@regstyle,
+ ItalicFont = *-\sourcecodepro@regstyle It,
+ BoldFont = *-\sourcecodepro@boldstyle,
+ BoldItalicFont = *-\sourcecodepro@boldstyle It ]
+ {SourceCodePro}
+ \makeatother
+ \fi
+ $endif$
+\fi
+
+%
+% heading color
+%
+\definecolor{heading-color}{RGB}{40,40,40}
+$if(beamer)$
+$else$
+\addtokomafont{section}{\color{heading-color}}
+$endif$
+% When using the classes report, scrreprt, book,
+% scrbook or memoir, uncomment the following line.
+%\addtokomafont{chapter}{\color{heading-color}}
+
+%
+% variables for title, author and date
+%
+$if(beamer)$
+$else$
+\usepackage{titling}
+\title{$title$}
+\author{$for(author)$$author$$sep$, $endfor$}
+\date{$date$}
+$endif$
+
+%
+% tables
+%
+$if(tables)$
+
+\definecolor{table-row-color}{HTML}{F5F5F5}
+\definecolor{table-rule-color}{HTML}{999999}
+
+%\arrayrulecolor{black!40}
+\arrayrulecolor{table-rule-color} % color of \toprule, \midrule, \bottomrule
+\setlength\heavyrulewidth{0.3ex} % thickness of \toprule, \bottomrule
+\renewcommand{\arraystretch}{1.3} % spacing (padding)
+
+$if(table-use-row-colors)$
+% TODO: This doesn't work anymore. I don't know why.
+% Reset rownum counter so that each table
+% starts with the same row colors.
+% https://tex.stackexchange.com/questions/170637/restarting-rowcolors
+%
+% Unfortunately the colored cells extend beyond the edge of the
+% table because pandoc uses @-expressions (@{}) like so:
+%
+% \begin{longtable}[]{@{}ll@{}}
+% \end{longtable}
+%
+% https://en.wikibooks.org/wiki/LaTeX/Tables#.40-expressions
+\let\oldlongtable\longtable
+\let\endoldlongtable\endlongtable
+\renewenvironment{longtable}{
+\rowcolors{3}{}{table-row-color!100} % row color
+\oldlongtable} {
+\endoldlongtable
+\global\rownum=0\relax}
+$endif$
+$endif$
+
+%
+% remove paragraph indention
+%
+\setlength{\parindent}{0pt}
+\setlength{\parskip}{6pt plus 2pt minus 1pt}
+\setlength{\emergencystretch}{3em} % prevent overfull lines
+
+%
+%
+% Listings
+%
+%
+
+$if(listings)$
+
+%
+% general listing colors
+%
+\definecolor{listing-background}{HTML}{F7F7F7}
+\definecolor{listing-rule}{HTML}{B3B2B3}
+\definecolor{listing-numbers}{HTML}{B3B2B3}
+\definecolor{listing-text-color}{HTML}{000000}
+\definecolor{listing-keyword}{HTML}{ff0099}
+\definecolor{listing-keyword-2}{HTML}{1284CA} % additional keywords
+\definecolor{listing-keyword-3}{HTML}{9137CB} % additional keywords
+\definecolor{listing-identifier}{HTML}{ff0099}
+\definecolor{listing-string}{HTML}{00999A}
+\definecolor{listing-comment}{HTML}{8E8E8E}
+
+\lstdefinestyle{eisvogel_listing_style}{
+ language = java,
+$if(listings-disable-line-numbers)$
+ xleftmargin = 0.6em,
+ framexleftmargin = 0.4em,
+$else$
+ numbers = left,
+ xleftmargin = 2.7em,
+ framexleftmargin = 2.5em,
+$endif$
+ backgroundcolor = \color{listing-background},
+ basicstyle = \color{listing-text-color}\linespread{1.0}$if(code-block-font-size)$$code-block-font-size$$else$\small$endif$\ttfamily{},
+ breaklines = true,
+ frame = single,
+ framesep = 0.19em,
+ rulecolor = \color{listing-rule},
+ frameround = ffff,
+ tabsize = 4,
+ numberstyle = \color{listing-numbers},
+ aboveskip = 1.0em,
+ belowskip = 0.1em,
+ abovecaptionskip = 0em,
+ belowcaptionskip = 1.0em,
+ keywordstyle = {\color{listing-keyword}\bfseries},
+ keywordstyle = {[2]\color{listing-keyword-2}\bfseries},
+ keywordstyle = {[3]\color{listing-keyword-3}\bfseries\itshape},
+ sensitive = true,
+ identifierstyle = \color{listing-identifier},
+ commentstyle = \color{listing-comment},
+ stringstyle = \color{listing-string},
+ showstringspaces = false,
+ escapeinside = {/*@}{@*/}, % Allow LaTeX inside these special comments
+ literate =
+ {á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ú}{{\'u}}1
+ {Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ú}{{\'U}}1
+ {à}{{\`a}}1 {è}{{\'e}}1 {ì}{{\`i}}1 {ò}{{\`o}}1 {ù}{{\`u}}1
+ {À}{{\`A}}1 {È}{{\'E}}1 {Ì}{{\`I}}1 {Ò}{{\`O}}1 {Ù}{{\`U}}1
+ {ä}{{\"a}}1 {ë}{{\"e}}1 {ï}{{\"i}}1 {ö}{{\"o}}1 {ü}{{\"u}}1
+ {Ä}{{\"A}}1 {Ë}{{\"E}}1 {Ï}{{\"I}}1 {Ö}{{\"O}}1 {Ü}{{\"U}}1
+ {â}{{\^a}}1 {ê}{{\^e}}1 {î}{{\^i}}1 {ô}{{\^o}}1 {û}{{\^u}}1
+ {Â}{{\^A}}1 {Ê}{{\^E}}1 {Î}{{\^I}}1 {Ô}{{\^O}}1 {Û}{{\^U}}1
+ {œ}{{\oe}}1 {Œ}{{\OE}}1 {æ}{{\ae}}1 {Æ}{{\AE}}1 {ß}{{\ss}}1
+ {ç}{{\c c}}1 {Ç}{{\c C}}1 {ø}{{\o}}1 {å}{{\r a}}1 {Å}{{\r A}}1
+ {€}{{\EUR}}1 {£}{{\pounds}}1 {«}{{\guillemotleft}}1
+ {»}{{\guillemotright}}1 {ñ}{{\~n}}1 {Ñ}{{\~N}}1 {¿}{{?`}}1
+ {…}{{\ldots}}1 {≥}{{>=}}1 {≤}{{<=}}1 {„}{{\glqq}}1 {“}{{\grqq}}1
+ {”}{{''}}1
+}
+\lstset{style=eisvogel_listing_style}
+
+%
+% Java (Java SE 12, 2019-06-22)
+%
+\lstdefinelanguage{Java}{
+ morekeywords={
+ % normal keywords (without data types)
+ abstract,assert,break,case,catch,class,continue,default,
+ do,else,enum,exports,extends,final,finally,for,if,implements,
+ import,instanceof,interface,module,native,new,package,private,
+ protected,public,requires,return,static,strictfp,super,switch,
+ synchronized,this,throw,throws,transient,try,volatile,while,
+ % var is an identifier
+ var
+ },
+ morekeywords={[2] % data types
+ % primitive data types
+ boolean,byte,char,double,float,int,long,short,
+ % String
+ String,
+ % primitive wrapper types
+ Boolean,Byte,Character,Double,Float,Integer,Long,Short
+ % number types
+ Number,AtomicInteger,AtomicLong,BigDecimal,BigInteger,DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder,Short,
+ % other
+ Object,Void,void
+ },
+ morekeywords={[3] % literals
+ % reserved words for literal values
+ null,true,false,
+ },
+ sensitive,
+ morecomment = [l]//,
+ morecomment = [s]{/*}{*/},
+ morecomment = [s]{/**}{*/},
+ morestring = [b]",
+ morestring = [b]',
+}
+
+\lstdefinelanguage{XML}{
+ morestring = [b]",
+ moredelim = [s][\bfseries\color{listing-keyword}]{<}{\ },
+ moredelim = [s][\bfseries\color{listing-keyword}]{}{>},
+ moredelim = [l][\bfseries\color{listing-keyword}]{/>},
+ moredelim = [l][\bfseries\color{listing-keyword}]{>},
+ morecomment = [s]{}{?>},
+ morecomment = [s]{},
+ commentstyle = \color{listing-comment},
+ stringstyle = \color{listing-string},
+ identifierstyle = \color{listing-identifier}
+}
+$endif$
+
+%
+% header and footer
+%
+$if(beamer)$
+$else$
+$if(disable-header-and-footer)$
+$else$
+\usepackage{fancyhdr}
+
+\fancypagestyle{eisvogel-header-footer}{
+ \fancyhead{}
+ \fancyfoot{}
+ \lhead[$if(header-right)$$header-right$$else$$date$$endif$]{$if(header-left)$ \textbf{$header-left$}$else$$title$$endif$}
+ \chead[$if(header-center)$$header-center$$else$$endif$]{$if(header-center)$$header-center$$else$$endif$}
+ \rhead[$if(header-left)$$header-left$$else$$title$$endif$]{$if(header-right)$$header-right$$else$$date$$endif$}
+ \lfoot[$if(footer-right)$$footer-right$$else$\thepage$endif$]{$if(footer-left)$$footer-left$$else$$for(author)$$author$$sep$, $endfor$$endif$}
+ \cfoot[$if(footer-center)$$footer-center$$else$$endif$]{$if(footer-center)$$footer-center$$else$$endif$}
+ \rfoot[$if(footer-left)$$footer-left$$else$$for(author)$$author$$sep$, $endfor$$endif$]{$if(footer-right)$$footer-right$$else$\thepage $endif$}
+ \renewcommand{\headrulewidth}{0.5pt}
+ \renewcommand{\footrulewidth}{0.5pt}
+}
+\pagestyle{eisvogel-header-footer}
+$if(page-background)$
+\backgroundsetup{
+scale=1,
+color=black,
+opacity=$if(page-background-opacity)$$page-background-opacity$$else$0.2$endif$,
+angle=0,
+contents={%
+ \includegraphics[width=\paperwidth,height=\paperheight]{$page-background$}
+ }%
+}
+$endif$
+$endif$
+$endif$
+
+%%
+%% end added
+%%
+
+\begin{document}
+
+%%
+%% begin titlepage
+%%
+$if(beamer)$
+$else$
+$if(titlepage)$
+\begin{titlepage}
+$if(titlepage-background)$
+\newgeometry{top=2cm, right=4cm, bottom=3cm, left=4cm}
+$else$
+\newgeometry{left=6cm}
+$endif$
+$if(titlepage-color)$
+\definecolor{titlepage-color}{HTML}{$titlepage-color$}
+\newpagecolor{titlepage-color}\afterpage{\restorepagecolor}
+$endif$
+$if(titlepage-background)$
+\tikz[remember picture,overlay] \node[inner sep=0pt] at (current page.center){\includegraphics[width=\paperwidth,height=\paperheight]{$titlepage-background$}};
+$endif$
+\newcommand{\colorRule}[3][black]{\textcolor[HTML]{#1}{\rule{#2}{#3}}}
+\begin{flushleft}
+\noindent
+\\[-1em]
+\color[HTML]{$if(titlepage-text-color)$$titlepage-text-color$$else$5F5F5F$endif$}
+\makebox[0pt][l]{\colorRule[$if(titlepage-rule-color)$$titlepage-rule-color$$else$435488$endif$]{1.3\textwidth}{$if(titlepage-rule-height)$$titlepage-rule-height$$else$4$endif$pt}}
+\par
+\noindent
+ \includegraphics[width=$if(logo-width)$$logo-width$$else$35mm$endif$, left]{$logo$}
+
+$if(titlepage-background)$
+% The titlepage with a background image has other text spacing and text size
+{
+ \setstretch{2}
+ \vfill
+ \vskip -8em
+ \noindent {\huge \textbf{\textsf{$title$}}}
+ $if(subtitle)$
+ \vskip 1em
+ {\Large \textsf{$subtitle$}}
+ $endif$
+ \vskip 2em
+ \noindent {\Large \textsf{$for(author)$$author$$sep$, $endfor$} \vskip 0.6em \textsf{$date$}}
+ \vfill
+}
+$else$
+{
+ \setstretch{1.4}
+ \vfill
+ \noindent {\huge \textbf{\textsf{$title$}}}
+ $if(subtitle)$
+ \vskip 1em
+ {\Large \textsf{$subtitle$}}
+ $endif$
+ \vskip 2em
+ \noindent {\Large \textsf{$for(author)$$author$$sep$, $endfor$}}
+ \vfill
+}
+$endif$
+
+% original logo position
+% $if(logo)$
+% \noindent
+% \includegraphics[width=$if(logo-width)$$logo-width$$else$35mm$endif$, left]{$logo$}
+% $endif$
+
+$if(titlepage-background)$
+$else$
+\textsf{$date$}
+$endif$
+\end{flushleft}
+\end{titlepage}
+\restoregeometry
+$endif$
+$endif$
+
+%%
+%% end titlepage
+%%
+
+$if(has-frontmatter)$
+\frontmatter
+$endif$
+$if(title)$
+$if(beamer)$
+\frame{\titlepage}
+$endif$
+$if(abstract)$
+\begin{abstract}
+$abstract$
+\end{abstract}
+$endif$
+$endif$
+
+$if(first-chapter)$
+\setcounter{chapter}{$first-chapter$}
+\addtocounter{chapter}{-1}
+$endif$
+
+$for(include-before)$
+$include-before$
+
+$endfor$
+$if(toc)$
+$if(toc-title)$
+\renewcommand*\contentsname{$toc-title$}
+$endif$
+$if(beamer)$
+\begin{frame}[allowframebreaks]
+$if(toc-title)$
+ \frametitle{$toc-title$}
+$endif$
+ \tableofcontents[hideallsubsections]
+\end{frame}
+$if(toc-own-page)$
+\newpage
+$endif$
+$else$
+{
+$if(colorlinks)$
+\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$$endif$}
+$endif$
+\setcounter{tocdepth}{$toc-depth$}
+\tableofcontents
+$if(toc-own-page)$
+\newpage
+$endif$
+}
+$endif$
+$endif$
+$if(lot)$
+\listoftables
+$endif$
+$if(lof)$
+\listoffigures
+$endif$
+$if(linestretch)$
+\setstretch{$linestretch$}
+$endif$
+$if(has-frontmatter)$
+\mainmatter
+$endif$
+$body$
+
+$if(has-frontmatter)$
+\backmatter
+$endif$
+$if(natbib)$
+$if(bibliography)$
+$if(biblio-title)$
+$if(has-chapters)$
+\renewcommand\bibname{$biblio-title$}
+$else$
+\renewcommand\refname{$biblio-title$}
+$endif$
+$endif$
+$if(beamer)$
+\begin{frame}[allowframebreaks]{$biblio-title$}
+ \bibliographytrue
+$endif$
+ \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$}
+$if(beamer)$
+\end{frame}
+$endif$
+
+$endif$
+$endif$
+$if(biblatex)$
+$if(beamer)$
+\begin{frame}[allowframebreaks]{$biblio-title$}
+ \bibliographytrue
+ \printbibliography[heading=none]
+\end{frame}
+$else$
+\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$
+$endif$
+
+$endif$
+$for(include-after)$
+$include-after$
+
+$endfor$
+\end{document}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..01a16c4
--- /dev/null
+++ b/index.html
@@ -0,0 +1,29 @@
+
+
+
Pandoc PDF Builder
+
+
Upload a source document in Markdown (.md) format plus any images to be included.
+
This app will process them using Pandoc (https://pandoc.com) with a tweaked eisvogel template and awesomebox.sty to create a beautiful PDF, without the hassle of manual formatting in Word etc.
+
THIS IS IN ALPHA STATUS : STILL IN DEVELOPMENT.
+
+
+
Click here to upload (1) a .md Markdown file (2) any included images
+
+
+
+
Background
+
This app is a custom (i.e. not officially developed or maintained) app packaged for Cloudron.
+
It packages an instance of the excellent document conversion utility Pandoc.
+
Pandoc only runs from the command line. By packaging it into a web app, a consistent publishing environment can be maintained in a single location (avoiding multiple local installs which might change over time) and be accessible from any device, even without Pandoc and its utilities installed.
+
It is based on Cloudron's tutorial-supervisor app and the "pandoc docker" app from Viktor Petersson.
+
Once the source Markdown and related images files are uploaded, click the button to process them.
+
The app will create a PDF, which must be downloaded to the local computer, as it cannot be retrieved later.
+
The source Markdown file will be moved to /app/data/done (in future version, it will be deleted).
+
Image files will not be moved, in case they are required for subsequent PDF creations, e.g.on updating and re-uploading the source Markdown file.
+
NB : currently source image files might be overwritten by other users of the app uploading a file of the same name but different content. Your document will then be. reated with the wrong image. If so, re-upload them and retry. This issue will be addressed in a future version.
+
+
+
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
new file mode 100644
index 0000000..8225f3b
--- /dev/null
+++ b/node_modules/.package-lock.json
@@ -0,0 +1,128 @@
+{
+ "name": "pandoc-cloudron",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.1.0.tgz",
+ "integrity": "sha512-R5QZrOXxSs0JDUIU/VANvRJlQVMts9C0L76HToQdPdlftfZCE7W6dyH0G4GZ5UW9fRqUOhAoCE2aGekuu+3HjQ==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+ "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "node_modules/formidable": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
+ "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
+ "dependencies": {
+ "dezalgo": "1.0.3",
+ "hexoid": "1.0.0",
+ "once": "1.4.0",
+ "qs": "6.9.3"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
+ }
+ },
+ "node_modules/fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
+ },
+ "node_modules/hexoid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
+ "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.9.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
+ "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ }
+ }
+}
diff --git a/node_modules/asap/CHANGES.md b/node_modules/asap/CHANGES.md
new file mode 100644
index 0000000..f105b91
--- /dev/null
+++ b/node_modules/asap/CHANGES.md
@@ -0,0 +1,70 @@
+
+## 2.0.6
+
+Version 2.0.4 adds support for React Native by clarifying in package.json that
+the browser environment does not support Node.js domains.
+Why this is necessary, we leave as an exercise for the user.
+
+## 2.0.3
+
+Version 2.0.3 fixes a bug when adjusting the capacity of the task queue.
+
+## 2.0.1-2.02
+
+Version 2.0.1 fixes a bug in the way redirects were expressed that affected the
+function of Browserify, but which Mr would tolerate.
+
+## 2.0.0
+
+Version 2 of ASAP is a full rewrite with a few salient changes.
+First, the ASAP source is CommonJS only and designed with [Browserify][] and
+[Browserify-compatible][Mr] module loaders in mind.
+
+[Browserify]: https://github.com/substack/node-browserify
+[Mr]: https://github.com/montagejs/mr
+
+The new version has been refactored in two dimensions.
+Support for Node.js and browsers have been separated, using Browserify
+redirects and ASAP has been divided into two modules.
+The "raw" layer depends on the tasks to catch thrown exceptions and unravel
+Node.js domains.
+
+The full implementation of ASAP is loadable as `require("asap")` in both Node.js
+and browsers.
+
+The raw layer that lacks exception handling overhead is loadable as
+`require("asap/raw")`.
+The interface is the same for both layers.
+
+Tasks are no longer required to be functions, but can rather be any object that
+implements `task.call()`.
+With this feature you can recycle task objects to avoid garbage collector churn
+and avoid closures in general.
+
+The implementation has been rigorously documented so that our successors can
+understand the scope of the problem that this module solves and all of its
+nuances, ensuring that the next generation of implementations know what details
+are essential.
+
+- [asap.js](https://github.com/kriskowal/asap/blob/master/asap.js)
+- [raw.js](https://github.com/kriskowal/asap/blob/master/raw.js)
+- [browser-asap.js](https://github.com/kriskowal/asap/blob/master/browser-asap.js)
+- [browser-raw.js](https://github.com/kriskowal/asap/blob/master/browser-raw.js)
+
+The new version has also been rigorously tested across a broad spectrum of
+browsers, in both the window and worker context.
+The following charts capture the browser test results for the most recent
+release.
+The first chart shows test results for ASAP running in the main window context.
+The second chart shows test results for ASAP running in a web worker context.
+Test results are inconclusive (grey) on browsers that do not support web
+workers.
+These data are captured automatically by [Continuous
+Integration][].
+
+
+
+
+
+[Continuous Integration]: https://github.com/kriskowal/asap/blob/master/CONTRIBUTING.md
+
diff --git a/node_modules/asap/LICENSE.md b/node_modules/asap/LICENSE.md
new file mode 100644
index 0000000..ba18c61
--- /dev/null
+++ b/node_modules/asap/LICENSE.md
@@ -0,0 +1,21 @@
+
+Copyright 2009–2014 Contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
diff --git a/node_modules/asap/README.md b/node_modules/asap/README.md
new file mode 100644
index 0000000..452fd8c
--- /dev/null
+++ b/node_modules/asap/README.md
@@ -0,0 +1,237 @@
+# ASAP
+
+[](https://travis-ci.org/kriskowal/asap)
+
+Promise and asynchronous observer libraries, as well as hand-rolled callback
+programs and libraries, often need a mechanism to postpone the execution of a
+callback until the next available event.
+(See [Designing API’s for Asynchrony][Zalgo].)
+The `asap` function executes a task **as soon as possible** but not before it
+returns, waiting only for the completion of the current event and previously
+scheduled tasks.
+
+```javascript
+asap(function () {
+ // ...
+});
+```
+
+[Zalgo]: http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
+
+This CommonJS package provides an `asap` module that exports a function that
+executes a task function *as soon as possible*.
+
+ASAP strives to schedule events to occur before yielding for IO, reflow,
+or redrawing.
+Each event receives an independent stack, with only platform code in parent
+frames and the events run in the order they are scheduled.
+
+ASAP provides a fast event queue that will execute tasks until it is
+empty before yielding to the JavaScript engine's underlying event-loop.
+When a task gets added to a previously empty event queue, ASAP schedules a flush
+event, preferring for that event to occur before the JavaScript engine has an
+opportunity to perform IO tasks or rendering, thus making the first task and
+subsequent tasks semantically indistinguishable.
+ASAP uses a variety of techniques to preserve this invariant on different
+versions of browsers and Node.js.
+
+By design, ASAP prevents input events from being handled until the task
+queue is empty.
+If the process is busy enough, this may cause incoming connection requests to be
+dropped, and may cause existing connections to inform the sender to reduce the
+transmission rate or stall.
+ASAP allows this on the theory that, if there is enough work to do, there is no
+sense in looking for trouble.
+As a consequence, ASAP can interfere with smooth animation.
+If your task should be tied to the rendering loop, consider using
+`requestAnimationFrame` instead.
+A long sequence of tasks can also effect the long running script dialog.
+If this is a problem, you may be able to use ASAP’s cousin `setImmediate` to
+break long processes into shorter intervals and periodically allow the browser
+to breathe.
+`setImmediate` will yield for IO, reflow, and repaint events.
+It also returns a handler and can be canceled.
+For a `setImmediate` shim, consider [YuzuJS setImmediate][setImmediate].
+
+[setImmediate]: https://github.com/YuzuJS/setImmediate
+
+Take care.
+ASAP can sustain infinite recursive calls without warning.
+It will not halt from a stack overflow, and it will not consume unbounded
+memory.
+This is behaviorally equivalent to an infinite loop.
+Just as with infinite loops, you can monitor a Node.js process for this behavior
+with a heart-beat signal.
+As with infinite loops, a very small amount of caution goes a long way to
+avoiding problems.
+
+```javascript
+function loop() {
+ asap(loop);
+}
+loop();
+```
+
+In browsers, if a task throws an exception, it will not interrupt the flushing
+of high-priority tasks.
+The exception will be postponed to a later, low-priority event to avoid
+slow-downs.
+In Node.js, if a task throws an exception, ASAP will resume flushing only if—and
+only after—the error is handled by `domain.on("error")` or
+`process.on("uncaughtException")`.
+
+## Raw ASAP
+
+Checking for exceptions comes at a cost.
+The package also provides an `asap/raw` module that exports the underlying
+implementation which is faster but stalls if a task throws an exception.
+This internal version of the ASAP function does not check for errors.
+If a task does throw an error, it will stall the event queue unless you manually
+call `rawAsap.requestFlush()` before throwing the error, or any time after.
+
+In Node.js, `asap/raw` also runs all tasks outside any domain.
+If you need a task to be bound to your domain, you will have to do it manually.
+
+```js
+if (process.domain) {
+ task = process.domain.bind(task);
+}
+rawAsap(task);
+```
+
+## Tasks
+
+A task may be any object that implements `call()`.
+A function will suffice, but closures tend not to be reusable and can cause
+garbage collector churn.
+Both `asap` and `rawAsap` accept task objects to give you the option of
+recycling task objects or using higher callable object abstractions.
+See the `asap` source for an illustration.
+
+
+## Compatibility
+
+ASAP is tested on Node.js v0.10 and in a broad spectrum of web browsers.
+The following charts capture the browser test results for the most recent
+release.
+The first chart shows test results for ASAP running in the main window context.
+The second chart shows test results for ASAP running in a web worker context.
+Test results are inconclusive (grey) on browsers that do not support web
+workers.
+These data are captured automatically by [Continuous
+Integration][].
+
+[Continuous Integration]: https://github.com/kriskowal/asap/blob/master/CONTRIBUTING.md
+
+
+
+
+
+## Caveats
+
+When a task is added to an empty event queue, it is not always possible to
+guarantee that the task queue will begin flushing immediately after the current
+event.
+However, once the task queue begins flushing, it will not yield until the queue
+is empty, even if the queue grows while executing tasks.
+
+The following browsers allow the use of [DOM mutation observers][] to access
+the HTML [microtask queue][], and thus begin flushing ASAP's task queue
+immediately at the end of the current event loop turn, before any rendering or
+IO:
+
+[microtask queue]: http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#microtask-queue
+[DOM mutation observers]: http://dom.spec.whatwg.org/#mutation-observers
+
+- Android 4–4.3
+- Chrome 26–34
+- Firefox 14–29
+- Internet Explorer 11
+- iPad Safari 6–7.1
+- iPhone Safari 7–7.1
+- Safari 6–7
+
+In the absense of mutation observers, there are a few browsers, and situations
+like web workers in some of the above browsers, where [message channels][]
+would be a useful way to avoid falling back to timers.
+Message channels give direct access to the HTML [task queue][], so the ASAP
+task queue would flush after any already queued rendering and IO tasks, but
+without having the minimum delay imposed by timers.
+However, among these browsers, Internet Explorer 10 and Safari do not reliably
+dispatch messages, so they are not worth the trouble to implement.
+
+[message channels]: http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#message-channels
+[task queue]: http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#concept-task
+
+- Internet Explorer 10
+- Safair 5.0-1
+- Opera 11-12
+
+In the absense of mutation observers, these browsers and the following browsers
+all fall back to using `setTimeout` and `setInterval` to ensure that a `flush`
+occurs.
+The implementation uses both and cancels whatever handler loses the race, since
+`setTimeout` tends to occasionally skip tasks in unisolated circumstances.
+Timers generally delay the flushing of ASAP's task queue for four milliseconds.
+
+- Firefox 3–13
+- Internet Explorer 6–10
+- iPad Safari 4.3
+- Lynx 2.8.7
+
+
+## Heritage
+
+ASAP has been factored out of the [Q][] asynchronous promise library.
+It originally had a naïve implementation in terms of `setTimeout`, but
+[Malte Ubl][NonBlocking] provided an insight that `postMessage` might be
+useful for creating a high-priority, no-delay event dispatch hack.
+Since then, Internet Explorer proposed and implemented `setImmediate`.
+Robert Katić began contributing to Q by measuring the performance of
+the internal implementation of `asap`, paying particular attention to
+error recovery.
+Domenic, Robert, and Kris Kowal collectively settled on the current strategy of
+unrolling the high-priority event queue internally regardless of what strategy
+we used to dispatch the potentially lower-priority flush event.
+Domenic went on to make ASAP cooperate with Node.js domains.
+
+[Q]: https://github.com/kriskowal/q
+[NonBlocking]: http://www.nonblocking.io/2011/06/windownexttick.html
+
+For further reading, Nicholas Zakas provided a thorough article on [The
+Case for setImmediate][NCZ].
+
+[NCZ]: http://www.nczonline.net/blog/2013/07/09/the-case-for-setimmediate/
+
+Ember’s RSVP promise implementation later [adopted][RSVP ASAP] the name ASAP but
+further developed the implentation.
+Particularly, The `MessagePort` implementation was abandoned due to interaction
+[problems with Mobile Internet Explorer][IE Problems] in favor of an
+implementation backed on the newer and more reliable DOM `MutationObserver`
+interface.
+These changes were back-ported into this library.
+
+[IE Problems]: https://github.com/cujojs/when/issues/197
+[RSVP ASAP]: https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js
+
+In addition, ASAP factored into `asap` and `asap/raw`, such that `asap` remained
+exception-safe, but `asap/raw` provided a tight kernel that could be used for
+tasks that guaranteed that they would not throw exceptions.
+This core is useful for promise implementations that capture thrown errors in
+rejected promises and do not need a second safety net.
+At the same time, the exception handling in `asap` was factored into separate
+implementations for Node.js and browsers, using the the [Browserify][Browser
+Config] `browser` property in `package.json` to instruct browser module loaders
+and bundlers, including [Browserify][], [Mr][], and [Mop][], to use the
+browser-only implementation.
+
+[Browser Config]: https://gist.github.com/defunctzombie/4339901
+[Browserify]: https://github.com/substack/node-browserify
+[Mr]: https://github.com/montagejs/mr
+[Mop]: https://github.com/montagejs/mop
+
+## License
+
+Copyright 2009-2014 by Contributors
+MIT License (enclosed)
+
diff --git a/node_modules/asap/asap.js b/node_modules/asap/asap.js
new file mode 100644
index 0000000..f04fcd5
--- /dev/null
+++ b/node_modules/asap/asap.js
@@ -0,0 +1,65 @@
+"use strict";
+
+var rawAsap = require("./raw");
+var freeTasks = [];
+
+/**
+ * Calls a task as soon as possible after returning, in its own event, with
+ * priority over IO events. An exception thrown in a task can be handled by
+ * `process.on("uncaughtException") or `domain.on("error")`, but will otherwise
+ * crash the process. If the error is handled, all subsequent tasks will
+ * resume.
+ *
+ * @param {{call}} task A callable object, typically a function that takes no
+ * arguments.
+ */
+module.exports = asap;
+function asap(task) {
+ var rawTask;
+ if (freeTasks.length) {
+ rawTask = freeTasks.pop();
+ } else {
+ rawTask = new RawTask();
+ }
+ rawTask.task = task;
+ rawTask.domain = process.domain;
+ rawAsap(rawTask);
+}
+
+function RawTask() {
+ this.task = null;
+ this.domain = null;
+}
+
+RawTask.prototype.call = function () {
+ if (this.domain) {
+ this.domain.enter();
+ }
+ var threw = true;
+ try {
+ this.task.call();
+ threw = false;
+ // If the task throws an exception (presumably) Node.js restores the
+ // domain stack for the next event.
+ if (this.domain) {
+ this.domain.exit();
+ }
+ } finally {
+ // We use try/finally and a threw flag to avoid messing up stack traces
+ // when we catch and release errors.
+ if (threw) {
+ // In Node.js, uncaught exceptions are considered fatal errors.
+ // Re-throw them to interrupt flushing!
+ // Ensure that flushing continues if an uncaught exception is
+ // suppressed listening process.on("uncaughtException") or
+ // domain.on("error").
+ rawAsap.requestFlush();
+ }
+ // If the task threw an error, we do not want to exit the domain here.
+ // Exiting the domain would prevent the domain from catching the error.
+ this.task = null;
+ this.domain = null;
+ freeTasks.push(this);
+ }
+};
+
diff --git a/node_modules/asap/browser-asap.js b/node_modules/asap/browser-asap.js
new file mode 100644
index 0000000..805c982
--- /dev/null
+++ b/node_modules/asap/browser-asap.js
@@ -0,0 +1,66 @@
+"use strict";
+
+// rawAsap provides everything we need except exception management.
+var rawAsap = require("./raw");
+// RawTasks are recycled to reduce GC churn.
+var freeTasks = [];
+// We queue errors to ensure they are thrown in right order (FIFO).
+// Array-as-queue is good enough here, since we are just dealing with exceptions.
+var pendingErrors = [];
+var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError);
+
+function throwFirstError() {
+ if (pendingErrors.length) {
+ throw pendingErrors.shift();
+ }
+}
+
+/**
+ * Calls a task as soon as possible after returning, in its own event, with priority
+ * over other events like animation, reflow, and repaint. An error thrown from an
+ * event will not interrupt, nor even substantially slow down the processing of
+ * other events, but will be rather postponed to a lower priority event.
+ * @param {{call}} task A callable object, typically a function that takes no
+ * arguments.
+ */
+module.exports = asap;
+function asap(task) {
+ var rawTask;
+ if (freeTasks.length) {
+ rawTask = freeTasks.pop();
+ } else {
+ rawTask = new RawTask();
+ }
+ rawTask.task = task;
+ rawAsap(rawTask);
+}
+
+// We wrap tasks with recyclable task objects. A task object implements
+// `call`, just like a function.
+function RawTask() {
+ this.task = null;
+}
+
+// The sole purpose of wrapping the task is to catch the exception and recycle
+// the task object after its single use.
+RawTask.prototype.call = function () {
+ try {
+ this.task.call();
+ } catch (error) {
+ if (asap.onerror) {
+ // This hook exists purely for testing purposes.
+ // Its name will be periodically randomized to break any code that
+ // depends on its existence.
+ asap.onerror(error);
+ } else {
+ // In a web browser, exceptions are not fatal. However, to avoid
+ // slowing down the queue of pending tasks, we rethrow the error in a
+ // lower priority turn.
+ pendingErrors.push(error);
+ requestErrorThrow();
+ }
+ } finally {
+ this.task = null;
+ freeTasks[freeTasks.length] = this;
+ }
+};
diff --git a/node_modules/asap/browser-raw.js b/node_modules/asap/browser-raw.js
new file mode 100644
index 0000000..9cee7e3
--- /dev/null
+++ b/node_modules/asap/browser-raw.js
@@ -0,0 +1,223 @@
+"use strict";
+
+// Use the fastest means possible to execute a task in its own turn, with
+// priority over other events including IO, animation, reflow, and redraw
+// events in browsers.
+//
+// An exception thrown by a task will permanently interrupt the processing of
+// subsequent tasks. The higher level `asap` function ensures that if an
+// exception is thrown by a task, that the task queue will continue flushing as
+// soon as possible, but if you use `rawAsap` directly, you are responsible to
+// either ensure that no exceptions are thrown from your task, or to manually
+// call `rawAsap.requestFlush` if an exception is thrown.
+module.exports = rawAsap;
+function rawAsap(task) {
+ if (!queue.length) {
+ requestFlush();
+ flushing = true;
+ }
+ // Equivalent to push, but avoids a function call.
+ queue[queue.length] = task;
+}
+
+var queue = [];
+// Once a flush has been requested, no further calls to `requestFlush` are
+// necessary until the next `flush` completes.
+var flushing = false;
+// `requestFlush` is an implementation-specific method that attempts to kick
+// off a `flush` event as quickly as possible. `flush` will attempt to exhaust
+// the event queue before yielding to the browser's own event loop.
+var requestFlush;
+// The position of the next task to execute in the task queue. This is
+// preserved between calls to `flush` so that it can be resumed if
+// a task throws an exception.
+var index = 0;
+// If a task schedules additional tasks recursively, the task queue can grow
+// unbounded. To prevent memory exhaustion, the task queue will periodically
+// truncate already-completed tasks.
+var capacity = 1024;
+
+// The flush function processes all tasks that have been scheduled with
+// `rawAsap` unless and until one of those tasks throws an exception.
+// If a task throws an exception, `flush` ensures that its state will remain
+// consistent and will resume where it left off when called again.
+// However, `flush` does not make any arrangements to be called again if an
+// exception is thrown.
+function flush() {
+ while (index < queue.length) {
+ var currentIndex = index;
+ // Advance the index before calling the task. This ensures that we will
+ // begin flushing on the next task the task throws an error.
+ index = index + 1;
+ queue[currentIndex].call();
+ // Prevent leaking memory for long chains of recursive calls to `asap`.
+ // If we call `asap` within tasks scheduled by `asap`, the queue will
+ // grow, but to avoid an O(n) walk for every task we execute, we don't
+ // shift tasks off the queue after they have been executed.
+ // Instead, we periodically shift 1024 tasks off the queue.
+ if (index > capacity) {
+ // Manually shift all values starting at the index back to the
+ // beginning of the queue.
+ for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
+ queue[scan] = queue[scan + index];
+ }
+ queue.length -= index;
+ index = 0;
+ }
+ }
+ queue.length = 0;
+ index = 0;
+ flushing = false;
+}
+
+// `requestFlush` is implemented using a strategy based on data collected from
+// every available SauceLabs Selenium web driver worker at time of writing.
+// https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593
+
+// Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that
+// have WebKitMutationObserver but not un-prefixed MutationObserver.
+// Must use `global` or `self` instead of `window` to work in both frames and web
+// workers. `global` is a provision of Browserify, Mr, Mrs, or Mop.
+
+/* globals self */
+var scope = typeof global !== "undefined" ? global : self;
+var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
+
+// MutationObservers are desirable because they have high priority and work
+// reliably everywhere they are implemented.
+// They are implemented in all modern browsers.
+//
+// - Android 4-4.3
+// - Chrome 26-34
+// - Firefox 14-29
+// - Internet Explorer 11
+// - iPad Safari 6-7.1
+// - iPhone Safari 7-7.1
+// - Safari 6-7
+if (typeof BrowserMutationObserver === "function") {
+ requestFlush = makeRequestCallFromMutationObserver(flush);
+
+// MessageChannels are desirable because they give direct access to the HTML
+// task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera
+// 11-12, and in web workers in many engines.
+// Although message channels yield to any queued rendering and IO tasks, they
+// would be better than imposing the 4ms delay of timers.
+// However, they do not work reliably in Internet Explorer or Safari.
+
+// Internet Explorer 10 is the only browser that has setImmediate but does
+// not have MutationObservers.
+// Although setImmediate yields to the browser's renderer, it would be
+// preferrable to falling back to setTimeout since it does not have
+// the minimum 4ms penalty.
+// Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and
+// Desktop to a lesser extent) that renders both setImmediate and
+// MessageChannel useless for the purposes of ASAP.
+// https://github.com/kriskowal/q/issues/396
+
+// Timers are implemented universally.
+// We fall back to timers in workers in most engines, and in foreground
+// contexts in the following browsers.
+// However, note that even this simple case requires nuances to operate in a
+// broad spectrum of browsers.
+//
+// - Firefox 3-13
+// - Internet Explorer 6-9
+// - iPad Safari 4.3
+// - Lynx 2.8.7
+} else {
+ requestFlush = makeRequestCallFromTimer(flush);
+}
+
+// `requestFlush` requests that the high priority event queue be flushed as
+// soon as possible.
+// This is useful to prevent an error thrown in a task from stalling the event
+// queue if the exception handled by Node.js’s
+// `process.on("uncaughtException")` or by a domain.
+rawAsap.requestFlush = requestFlush;
+
+// To request a high priority event, we induce a mutation observer by toggling
+// the text of a text node between "1" and "-1".
+function makeRequestCallFromMutationObserver(callback) {
+ var toggle = 1;
+ var observer = new BrowserMutationObserver(callback);
+ var node = document.createTextNode("");
+ observer.observe(node, {characterData: true});
+ return function requestCall() {
+ toggle = -toggle;
+ node.data = toggle;
+ };
+}
+
+// The message channel technique was discovered by Malte Ubl and was the
+// original foundation for this library.
+// http://www.nonblocking.io/2011/06/windownexttick.html
+
+// Safari 6.0.5 (at least) intermittently fails to create message ports on a
+// page's first load. Thankfully, this version of Safari supports
+// MutationObservers, so we don't need to fall back in that case.
+
+// function makeRequestCallFromMessageChannel(callback) {
+// var channel = new MessageChannel();
+// channel.port1.onmessage = callback;
+// return function requestCall() {
+// channel.port2.postMessage(0);
+// };
+// }
+
+// For reasons explained above, we are also unable to use `setImmediate`
+// under any circumstances.
+// Even if we were, there is another bug in Internet Explorer 10.
+// It is not sufficient to assign `setImmediate` to `requestFlush` because
+// `setImmediate` must be called *by name* and therefore must be wrapped in a
+// closure.
+// Never forget.
+
+// function makeRequestCallFromSetImmediate(callback) {
+// return function requestCall() {
+// setImmediate(callback);
+// };
+// }
+
+// Safari 6.0 has a problem where timers will get lost while the user is
+// scrolling. This problem does not impact ASAP because Safari 6.0 supports
+// mutation observers, so that implementation is used instead.
+// However, if we ever elect to use timers in Safari, the prevalent work-around
+// is to add a scroll event listener that calls for a flush.
+
+// `setTimeout` does not call the passed callback if the delay is less than
+// approximately 7 in web workers in Firefox 8 through 18, and sometimes not
+// even then.
+
+function makeRequestCallFromTimer(callback) {
+ return function requestCall() {
+ // We dispatch a timeout with a specified delay of 0 for engines that
+ // can reliably accommodate that request. This will usually be snapped
+ // to a 4 milisecond delay, but once we're flushing, there's no delay
+ // between events.
+ var timeoutHandle = setTimeout(handleTimer, 0);
+ // However, since this timer gets frequently dropped in Firefox
+ // workers, we enlist an interval handle that will try to fire
+ // an event 20 times per second until it succeeds.
+ var intervalHandle = setInterval(handleTimer, 50);
+
+ function handleTimer() {
+ // Whichever timer succeeds will cancel both timers and
+ // execute the callback.
+ clearTimeout(timeoutHandle);
+ clearInterval(intervalHandle);
+ callback();
+ }
+ };
+}
+
+// This is for `asap.js` only.
+// Its name will be periodically randomized to break any code that depends on
+// its existence.
+rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer;
+
+// ASAP was originally a nextTick shim included in Q. This was factored out
+// into this ASAP package. It was later adapted to RSVP which made further
+// amendments. These decisions, particularly to marginalize MessageChannel and
+// to capture the MutationObserver implementation in a closure, were integrated
+// back into ASAP proper.
+// https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js
diff --git a/node_modules/asap/package.json b/node_modules/asap/package.json
new file mode 100644
index 0000000..ae9f303
--- /dev/null
+++ b/node_modules/asap/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "asap",
+ "version": "2.0.6",
+ "description": "High-priority task queue for Node.js and browsers",
+ "keywords": [
+ "event",
+ "task",
+ "queue"
+ ],
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/kriskowal/asap.git"
+ },
+ "main": "./asap.js",
+ "browser": {
+ "./asap": "./browser-asap.js",
+ "./asap.js": "./browser-asap.js",
+ "./raw": "./browser-raw.js",
+ "./raw.js": "./browser-raw.js",
+ "./test/domain.js": "./test/browser-domain.js"
+ },
+ "react-native": {
+ "domain": false
+ },
+ "files": [
+ "raw.js",
+ "asap.js",
+ "browser-raw.js",
+ "browser-asap.js"
+ ],
+ "scripts": {
+ "test": "npm run lint && npm run test-node",
+ "test-travis": "npm run lint && npm run test-node && npm run test-saucelabs && npm run test-saucelabs-worker",
+ "test-node": "node test/asap-test.js",
+ "test-publish": "node scripts/publish-bundle.js test/asap-test.js | pbcopy",
+ "test-browser": "node scripts/publish-bundle.js test/asap-test.js | xargs opener",
+ "test-saucelabs": "node scripts/saucelabs.js test/asap-test.js scripts/saucelabs-spot-configurations.json",
+ "test-saucelabs-all": "node scripts/saucelabs.js test/asap-test.js scripts/saucelabs-all-configurations.json",
+ "test-saucelabs-worker": "node scripts/saucelabs-worker-test.js scripts/saucelabs-spot-configurations.json",
+ "test-saucelabs-worker-all": "node scripts/saucelabs-worker-test.js scripts/saucelabs-all-configurations.json",
+ "lint": "jshint raw.js asap.js browser-raw.js browser-asap.js $(find scripts -name '*.js' | grep -v gauntlet)",
+ "benchmarks": "node benchmarks"
+ },
+ "devDependencies": {
+ "events": "^1.0.1",
+ "jshint": "^2.5.1",
+ "knox": "^0.8.10",
+ "mr": "^2.0.5",
+ "opener": "^1.3.0",
+ "q": "^2.0.3",
+ "q-io": "^2.0.3",
+ "saucelabs": "^0.1.1",
+ "wd": "^0.2.21",
+ "weak-map": "^1.0.5",
+ "benchmark": "^1.0.0"
+ }
+}
diff --git a/node_modules/asap/raw.js b/node_modules/asap/raw.js
new file mode 100644
index 0000000..ae3b892
--- /dev/null
+++ b/node_modules/asap/raw.js
@@ -0,0 +1,101 @@
+"use strict";
+
+var domain; // The domain module is executed on demand
+var hasSetImmediate = typeof setImmediate === "function";
+
+// Use the fastest means possible to execute a task in its own turn, with
+// priority over other events including network IO events in Node.js.
+//
+// An exception thrown by a task will permanently interrupt the processing of
+// subsequent tasks. The higher level `asap` function ensures that if an
+// exception is thrown by a task, that the task queue will continue flushing as
+// soon as possible, but if you use `rawAsap` directly, you are responsible to
+// either ensure that no exceptions are thrown from your task, or to manually
+// call `rawAsap.requestFlush` if an exception is thrown.
+module.exports = rawAsap;
+function rawAsap(task) {
+ if (!queue.length) {
+ requestFlush();
+ flushing = true;
+ }
+ // Avoids a function call
+ queue[queue.length] = task;
+}
+
+var queue = [];
+// Once a flush has been requested, no further calls to `requestFlush` are
+// necessary until the next `flush` completes.
+var flushing = false;
+// The position of the next task to execute in the task queue. This is
+// preserved between calls to `flush` so that it can be resumed if
+// a task throws an exception.
+var index = 0;
+// If a task schedules additional tasks recursively, the task queue can grow
+// unbounded. To prevent memory excaustion, the task queue will periodically
+// truncate already-completed tasks.
+var capacity = 1024;
+
+// The flush function processes all tasks that have been scheduled with
+// `rawAsap` unless and until one of those tasks throws an exception.
+// If a task throws an exception, `flush` ensures that its state will remain
+// consistent and will resume where it left off when called again.
+// However, `flush` does not make any arrangements to be called again if an
+// exception is thrown.
+function flush() {
+ while (index < queue.length) {
+ var currentIndex = index;
+ // Advance the index before calling the task. This ensures that we will
+ // begin flushing on the next task the task throws an error.
+ index = index + 1;
+ queue[currentIndex].call();
+ // Prevent leaking memory for long chains of recursive calls to `asap`.
+ // If we call `asap` within tasks scheduled by `asap`, the queue will
+ // grow, but to avoid an O(n) walk for every task we execute, we don't
+ // shift tasks off the queue after they have been executed.
+ // Instead, we periodically shift 1024 tasks off the queue.
+ if (index > capacity) {
+ // Manually shift all values starting at the index back to the
+ // beginning of the queue.
+ for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
+ queue[scan] = queue[scan + index];
+ }
+ queue.length -= index;
+ index = 0;
+ }
+ }
+ queue.length = 0;
+ index = 0;
+ flushing = false;
+}
+
+rawAsap.requestFlush = requestFlush;
+function requestFlush() {
+ // Ensure flushing is not bound to any domain.
+ // It is not sufficient to exit the domain, because domains exist on a stack.
+ // To execute code outside of any domain, the following dance is necessary.
+ var parentDomain = process.domain;
+ if (parentDomain) {
+ if (!domain) {
+ // Lazy execute the domain module.
+ // Only employed if the user elects to use domains.
+ domain = require("domain");
+ }
+ domain.active = process.domain = null;
+ }
+
+ // `setImmediate` is slower that `process.nextTick`, but `process.nextTick`
+ // cannot handle recursion.
+ // `requestFlush` will only be called recursively from `asap.js`, to resume
+ // flushing after an error is thrown into a domain.
+ // Conveniently, `setImmediate` was introduced in the same version
+ // `process.nextTick` started throwing recursion errors.
+ if (flushing && hasSetImmediate) {
+ setImmediate(flush);
+ } else {
+ process.nextTick(flush);
+ }
+
+ if (parentDomain) {
+ domain.active = process.domain = parentDomain;
+ }
+}
diff --git a/node_modules/content-disposition/HISTORY.md b/node_modules/content-disposition/HISTORY.md
new file mode 100644
index 0000000..488effa
--- /dev/null
+++ b/node_modules/content-disposition/HISTORY.md
@@ -0,0 +1,60 @@
+0.5.4 / 2021-12-10
+==================
+
+ * deps: safe-buffer@5.2.1
+
+0.5.3 / 2018-12-17
+==================
+
+ * Use `safe-buffer` for improved Buffer API
+
+0.5.2 / 2016-12-08
+==================
+
+ * Fix `parse` to accept any linear whitespace character
+
+0.5.1 / 2016-01-17
+==================
+
+ * perf: enable strict mode
+
+0.5.0 / 2014-10-11
+==================
+
+ * Add `parse` function
+
+0.4.0 / 2014-09-21
+==================
+
+ * Expand non-Unicode `filename` to the full ISO-8859-1 charset
+
+0.3.0 / 2014-09-20
+==================
+
+ * Add `fallback` option
+ * Add `type` option
+
+0.2.0 / 2014-09-19
+==================
+
+ * Reduce ambiguity of file names with hex escape in buggy browsers
+
+0.1.2 / 2014-09-19
+==================
+
+ * Fix periodic invalid Unicode filename header
+
+0.1.1 / 2014-09-19
+==================
+
+ * Fix invalid characters appearing in `filename*` parameter
+
+0.1.0 / 2014-09-18
+==================
+
+ * Make the `filename` argument optional
+
+0.0.0 / 2014-09-18
+==================
+
+ * Initial release
diff --git a/node_modules/content-disposition/LICENSE b/node_modules/content-disposition/LICENSE
new file mode 100644
index 0000000..84441fb
--- /dev/null
+++ b/node_modules/content-disposition/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2014-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/content-disposition/README.md b/node_modules/content-disposition/README.md
new file mode 100644
index 0000000..3a0bb05
--- /dev/null
+++ b/node_modules/content-disposition/README.md
@@ -0,0 +1,142 @@
+# content-disposition
+
+[![NPM Version][npm-image]][npm-url]
+[![NPM Downloads][downloads-image]][downloads-url]
+[![Node.js Version][node-version-image]][node-version-url]
+[![Build Status][github-actions-ci-image]][github-actions-ci-url]
+[![Test Coverage][coveralls-image]][coveralls-url]
+
+Create and parse HTTP `Content-Disposition` header
+
+## Installation
+
+```sh
+$ npm install content-disposition
+```
+
+## API
+
+```js
+var contentDisposition = require('content-disposition')
+```
+
+### contentDisposition(filename, options)
+
+Create an attachment `Content-Disposition` header value using the given file name,
+if supplied. The `filename` is optional and if no file name is desired, but you
+want to specify `options`, set `filename` to `undefined`.
+
+```js
+res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
+```
+
+**note** HTTP headers are of the ISO-8859-1 character set. If you are writing this
+header through a means different from `setHeader` in Node.js, you'll want to specify
+the `'binary'` encoding in Node.js.
+
+#### Options
+
+`contentDisposition` accepts these properties in the options object.
+
+##### fallback
+
+If the `filename` option is outside ISO-8859-1, then the file name is actually
+stored in a supplemental field for clients that support Unicode file names and
+a ISO-8859-1 version of the file name is automatically generated.
+
+This specifies the ISO-8859-1 file name to override the automatic generation or
+disables the generation all together, defaults to `true`.
+
+ - A string will specify the ISO-8859-1 file name to use in place of automatic
+ generation.
+ - `false` will disable including a ISO-8859-1 file name and only include the
+ Unicode version (unless the file name is already ISO-8859-1).
+ - `true` will enable automatic generation if the file name is outside ISO-8859-1.
+
+If the `filename` option is ISO-8859-1 and this option is specified and has a
+different value, then the `filename` option is encoded in the extended field
+and this set as the fallback field, even though they are both ISO-8859-1.
+
+##### type
+
+Specifies the disposition type, defaults to `"attachment"`. This can also be
+`"inline"`, or any other value (all values except inline are treated like
+`attachment`, but can convey additional information if both parties agree to
+it). The type is normalized to lower-case.
+
+### contentDisposition.parse(string)
+
+```js
+var disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
+```
+
+Parse a `Content-Disposition` header string. This automatically handles extended
+("Unicode") parameters by decoding them and providing them under the standard
+parameter name. This will return an object with the following properties (examples
+are shown for the string `'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'`):
+
+ - `type`: The disposition type (always lower case). Example: `'attachment'`
+
+ - `parameters`: An object of the parameters in the disposition (name of parameter
+ always lower case and extended versions replace non-extended versions). Example:
+ `{filename: "€ rates.txt"}`
+
+## Examples
+
+### Send a file for download
+
+```js
+var contentDisposition = require('content-disposition')
+var destroy = require('destroy')
+var fs = require('fs')
+var http = require('http')
+var onFinished = require('on-finished')
+
+var filePath = '/path/to/public/plans.pdf'
+
+http.createServer(function onRequest (req, res) {
+ // set headers
+ res.setHeader('Content-Type', 'application/pdf')
+ res.setHeader('Content-Disposition', contentDisposition(filePath))
+
+ // send file
+ var stream = fs.createReadStream(filePath)
+ stream.pipe(res)
+ onFinished(res, function () {
+ destroy(stream)
+ })
+})
+```
+
+## Testing
+
+```sh
+$ npm test
+```
+
+## References
+
+- [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1][rfc-2616]
+- [RFC 5987: Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters][rfc-5987]
+- [RFC 6266: Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)][rfc-6266]
+- [Test Cases for HTTP Content-Disposition header field (RFC 6266) and the Encodings defined in RFCs 2047, 2231 and 5987][tc-2231]
+
+[rfc-2616]: https://tools.ietf.org/html/rfc2616
+[rfc-5987]: https://tools.ietf.org/html/rfc5987
+[rfc-6266]: https://tools.ietf.org/html/rfc6266
+[tc-2231]: http://greenbytes.de/tech/tc2231/
+
+## License
+
+[MIT](LICENSE)
+
+[npm-image]: https://img.shields.io/npm/v/content-disposition.svg
+[npm-url]: https://npmjs.org/package/content-disposition
+[node-version-image]: https://img.shields.io/node/v/content-disposition.svg
+[node-version-url]: https://nodejs.org/en/download
+[coveralls-image]: https://img.shields.io/coveralls/jshttp/content-disposition.svg
+[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
+[downloads-image]: https://img.shields.io/npm/dm/content-disposition.svg
+[downloads-url]: https://npmjs.org/package/content-disposition
+[github-actions-ci-image]: https://img.shields.io/github/workflow/status/jshttp/content-disposition/ci/master?label=ci
+[github-actions-ci-url]: https://github.com/jshttp/content-disposition?query=workflow%3Aci
diff --git a/node_modules/content-disposition/index.js b/node_modules/content-disposition/index.js
new file mode 100644
index 0000000..ecec899
--- /dev/null
+++ b/node_modules/content-disposition/index.js
@@ -0,0 +1,458 @@
+/*!
+ * content-disposition
+ * Copyright(c) 2014-2017 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+'use strict'
+
+/**
+ * Module exports.
+ * @public
+ */
+
+module.exports = contentDisposition
+module.exports.parse = parse
+
+/**
+ * Module dependencies.
+ * @private
+ */
+
+var basename = require('path').basename
+var Buffer = require('safe-buffer').Buffer
+
+/**
+ * RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
+ * @private
+ */
+
+var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex
+
+/**
+ * RegExp to match percent encoding escape.
+ * @private
+ */
+
+var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
+var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g
+
+/**
+ * RegExp to match non-latin1 characters.
+ * @private
+ */
+
+var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g
+
+/**
+ * RegExp to match quoted-pair in RFC 2616
+ *
+ * quoted-pair = "\" CHAR
+ * CHAR =
+ * @private
+ */
+
+var QESC_REGEXP = /\\([\u0000-\u007f])/g // eslint-disable-line no-control-regex
+
+/**
+ * RegExp to match chars that must be quoted-pair in RFC 2616
+ * @private
+ */
+
+var QUOTE_REGEXP = /([\\"])/g
+
+/**
+ * RegExp for various RFC 2616 grammar
+ *
+ * parameter = token "=" ( token | quoted-string )
+ * token = 1*
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ * qdtext = >
+ * quoted-pair = "\" CHAR
+ * CHAR =
+ * TEXT =
+ * LWS = [CRLF] 1*( SP | HT )
+ * CRLF = CR LF
+ * CR =
+ * LF =
+ * SP =
+ * HT =
+ * CTL =
+ * OCTET =
+ * @private
+ */
+
+var PARAM_REGEXP = /;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g // eslint-disable-line no-control-regex
+var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/
+var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/
+
+/**
+ * RegExp for various RFC 5987 grammar
+ *
+ * ext-value = charset "'" [ language ] "'" value-chars
+ * charset = "UTF-8" / "ISO-8859-1" / mime-charset
+ * mime-charset = 1*mime-charsetc
+ * mime-charsetc = ALPHA / DIGIT
+ * / "!" / "#" / "$" / "%" / "&"
+ * / "+" / "-" / "^" / "_" / "`"
+ * / "{" / "}" / "~"
+ * language = ( 2*3ALPHA [ extlang ] )
+ * / 4ALPHA
+ * / 5*8ALPHA
+ * extlang = *3( "-" 3ALPHA )
+ * value-chars = *( pct-encoded / attr-char )
+ * pct-encoded = "%" HEXDIG HEXDIG
+ * attr-char = ALPHA / DIGIT
+ * / "!" / "#" / "$" / "&" / "+" / "-" / "."
+ * / "^" / "_" / "`" / "|" / "~"
+ * @private
+ */
+
+var EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/
+
+/**
+ * RegExp for various RFC 6266 grammar
+ *
+ * disposition-type = "inline" | "attachment" | disp-ext-type
+ * disp-ext-type = token
+ * disposition-parm = filename-parm | disp-ext-parm
+ * filename-parm = "filename" "=" value
+ * | "filename*" "=" ext-value
+ * disp-ext-parm = token "=" value
+ * | ext-token "=" ext-value
+ * ext-token =
+ * @private
+ */
+
+var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/ // eslint-disable-line no-control-regex
+
+/**
+ * Create an attachment Content-Disposition header.
+ *
+ * @param {string} [filename]
+ * @param {object} [options]
+ * @param {string} [options.type=attachment]
+ * @param {string|boolean} [options.fallback=true]
+ * @return {string}
+ * @public
+ */
+
+function contentDisposition (filename, options) {
+ var opts = options || {}
+
+ // get type
+ var type = opts.type || 'attachment'
+
+ // get parameters
+ var params = createparams(filename, opts.fallback)
+
+ // format into string
+ return format(new ContentDisposition(type, params))
+}
+
+/**
+ * Create parameters object from filename and fallback.
+ *
+ * @param {string} [filename]
+ * @param {string|boolean} [fallback=true]
+ * @return {object}
+ * @private
+ */
+
+function createparams (filename, fallback) {
+ if (filename === undefined) {
+ return
+ }
+
+ var params = {}
+
+ if (typeof filename !== 'string') {
+ throw new TypeError('filename must be a string')
+ }
+
+ // fallback defaults to true
+ if (fallback === undefined) {
+ fallback = true
+ }
+
+ if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
+ throw new TypeError('fallback must be a string or boolean')
+ }
+
+ if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
+ throw new TypeError('fallback must be ISO-8859-1 string')
+ }
+
+ // restrict to file base name
+ var name = basename(filename)
+
+ // determine if name is suitable for quoted string
+ var isQuotedString = TEXT_REGEXP.test(name)
+
+ // generate fallback name
+ var fallbackName = typeof fallback !== 'string'
+ ? fallback && getlatin1(name)
+ : basename(fallback)
+ var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
+
+ // set extended filename parameter
+ if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
+ params['filename*'] = name
+ }
+
+ // set filename parameter
+ if (isQuotedString || hasFallback) {
+ params.filename = hasFallback
+ ? fallbackName
+ : name
+ }
+
+ return params
+}
+
+/**
+ * Format object to Content-Disposition header.
+ *
+ * @param {object} obj
+ * @param {string} obj.type
+ * @param {object} [obj.parameters]
+ * @return {string}
+ * @private
+ */
+
+function format (obj) {
+ var parameters = obj.parameters
+ var type = obj.type
+
+ if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {
+ throw new TypeError('invalid type')
+ }
+
+ // start with normalized type
+ var string = String(type).toLowerCase()
+
+ // append parameters
+ if (parameters && typeof parameters === 'object') {
+ var param
+ var params = Object.keys(parameters).sort()
+
+ for (var i = 0; i < params.length; i++) {
+ param = params[i]
+
+ var val = param.substr(-1) === '*'
+ ? ustring(parameters[param])
+ : qstring(parameters[param])
+
+ string += '; ' + param + '=' + val
+ }
+ }
+
+ return string
+}
+
+/**
+ * Decode a RFC 5987 field value (gracefully).
+ *
+ * @param {string} str
+ * @return {string}
+ * @private
+ */
+
+function decodefield (str) {
+ var match = EXT_VALUE_REGEXP.exec(str)
+
+ if (!match) {
+ throw new TypeError('invalid extended field value')
+ }
+
+ var charset = match[1].toLowerCase()
+ var encoded = match[2]
+ var value
+
+ // to binary string
+ var binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)
+
+ switch (charset) {
+ case 'iso-8859-1':
+ value = getlatin1(binary)
+ break
+ case 'utf-8':
+ value = Buffer.from(binary, 'binary').toString('utf8')
+ break
+ default:
+ throw new TypeError('unsupported charset in extended field')
+ }
+
+ return value
+}
+
+/**
+ * Get ISO-8859-1 version of string.
+ *
+ * @param {string} val
+ * @return {string}
+ * @private
+ */
+
+function getlatin1 (val) {
+ // simple Unicode -> ISO-8859-1 transformation
+ return String(val).replace(NON_LATIN1_REGEXP, '?')
+}
+
+/**
+ * Parse Content-Disposition header string.
+ *
+ * @param {string} string
+ * @return {object}
+ * @public
+ */
+
+function parse (string) {
+ if (!string || typeof string !== 'string') {
+ throw new TypeError('argument string is required')
+ }
+
+ var match = DISPOSITION_TYPE_REGEXP.exec(string)
+
+ if (!match) {
+ throw new TypeError('invalid type format')
+ }
+
+ // normalize type
+ var index = match[0].length
+ var type = match[1].toLowerCase()
+
+ var key
+ var names = []
+ var params = {}
+ var value
+
+ // calculate index to start at
+ index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === ';'
+ ? index - 1
+ : index
+
+ // match parameters
+ while ((match = PARAM_REGEXP.exec(string))) {
+ if (match.index !== index) {
+ throw new TypeError('invalid parameter format')
+ }
+
+ index += match[0].length
+ key = match[1].toLowerCase()
+ value = match[2]
+
+ if (names.indexOf(key) !== -1) {
+ throw new TypeError('invalid duplicate parameter')
+ }
+
+ names.push(key)
+
+ if (key.indexOf('*') + 1 === key.length) {
+ // decode extended value
+ key = key.slice(0, -1)
+ value = decodefield(value)
+
+ // overwrite existing value
+ params[key] = value
+ continue
+ }
+
+ if (typeof params[key] === 'string') {
+ continue
+ }
+
+ if (value[0] === '"') {
+ // remove quotes and escapes
+ value = value
+ .substr(1, value.length - 2)
+ .replace(QESC_REGEXP, '$1')
+ }
+
+ params[key] = value
+ }
+
+ if (index !== -1 && index !== string.length) {
+ throw new TypeError('invalid parameter format')
+ }
+
+ return new ContentDisposition(type, params)
+}
+
+/**
+ * Percent decode a single character.
+ *
+ * @param {string} str
+ * @param {string} hex
+ * @return {string}
+ * @private
+ */
+
+function pdecode (str, hex) {
+ return String.fromCharCode(parseInt(hex, 16))
+}
+
+/**
+ * Percent encode a single character.
+ *
+ * @param {string} char
+ * @return {string}
+ * @private
+ */
+
+function pencode (char) {
+ return '%' + String(char)
+ .charCodeAt(0)
+ .toString(16)
+ .toUpperCase()
+}
+
+/**
+ * Quote a string for HTTP.
+ *
+ * @param {string} val
+ * @return {string}
+ * @private
+ */
+
+function qstring (val) {
+ var str = String(val)
+
+ return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
+}
+
+/**
+ * Encode a Unicode string for HTTP (RFC 5987).
+ *
+ * @param {string} val
+ * @return {string}
+ * @private
+ */
+
+function ustring (val) {
+ var str = String(val)
+
+ // percent encode as UTF-8
+ var encoded = encodeURIComponent(str)
+ .replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)
+
+ return 'UTF-8\'\'' + encoded
+}
+
+/**
+ * Class for parsed Content-Disposition header for v8 optimization
+ *
+ * @public
+ * @param {string} type
+ * @param {object} parameters
+ * @constructor
+ */
+
+function ContentDisposition (type, parameters) {
+ this.type = type
+ this.parameters = parameters
+}
diff --git a/node_modules/content-disposition/package.json b/node_modules/content-disposition/package.json
new file mode 100644
index 0000000..43c70ce
--- /dev/null
+++ b/node_modules/content-disposition/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "content-disposition",
+ "description": "Create and parse Content-Disposition header",
+ "version": "0.5.4",
+ "author": "Douglas Christopher Wilson ",
+ "license": "MIT",
+ "keywords": [
+ "content-disposition",
+ "http",
+ "rfc6266",
+ "res"
+ ],
+ "repository": "jshttp/content-disposition",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "devDependencies": {
+ "deep-equal": "1.0.1",
+ "eslint": "7.32.0",
+ "eslint-config-standard": "13.0.1",
+ "eslint-plugin-import": "2.25.3",
+ "eslint-plugin-markdown": "2.2.1",
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-promise": "5.2.0",
+ "eslint-plugin-standard": "4.1.0",
+ "istanbul": "0.4.5",
+ "mocha": "9.1.3"
+ },
+ "files": [
+ "LICENSE",
+ "HISTORY.md",
+ "README.md",
+ "index.js"
+ ],
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "scripts": {
+ "lint": "eslint .",
+ "test": "mocha --reporter spec --bail --check-leaks test/",
+ "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/",
+ "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/"
+ }
+}
diff --git a/node_modules/destroy/LICENSE b/node_modules/destroy/LICENSE
new file mode 100644
index 0000000..a7ae8ee
--- /dev/null
+++ b/node_modules/destroy/LICENSE
@@ -0,0 +1,22 @@
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Jonathan Ong me@jongleberry.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/destroy/README.md b/node_modules/destroy/README.md
new file mode 100644
index 0000000..69ac806
--- /dev/null
+++ b/node_modules/destroy/README.md
@@ -0,0 +1,61 @@
+# Destroy
+
+[![NPM version][npm-image]][npm-url]
+[![Build Status][github-actions-ci-image]][github-actions-ci-url]
+[![Test coverage][coveralls-image]][coveralls-url]
+[![License][license-image]][license-url]
+[![Downloads][downloads-image]][downloads-url]
+
+Destroy a stream.
+
+This module is meant to ensure a stream gets destroyed, handling different APIs
+and Node.js bugs.
+
+## API
+
+```js
+var destroy = require('destroy')
+```
+
+### destroy(stream)
+
+Destroy the given stream. In most cases, this is identical to a simple
+`stream.destroy()` call. The rules are as follows for a given stream:
+
+ 1. If the `stream` is an instance of `ReadStream`, then call `stream.destroy()`
+ and add a listener to the `open` event to call `stream.close()` if it is
+ fired. This is for a Node.js bug that will leak a file descriptor if
+ `.destroy()` is called before `open`.
+ 2. If the `stream` is an instance of a zlib stream, then call `stream.destroy()`
+ and close the underlying zlib handle if open, otherwise call `stream.close()`.
+ This is for consistency across Node.js versions and a Node.js bug that will
+ leak a native zlib handle.
+ 3. If the `stream` is not an instance of `Stream`, then nothing happens.
+ 4. If the `stream` has a `.destroy()` method, then call it.
+
+The function returns the `stream` passed in as the argument.
+
+## Example
+
+```js
+var destroy = require('destroy')
+
+var fs = require('fs')
+var stream = fs.createReadStream('package.json')
+
+// ... and later
+destroy(stream)
+```
+
+[npm-image]: https://img.shields.io/npm/v/destroy.svg?style=flat-square
+[npm-url]: https://npmjs.org/package/destroy
+[github-tag]: http://img.shields.io/github/tag/stream-utils/destroy.svg?style=flat-square
+[github-url]: https://github.com/stream-utils/destroy/tags
+[coveralls-image]: https://img.shields.io/coveralls/stream-utils/destroy.svg?style=flat-square
+[coveralls-url]: https://coveralls.io/r/stream-utils/destroy?branch=master
+[license-image]: http://img.shields.io/npm/l/destroy.svg?style=flat-square
+[license-url]: LICENSE.md
+[downloads-image]: http://img.shields.io/npm/dm/destroy.svg?style=flat-square
+[downloads-url]: https://npmjs.org/package/destroy
+[github-actions-ci-image]: https://img.shields.io/github/workflow/status/stream-utils/destroy/ci/master?label=ci&style=flat-square
+[github-actions-ci-url]: https://github.com/stream-utils/destroy/actions/workflows/ci.yml
diff --git a/node_modules/destroy/index.js b/node_modules/destroy/index.js
new file mode 100644
index 0000000..d330ae5
--- /dev/null
+++ b/node_modules/destroy/index.js
@@ -0,0 +1,145 @@
+/*!
+ * destroy
+ * Copyright(c) 2014 Jonathan Ong
+ * MIT Licensed
+ */
+
+'use strict'
+
+/**
+ * Module dependencies.
+ * @private
+ */
+
+var ReadStream = require('fs').ReadStream
+var Stream = require('stream')
+var Zlib = require('zlib')
+
+/**
+ * Module exports.
+ * @public
+ */
+
+module.exports = destroy
+
+/**
+ * Destroy a stream.
+ *
+ * @param {object} stream
+ * @public
+ */
+
+function destroy (stream) {
+ if (stream instanceof ReadStream) {
+ return destroyReadStream(stream)
+ }
+
+ if (stream instanceof Zlib.Gzip ||
+ stream instanceof Zlib.Gunzip ||
+ stream instanceof Zlib.Deflate ||
+ stream instanceof Zlib.DeflateRaw ||
+ stream instanceof Zlib.Inflate ||
+ stream instanceof Zlib.InflateRaw ||
+ stream instanceof Zlib.Unzip) {
+ return destroyZlibStream(stream)
+ }
+
+ if (!(stream instanceof Stream)) {
+ return stream
+ }
+
+ if (typeof stream.destroy === 'function') {
+ stream.destroy()
+ }
+
+ return stream
+}
+
+/**
+ * Destroy a ReadStream.
+ *
+ * @param {object} stream
+ * @private
+ */
+
+function destroyReadStream (stream) {
+ stream.destroy()
+
+ if (typeof stream.close === 'function') {
+ // node.js core bug work-around
+ stream.on('open', onOpenClose)
+ }
+
+ return stream
+}
+
+/**
+ * Destroy a Zlib stream.
+ *
+ * Zlib streams don't have a destroy function in Node.js 6. On top of that
+ * simply calling destroy on a zlib stream in Node.js 8+ will result in a
+ * memory leak. So until that is fixed, we need to call both close AND destroy.
+ *
+ * PR to fix memory leak: https://github.com/nodejs/node/pull/23734
+ *
+ * In Node.js 6+8, it's important that destroy is called before close as the
+ * stream would otherwise emit the error 'zlib binding closed'.
+ *
+ * @param {object} stream
+ * @private
+ */
+
+function destroyZlibStream (stream) {
+ if (typeof stream.destroy === 'function') {
+ // node.js core bug work-around
+ // istanbul ignore if: node.js 0.8
+ if (stream._binding) {
+ // node.js < 0.10.0
+ stream.destroy()
+ if (stream._processing) {
+ stream._needDrain = true
+ stream.once('drain', onDrainClearBinding)
+ } else {
+ stream._binding.clear()
+ }
+ } else if (stream._destroy && stream._destroy !== Stream.Transform.prototype._destroy) {
+ // node.js >= 12, ^11.1.0, ^10.15.1
+ stream.destroy()
+ } else if (stream._destroy && typeof stream.close === 'function') {
+ // node.js 7, 8
+ stream.destroyed = true
+ stream.close()
+ } else {
+ // fallback
+ // istanbul ignore next
+ stream.destroy()
+ }
+ } else if (typeof stream.close === 'function') {
+ // node.js < 8 fallback
+ stream.close()
+ }
+
+ return stream
+}
+
+/**
+ * On drain handler to clear binding.
+ * @private
+ */
+
+// istanbul ignore next: node.js 0.8
+function onDrainClearBinding () {
+ this._binding.clear()
+}
+
+/**
+ * On open handler to close stream.
+ * @private
+ */
+
+function onOpenClose () {
+ if (typeof this.fd === 'number') {
+ // actually close down the fd
+ this.close()
+ }
+}
diff --git a/node_modules/destroy/package.json b/node_modules/destroy/package.json
new file mode 100644
index 0000000..f3c5f27
--- /dev/null
+++ b/node_modules/destroy/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "destroy",
+ "description": "destroy a stream if possible",
+ "version": "1.1.0",
+ "author": {
+ "name": "Jonathan Ong",
+ "email": "me@jongleberry.com",
+ "url": "http://jongleberry.com",
+ "twitter": "https://twitter.com/jongleberry"
+ },
+ "contributors": [
+ "Douglas Christopher Wilson "
+ ],
+ "license": "MIT",
+ "repository": "stream-utils/destroy",
+ "devDependencies": {
+ "eslint": "7.32.0",
+ "eslint-config-standard": "14.1.1",
+ "eslint-plugin-import": "2.25.4",
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-promise": "5.2.0",
+ "eslint-plugin-standard": "4.1.0",
+ "mocha": "9.2.0",
+ "nyc": "15.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ },
+ "scripts": {
+ "lint": "eslint .",
+ "test": "mocha --reporter spec",
+ "test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
+ "test-cov": "nyc --reporter=html --reporter=text npm test"
+ },
+ "files": [
+ "index.js",
+ "LICENSE"
+ ],
+ "keywords": [
+ "stream",
+ "streams",
+ "destroy",
+ "cleanup",
+ "leak",
+ "fd"
+ ]
+}
diff --git a/node_modules/dezalgo/.travis.yml b/node_modules/dezalgo/.travis.yml
new file mode 100644
index 0000000..e1bcee1
--- /dev/null
+++ b/node_modules/dezalgo/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+before_script: npm install -g npm@latest
+node_js:
+ - '0.8'
+ - '0.10'
+ - '0.12'
+ - 'iojs'
diff --git a/node_modules/dezalgo/LICENSE b/node_modules/dezalgo/LICENSE
new file mode 100644
index 0000000..19129e3
--- /dev/null
+++ b/node_modules/dezalgo/LICENSE
@@ -0,0 +1,15 @@
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/node_modules/dezalgo/README.md b/node_modules/dezalgo/README.md
new file mode 100644
index 0000000..bdfc8ba
--- /dev/null
+++ b/node_modules/dezalgo/README.md
@@ -0,0 +1,29 @@
+# dezalgo
+
+Contain async insanity so that the dark pony lord doesn't eat souls
+
+See [this blog
+post](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony).
+
+## USAGE
+
+Pass a callback to `dezalgo` and it will ensure that it is *always*
+called in a future tick, and never in this tick.
+
+```javascript
+var dz = require('dezalgo')
+
+var cache = {}
+function maybeSync(arg, cb) {
+ cb = dz(cb)
+
+ // this will actually defer to nextTick
+ if (cache[arg]) cb(null, cache[arg])
+
+ fs.readFile(arg, function (er, data) {
+ // since this is *already* defered, it will call immediately
+ if (er) cb(er)
+ cb(null, cache[arg] = data)
+ })
+}
+```
diff --git a/node_modules/dezalgo/dezalgo.js b/node_modules/dezalgo/dezalgo.js
new file mode 100644
index 0000000..04fd3ba
--- /dev/null
+++ b/node_modules/dezalgo/dezalgo.js
@@ -0,0 +1,22 @@
+var wrappy = require('wrappy')
+module.exports = wrappy(dezalgo)
+
+var asap = require('asap')
+
+function dezalgo (cb) {
+ var sync = true
+ asap(function () {
+ sync = false
+ })
+
+ return function zalgoSafe() {
+ var args = arguments
+ var me = this
+ if (sync)
+ asap(function() {
+ cb.apply(me, args)
+ })
+ else
+ cb.apply(me, args)
+ }
+}
diff --git a/node_modules/dezalgo/package.json b/node_modules/dezalgo/package.json
new file mode 100644
index 0000000..634dd0d
--- /dev/null
+++ b/node_modules/dezalgo/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "dezalgo",
+ "version": "1.0.3",
+ "description": "Contain async insanity so that the dark pony lord doesn't eat souls",
+ "main": "dezalgo.js",
+ "directories": {
+ "test": "test"
+ },
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ },
+ "devDependencies": {
+ "tap": "^1.2.0"
+ },
+ "scripts": {
+ "test": "tap test/*.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/npm/dezalgo"
+ },
+ "keywords": [
+ "async",
+ "zalgo",
+ "the dark pony",
+ "he comes",
+ "asynchrony of all holy and good",
+ "T̯̪ͅo̯͖̹ ̻̮̖̲͢i̥̖n̢͈͇̝͍v͏͉ok̭̬̝ͅe̞͍̩̫͍̩͝ ̩̮̖̟͇͉́t͔͔͎̗h͏̗̟e̘͉̰̦̠̞͓ ͕h͉̟͎̪̠̱͠ḭ̮̩v̺͉͇̩e̵͖-̺̪m͍i̜n̪̲̲̲̮d̷ ̢r̠̼̯̹̦̦͘ͅe͓̳͓̙p̺̗̫͙͘ͅr͔̰͜e̴͓̞s͉̩̩͟ͅe͏̣n͚͇̗̭̺͍tì͙̣n͏̖̥̗͎̰̪g̞͓̭̱̯̫̕ ̣̱͜ͅc̦̰̰̠̮͎͙̀hao̺̜̻͍͙ͅs͉͓̘.͎̼̺̼͕̹͘",
+ "̠̞̱̰I͖͇̝̻n̦̰͍̰̟v̤̺̫̳̭̼̗͘ò̹̟̩̩͚k̢̥̠͍͉̦̬i̖͓͔̮̱̻͘n̶̳͙̫͎g̖̯̣̲̪͉ ̞͎̗͕͚ͅt̲͕̘̺̯̗̦h̘̦̲̜̻e̳͎͉̬͙ ̴̞̪̲̥f̜̯͓͓̭̭͢e̱̘͔̮e̜̤l̺̱͖̯͓͙͈͢i̵̦̬͉͔̫͚͕n͉g̨͖̙̙̹̹̟̤ ͉̪o̞̠͍̪̰͙ͅf̬̲̺ ͔͕̲͕͕̲̕c̙͉h̝͔̩̙̕ͅa̲͖̻̗̹o̥̼̫s̝̖̜̝͚̫̟.̺͚ ̸̱̲W̶̥̣͖̦i͏̤̬̱̳̣ͅt͉h̗̪̪ ̷̱͚̹̪ǫ͕̗̣̳̦͎u̼̦͔̥̮̕ţ͖͎̻͔͉ ̴͎̩òr̹̰̖͉͈͝d̷̲̦̖͓e̲͓̠r",
+ "̧͚̜͓̰̭̭Ṯ̫̹̜̮̟̮͝h͚̘̩̘̖̰́e ̥̘͓͉͔͙̼N̟̜̣̘͔̪e̞̞̤͢z̰̖̘͇p̠͟e̺̱̣͍͙̝ṛ̘̬͔̙͇̠d͝ḭ̯̱̥̗̩a̛ͅn͏̦ ̷̥hi̥v̖̳̹͉̮̱͝e̹̪̘̖̰̟-̴͙͓͚̜̻mi̗̺̻͙̺ͅn̪̯͈d ͏̘͓̫̳ͅơ̹͔̳̖̣͓f͈̹̘ ͕ͅc̗̤̠̜̮̥̥h̡͍̩̭̫͚̱a̤͉̤͔͜os͕̤̼͍̲̀ͅ.̡̱ ̦Za̯̱̗̭͍̣͚l̗͉̰̤g͏̣̭̬̗̲͖ͅo̶̭̩̳̟͈.̪̦̰̳",
+ "H̴̱̦̗̬̣͓̺e̮ ͉̠̰̞͎̖͟ẁh̛̺̯ͅo̖̫͡ ̢Ẁa̡̗i̸t͖̣͉̀ş͔̯̩ ̤̦̮͇̞̦̲B͎̭͇̦̼e̢hin͏͙̟̪d̴̰͓̻̣̮͕ͅ T͖̮̕h͖e̘̺̰̙͘ ̥Ẁ̦͔̻͚a̞͖̪͉l̪̠̻̰̣̠l̲͎͞",
+ "Z̘͍̼͎̣͔͝Ą̲̜̱̱̹̤͇L̶̝̰̭͔G͍̖͍O̫͜ͅ!̼̤ͅ",
+ "H̝̪̜͓̀̌̂̒E̢̙̠̣ ̴̳͇̥̟̠͍̐C̹̓̑̐̆͝Ó̶̭͓̚M̬̼Ĕ̖̤͔͔̟̹̽̿̊ͥ̍ͫS̻̰̦̻̖̘̱̒ͪ͌̅͟"
+ ],
+ "author": "Isaac Z. Schlueter (http://blog.izs.me/)",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/npm/dezalgo/issues"
+ },
+ "homepage": "https://github.com/npm/dezalgo"
+}
diff --git a/node_modules/dezalgo/test/basic.js b/node_modules/dezalgo/test/basic.js
new file mode 100644
index 0000000..da09e72
--- /dev/null
+++ b/node_modules/dezalgo/test/basic.js
@@ -0,0 +1,29 @@
+var test = require('tap').test
+var dz = require('../dezalgo.js')
+
+test('the dark pony', function(t) {
+
+ var n = 0
+ function foo(i, cb) {
+ cb = dz(cb)
+ if (++n % 2) cb(true, i)
+ else process.nextTick(cb.bind(null, false, i))
+ }
+
+ var called = 0
+ var order = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
+ var o = 0
+ for (var i = 0; i < 10; i++) {
+ foo(i, function(cached, i) {
+ t.equal(i, order[o++])
+ t.equal(i % 2, cached ? 0 : 1)
+ called++
+ })
+ t.equal(called, 0)
+ }
+
+ setTimeout(function() {
+ t.equal(called, 10)
+ t.end()
+ })
+})
diff --git a/node_modules/ee-first/LICENSE b/node_modules/ee-first/LICENSE
new file mode 100644
index 0000000..a7ae8ee
--- /dev/null
+++ b/node_modules/ee-first/LICENSE
@@ -0,0 +1,22 @@
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Jonathan Ong me@jongleberry.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/ee-first/README.md b/node_modules/ee-first/README.md
new file mode 100644
index 0000000..cbd2478
--- /dev/null
+++ b/node_modules/ee-first/README.md
@@ -0,0 +1,80 @@
+# EE First
+
+[![NPM version][npm-image]][npm-url]
+[![Build status][travis-image]][travis-url]
+[![Test coverage][coveralls-image]][coveralls-url]
+[![License][license-image]][license-url]
+[![Downloads][downloads-image]][downloads-url]
+[![Gittip][gittip-image]][gittip-url]
+
+Get the first event in a set of event emitters and event pairs,
+then clean up after itself.
+
+## Install
+
+```sh
+$ npm install ee-first
+```
+
+## API
+
+```js
+var first = require('ee-first')
+```
+
+### first(arr, listener)
+
+Invoke `listener` on the first event from the list specified in `arr`. `arr` is
+an array of arrays, with each array in the format `[ee, ...event]`. `listener`
+will be called only once, the first time any of the given events are emitted. If
+`error` is one of the listened events, then if that fires first, the `listener`
+will be given the `err` argument.
+
+The `listener` is invoked as `listener(err, ee, event, args)`, where `err` is the
+first argument emitted from an `error` event, if applicable; `ee` is the event
+emitter that fired; `event` is the string event name that fired; and `args` is an
+array of the arguments that were emitted on the event.
+
+```js
+var ee1 = new EventEmitter()
+var ee2 = new EventEmitter()
+
+first([
+ [ee1, 'close', 'end', 'error'],
+ [ee2, 'error']
+], function (err, ee, event, args) {
+ // listener invoked
+})
+```
+
+#### .cancel()
+
+The group of listeners can be cancelled before being invoked and have all the event
+listeners removed from the underlying event emitters.
+
+```js
+var thunk = first([
+ [ee1, 'close', 'end', 'error'],
+ [ee2, 'error']
+], function (err, ee, event, args) {
+ // listener invoked
+})
+
+// cancel and clean up
+thunk.cancel()
+```
+
+[npm-image]: https://img.shields.io/npm/v/ee-first.svg?style=flat-square
+[npm-url]: https://npmjs.org/package/ee-first
+[github-tag]: http://img.shields.io/github/tag/jonathanong/ee-first.svg?style=flat-square
+[github-url]: https://github.com/jonathanong/ee-first/tags
+[travis-image]: https://img.shields.io/travis/jonathanong/ee-first.svg?style=flat-square
+[travis-url]: https://travis-ci.org/jonathanong/ee-first
+[coveralls-image]: https://img.shields.io/coveralls/jonathanong/ee-first.svg?style=flat-square
+[coveralls-url]: https://coveralls.io/r/jonathanong/ee-first?branch=master
+[license-image]: http://img.shields.io/npm/l/ee-first.svg?style=flat-square
+[license-url]: LICENSE.md
+[downloads-image]: http://img.shields.io/npm/dm/ee-first.svg?style=flat-square
+[downloads-url]: https://npmjs.org/package/ee-first
+[gittip-image]: https://img.shields.io/gittip/jonathanong.svg?style=flat-square
+[gittip-url]: https://www.gittip.com/jonathanong/
diff --git a/node_modules/ee-first/index.js b/node_modules/ee-first/index.js
new file mode 100644
index 0000000..501287c
--- /dev/null
+++ b/node_modules/ee-first/index.js
@@ -0,0 +1,95 @@
+/*!
+ * ee-first
+ * Copyright(c) 2014 Jonathan Ong
+ * MIT Licensed
+ */
+
+'use strict'
+
+/**
+ * Module exports.
+ * @public
+ */
+
+module.exports = first
+
+/**
+ * Get the first event in a set of event emitters and event pairs.
+ *
+ * @param {array} stuff
+ * @param {function} done
+ * @public
+ */
+
+function first(stuff, done) {
+ if (!Array.isArray(stuff))
+ throw new TypeError('arg must be an array of [ee, events...] arrays')
+
+ var cleanups = []
+
+ for (var i = 0; i < stuff.length; i++) {
+ var arr = stuff[i]
+
+ if (!Array.isArray(arr) || arr.length < 2)
+ throw new TypeError('each array member must be [ee, events...]')
+
+ var ee = arr[0]
+
+ for (var j = 1; j < arr.length; j++) {
+ var event = arr[j]
+ var fn = listener(event, callback)
+
+ // listen to the event
+ ee.on(event, fn)
+ // push this listener to the list of cleanups
+ cleanups.push({
+ ee: ee,
+ event: event,
+ fn: fn,
+ })
+ }
+ }
+
+ function callback() {
+ cleanup()
+ done.apply(null, arguments)
+ }
+
+ function cleanup() {
+ var x
+ for (var i = 0; i < cleanups.length; i++) {
+ x = cleanups[i]
+ x.ee.removeListener(x.event, x.fn)
+ }
+ }
+
+ function thunk(fn) {
+ done = fn
+ }
+
+ thunk.cancel = cleanup
+
+ return thunk
+}
+
+/**
+ * Create the event listener.
+ * @private
+ */
+
+function listener(event, done) {
+ return function onevent(arg1) {
+ var args = new Array(arguments.length)
+ var ee = this
+ var err = event === 'error'
+ ? arg1
+ : null
+
+ // copy args to prevent arguments escaping scope
+ for (var i = 0; i < args.length; i++) {
+ args[i] = arguments[i]
+ }
+
+ done(err, ee, event, args)
+ }
+}
diff --git a/node_modules/ee-first/package.json b/node_modules/ee-first/package.json
new file mode 100644
index 0000000..b6d0b7d
--- /dev/null
+++ b/node_modules/ee-first/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "ee-first",
+ "description": "return the first event in a set of ee/event pairs",
+ "version": "1.1.1",
+ "author": {
+ "name": "Jonathan Ong",
+ "email": "me@jongleberry.com",
+ "url": "http://jongleberry.com",
+ "twitter": "https://twitter.com/jongleberry"
+ },
+ "contributors": [
+ "Douglas Christopher Wilson "
+ ],
+ "license": "MIT",
+ "repository": "jonathanong/ee-first",
+ "devDependencies": {
+ "istanbul": "0.3.9",
+ "mocha": "2.2.5"
+ },
+ "files": [
+ "index.js",
+ "LICENSE"
+ ],
+ "scripts": {
+ "test": "mocha --reporter spec --bail --check-leaks test/",
+ "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
+ "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
+ }
+}
diff --git a/node_modules/formidable/LICENSE b/node_modules/formidable/LICENSE
new file mode 100644
index 0000000..5e7ad11
--- /dev/null
+++ b/node_modules/formidable/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-present Felix Geisendörfer, and 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.
diff --git a/node_modules/formidable/README.md b/node_modules/formidable/README.md
new file mode 100644
index 0000000..de67598
--- /dev/null
+++ b/node_modules/formidable/README.md
@@ -0,0 +1,826 @@
+
+
+
+
+# formidable [![npm version][npmv-img]][npmv-url] [![MIT license][license-img]][license-url] [![Libera Manifesto][libera-manifesto-img]][libera-manifesto-url] [![Twitter][twitter-img]][twitter-url]
+
+> A Node.js module for parsing form data, especially file uploads.
+
+[![Code style][codestyle-img]][codestyle-url]
+[![codecoverage][codecov-img]][codecov-url]
+[![linux build status][linux-build-img]][build-url]
+[![windows build status][windows-build-img]][build-url]
+[![macos build status][macos-build-img]][build-url]
+
+If you have any _how-to_ kind of questions, please read the [Contributing
+Guide][contributing-url] and [Code of Conduct][code_of_conduct-url]
+documents. For bugs reports and feature requests, [please create an
+issue][open-issue-url] or ping [@tunnckoCore / @3a1FcBx0](https://twitter.com/3a1FcBx0)
+at Twitter.
+
+[![Conventional Commits][ccommits-img]][ccommits-url]
+[![Minimum Required Nodejs][nodejs-img]][npmv-url]
+[![Tidelift Subcsription][tidelift-img]][tidelift-url]
+[![Buy me a Kofi][kofi-img]][kofi-url]
+[![Renovate App Status][renovateapp-img]][renovateapp-url]
+[![Make A Pull Request][prs-welcome-img]][prs-welcome-url]
+
+This project is [semantically versioned](https://semver.org) and available as
+part of the [Tidelift Subscription][tidelift-url] for professional grade
+assurances, enhanced support and security.
+[Learn more.](https://tidelift.com/subscription/pkg/npm-formidable?utm_source=npm-formidable&utm_medium=referral&utm_campaign=enterprise)
+
+_The maintainers of `formidable` and thousands of other packages are working
+with Tidelift to deliver commercial support and maintenance for the Open Source
+dependencies you use to build your applications. Save time, reduce risk, and
+improve code health, while paying the maintainers of the exact dependencies you
+use._
+
+[![][npm-weekly-img]][npmv-url] [![][npm-monthly-img]][npmv-url]
+[![][npm-yearly-img]][npmv-url] [![][npm-alltime-img]][npmv-url]
+
+## Project Status: Maintained
+
+_Check [VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md) for more information on v1, v2, and v3 plans, NPM dist-tags and branches._
+
+This module was initially developed by
+[**@felixge**](https://github.com/felixge) for
+[Transloadit](http://transloadit.com/), a service focused on uploading and
+encoding images and videos. It has been battle-tested against hundreds of GBs of
+file uploads from a large variety of clients and is considered production-ready
+and is used in production for years.
+
+Currently, we are few maintainers trying to deal with it. :) More contributors
+are always welcome! :heart: Jump on
+[issue #412](https://github.com/felixge/node-formidable/issues/412) which is
+closed, but if you are interested we can discuss it and add you after strict
+rules, like enabling Two-Factor Auth in your npm and GitHub accounts.
+
+## Highlights
+
+- [Fast (~900-2500 mb/sec)](#benchmarks) & streaming multipart parser
+- Automatically writing file uploads to disk (optional, see
+ [`options.fileWriteStreamHandler`](#options))
+- [Plugins API](#useplugin-plugin) - allowing custom parsers and plugins
+- Low memory footprint
+- Graceful error handling
+- Very high test coverage
+
+## Install
+
+This project requires `Node.js >= 10.13`. Install it using
+[yarn](https://yarnpkg.com) or [npm](https://npmjs.com). _We highly
+recommend to use Yarn when you think to contribute to this project._
+
+This is a low-level package, and if you're using a high-level framework it _may_
+already be included. Check the examples below and the [examples/](https://github.com/node-formidable/formidable/tree/master/examples) folder.
+
+```sh
+# v2
+npm install formidable
+npm install formidable@latest
+npm install formidable@v2
+
+# or v3
+npm install formidable@v3
+```
+
+_**Note:** In near future v3 will be published on the `latest` NPM dist-tag. Future not ready releases will continue to be published on `canary` dist-tag._
+
+
+## Examples
+
+For more examples look at the `examples/` directory.
+
+### with Node.js http module
+
+Parse an incoming file upload, with the
+[Node.js's built-in `http` module](https://nodejs.org/api/http.html).
+
+```js
+const http = require('http');
+const formidable = require('formidable');
+
+const server = http.createServer((req, res) => {
+ if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
+ // parse a file upload
+ const form = formidable({ multiples: true });
+
+ form.parse(req, (err, fields, files) => {
+ if (err) {
+ res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
+ res.end(String(err));
+ return;
+ }
+ res.writeHead(200, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify({ fields, files }, null, 2));
+ });
+
+ return;
+ }
+
+ // show a file upload form
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(`
+
With Node.js "http" module
+
+ `);
+});
+
+server.listen(8080, () => {
+ console.log('Server listening on http://localhost:8080/ ...');
+});
+```
+
+### with Express.js
+
+There are multiple variants to do this, but Formidable just need Node.js Request
+stream, so something like the following example should work just fine, without
+any third-party [Express.js](https://ghub.now.sh/express) middleware.
+
+Or try the
+[examples/with-express.js](https://github.com/node-formidable/formidable/blob/master/examples/with-express.js)
+
+```js
+const express = require('express');
+const formidable = require('formidable');
+
+const app = express();
+
+app.get('/', (req, res) => {
+ res.send(`
+
With "express" npm package
+
+ `);
+});
+
+app.post('/api/upload', (req, res, next) => {
+ const form = formidable({ multiples: true });
+
+ form.parse(req, (err, fields, files) => {
+ if (err) {
+ next(err);
+ return;
+ }
+ res.json({ fields, files });
+ });
+});
+
+app.listen(3000, () => {
+ console.log('Server listening on http://localhost:3000 ...');
+});
+```
+
+### with Koa and Formidable
+
+Of course, with [Koa v1, v2 or future v3](https://ghub.now.sh/koa) the things
+are very similar. You can use `formidable` manually as shown below or through
+the [koa-better-body](https://ghub.now.sh/koa-better-body) package which is
+using `formidable` under the hood and support more features and different
+request bodies, check its documentation for more info.
+
+_Note: this example is assuming Koa v2. Be aware that you should pass `ctx.req`
+which is Node.js's Request, and **NOT** the `ctx.request` which is Koa's Request
+object - there is a difference._
+
+```js
+const Koa = require('koa');
+const formidable = require('formidable');
+
+const app = new Koa();
+
+app.on('error', (err) => {
+ console.error('server error', err);
+});
+
+app.use(async (ctx, next) => {
+ if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') {
+ const form = formidable({ multiples: true });
+
+ // not very elegant, but that's for now if you don't want to use `koa-better-body`
+ // or other middlewares.
+ await new Promise((resolve, reject) => {
+ form.parse(ctx.req, (err, fields, files) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ ctx.set('Content-Type', 'application/json');
+ ctx.status = 200;
+ ctx.state = { fields, files };
+ ctx.body = JSON.stringify(ctx.state, null, 2);
+ resolve();
+ });
+ });
+ await next();
+ return;
+ }
+
+ // show a file upload form
+ ctx.set('Content-Type', 'text/html');
+ ctx.status = 200;
+ ctx.body = `
+
With "koa" npm package
+
+ `;
+});
+
+app.use((ctx) => {
+ console.log('The next middleware is called');
+ console.log('Results:', ctx.state);
+});
+
+app.listen(3000, () => {
+ console.log('Server listening on http://localhost:3000 ...');
+});
+```
+
+## Benchmarks
+
+The benchmark is quite old, from the old codebase. But maybe quite true though.
+Previously the numbers was around ~500 mb/sec. Currently with moving to the new
+Node.js Streams API it's faster. You can clearly see the differences between the
+Node versions.
+
+_Note: a lot better benchmarking could and should be done in future._
+
+Benchmarked on 8GB RAM, Xeon X3440 (2.53 GHz, 4 cores, 8 threads)
+
+```
+~/github/node-formidable master
+❯ nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js
+
+ ⬢ Node 8
+
+1261.08 mb/sec
+
+ ⬢ Node 10
+
+1113.04 mb/sec
+
+ ⬢ Node 12
+
+2107.00 mb/sec
+
+ ⬢ Node 13
+
+2566.42 mb/sec
+```
+
+
+
+## API
+
+### Formidable / IncomingForm
+
+All shown are equivalent.
+
+_Please pass [`options`](#options) to the function/constructor, not by assigning
+them to the instance `form`_
+
+```js
+const formidable = require('formidable');
+const form = formidable(options);
+
+// or
+const { formidable } = require('formidable');
+const form = formidable(options);
+
+// or
+const { IncomingForm } = require('formidable');
+const form = new IncomingForm(options);
+
+// or
+const { Formidable } = require('formidable');
+const form = new Formidable(options);
+```
+
+### Options
+
+See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js)
+(the `DEFAULT_OPTIONS` constant).
+
+- `options.encoding` **{string}** - default `'utf-8'`; sets encoding for
+ incoming form fields,
+- `options.uploadDir` **{string}** - default `os.tmpdir()`; the directory for
+ placing file uploads in. You can move them later by using `fs.rename()`.
+- `options.keepExtensions` **{boolean}** - default `false`; to include the
+ extensions of the original files or not
+- `options.allowEmptyFiles` **{boolean}** - default `true`; allow upload empty
+ files
+- `options.minFileSize` **{number}** - default `1` (1byte); the minium size of
+ uploaded file.
+- `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb);
+ limit the size of uploaded file.
+- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set 0 for unlimited
+- `options.maxFieldsSize` **{number}** - default `20 * 1024 * 1024` (20mb);
+ limit the amount of memory all fields together (except files) can allocate in
+ bytes.
+- `options.hashAlgorithm` **{string | false}** - default `false`; include checksums calculated
+ for incoming files, set this to some hash algorithm, see
+ [crypto.createHash](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options)
+ for available algorithms
+- `options.fileWriteStreamHandler` **{function}** - default `null`, which by
+ default writes to host machine file system every file parsed; The function
+ should return an instance of a
+ [Writable stream](https://nodejs.org/api/stream.html#stream_class_stream_writable)
+ that will receive the uploaded file data. With this option, you can have any
+ custom behavior regarding where the uploaded file data will be streamed for.
+ If you are looking to write the file uploaded in other types of cloud storages
+ (AWS S3, Azure blob storage, Google cloud storage) or private file storage,
+ this is the option you're looking for. When this option is defined the default
+ behavior of writing the file in the host machine file system is lost.
+- `options.multiples` **{boolean}** - default `false`; when you call the
+ `.parse` method, the `files` argument (of the callback) will contain arrays of
+ files for inputs which submit multiple files using the HTML5 `multiple`
+ attribute. Also, the `fields` argument will contain arrays of values for
+ fields that have names ending with '[]'.
+- `options.filename` **{function}** - default `undefined` Use it to control
+ newFilename. Must return a string. Will be joined with options.uploadDir.
+
+- `options.filter` **{function}** - default function that always returns true.
+ Use it to filter files before they are uploaded. Must return a boolean.
+
+
+#### `options.filename` **{function}** function (name, ext, part, form) -> string
+
+_**Note:** If this size of combined fields, or size of some file is exceeded, an
+`'error'` event is fired._
+
+```js
+// The amount of bytes received for this form so far.
+form.bytesReceived;
+```
+
+```js
+// The expected number of bytes in this form.
+form.bytesExpected;
+```
+
+#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean
+
+**Note:** use an outside variable to cancel all uploads upon the first error
+
+```js
+const options = {
+ filter: function ({name, originalFilename, mimetype}) {
+ // keep only images
+ return mimetype && mimetype.includes("image");
+ }
+};
+```
+
+
+### .parse(request, callback)
+
+Parses an incoming Node.js `request` containing form data. If `callback` is
+provided, all fields and files are collected and passed to the callback.
+
+```js
+const formidable = require('formidable');
+
+const form = formidable({ multiples: true, uploadDir: __dirname });
+
+form.parse(req, (err, fields, files) => {
+ console.log('fields:', fields);
+ console.log('files:', files);
+});
+```
+
+You may overwrite this method if you are interested in directly accessing the
+multipart stream. Doing so will disable any `'field'` / `'file'` events
+processing which would occur otherwise, making you fully responsible for
+handling the processing.
+
+About `uploadDir`, given the following directory structure
+```
+project-name
+├── src
+│ └── server.js
+│
+└── uploads
+ └── image.jpg
+```
+
+`__dirname` would be the same directory as the source file itself (src)
+
+
+```js
+ `${__dirname}/../uploads`
+```
+
+to put files in uploads.
+
+Omitting `__dirname` would make the path relative to the current working directory. This would be the same if server.js is launched from src but not project-name.
+
+
+`null` will use default which is `os.tmpdir()`
+
+Note: If the directory does not exist, the uploaded files are __silently discarded__. To make sure it exists:
+
+```js
+import {createNecessaryDirectoriesSync} from "filesac";
+
+
+const uploadPath = `${__dirname}/../uploads`;
+createNecessaryDirectoriesSync(`${uploadPath}/x`);
+```
+
+
+In the example below, we listen on couple of events and direct them to the
+`data` listener, so you can do whatever you choose there, based on whether its
+before the file been emitted, the header value, the header name, on field, on
+file and etc.
+
+Or the other way could be to just override the `form.onPart` as it's shown a bit
+later.
+
+```js
+form.once('error', console.error);
+
+form.on('fileBegin', (formname, file) => {
+ form.emit('data', { name: 'fileBegin', formname, value: file });
+});
+
+form.on('file', (formname, file) => {
+ form.emit('data', { name: 'file', formname, value: file });
+});
+
+form.on('field', (fieldName, fieldValue) => {
+ form.emit('data', { name: 'field', key: fieldName, value: fieldValue });
+});
+
+form.once('end', () => {
+ console.log('Done!');
+});
+
+// If you want to customize whatever you want...
+form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => {
+ if (name === 'partBegin') {
+ }
+ if (name === 'partData') {
+ }
+ if (name === 'headerField') {
+ }
+ if (name === 'headerValue') {
+ }
+ if (name === 'headerEnd') {
+ }
+ if (name === 'headersEnd') {
+ }
+ if (name === 'field') {
+ console.log('field name:', key);
+ console.log('field value:', value);
+ }
+ if (name === 'file') {
+ console.log('file:', formname, value);
+ }
+ if (name === 'fileBegin') {
+ console.log('fileBegin:', formname, value);
+ }
+});
+```
+
+### .use(plugin: Plugin)
+
+A method that allows you to extend the Formidable library. By default we include
+4 plugins, which esentially are adapters to plug the different built-in parsers.
+
+**The plugins added by this method are always enabled.**
+
+_See [src/plugins/](./src/plugins/) for more detailed look on default plugins._
+
+The `plugin` param has such signature:
+
+```typescript
+function(formidable: Formidable, options: Options): void;
+```
+
+The architecture is simple. The `plugin` is a function that is passed with the
+Formidable instance (the `form` across the README examples) and the options.
+
+**Note:** the plugin function's `this` context is also the same instance.
+
+```js
+const formidable = require('formidable');
+
+const form = formidable({ keepExtensions: true });
+
+form.use((self, options) => {
+ // self === this === form
+ console.log('woohoo, custom plugin');
+ // do your stuff; check `src/plugins` for inspiration
+});
+
+form.parse(req, (error, fields, files) => {
+ console.log('done!');
+});
+```
+
+**Important to note**, is that inside plugin `this.options`, `self.options` and
+`options` MAY or MAY NOT be the same. General best practice is to always use the
+`this`, so you can later test your plugin independently and more easily.
+
+If you want to disable some parsing capabilities of Formidable, you can disable
+the plugin which corresponds to the parser. For example, if you want to disable
+multipart parsing (so the [src/parsers/Multipart.js](./src/parsers/Multipart.js)
+which is used in [src/plugins/multipart.js](./src/plugins/multipart.js)), then
+you can remove it from the `options.enabledPlugins`, like so
+
+```js
+const { Formidable } = require('formidable');
+
+const form = new Formidable({
+ hashAlgorithm: 'sha1',
+ enabledPlugins: ['octetstream', 'querystring', 'json'],
+});
+```
+
+**Be aware** that the order _MAY_ be important too. The names corresponds 1:1 to
+files in [src/plugins/](./src/plugins) folder.
+
+Pull requests for new built-in plugins MAY be accepted - for example, more
+advanced querystring parser. Add your plugin as a new file in `src/plugins/`
+folder (lowercased) and follow how the other plugins are made.
+
+### form.onPart
+
+If you want to use Formidable to only handle certain parts for you, you can do
+something similar. Or see
+[#387](https://github.com/node-formidable/node-formidable/issues/387) for
+inspiration, you can for example validate the mime-type.
+
+```js
+const form = formidable();
+
+form.onPart = (part) => {
+ part.on('data', (buffer) => {
+ // do whatever you want here
+ });
+};
+```
+
+For example, force Formidable to be used only on non-file "parts" (i.e., html
+fields)
+
+```js
+const form = formidable();
+
+form.onPart = function (part) {
+ // let formidable handle only non-file parts
+ if (part.originalFilename === '' || !part.mimetype) {
+ // used internally, please do not override!
+ form._handlePart(part);
+ }
+};
+```
+
+### File
+
+```ts
+export interface File {
+ // The size of the uploaded file in bytes.
+ // If the file is still being uploaded (see `'fileBegin'` event),
+ // this property says how many bytes of the file have been written to disk yet.
+ file.size: number;
+
+ // The path this file is being written to. You can modify this in the `'fileBegin'` event in
+ // case you are unhappy with the way formidable generates a temporary path for your files.
+ file.filepath: string;
+
+ // The name this file had according to the uploading client.
+ file.originalFilename: string | null;
+
+ // calculated based on options provided
+ file.newFilename: string | null;
+
+ // The mime type of this file, according to the uploading client.
+ file.mimetype: string | null;
+
+ // A Date object (or `null`) containing the time this file was last written to.
+ // Mostly here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
+ file.mtime: Date | null;
+
+ file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256'
+ // If `options.hashAlgorithm` calculation was set, you can read the hex digest out of this var (at the end it will be a string)
+ file.hash: string | object | null;
+}
+```
+
+#### file.toJSON()
+
+This method returns a JSON-representation of the file, allowing you to
+`JSON.stringify()` the file which is useful for logging and responding to
+requests.
+
+### Events
+
+#### `'progress'`
+
+Emitted after each incoming chunk of data that has been parsed. Can be used to
+roll your own progress bar.
+
+```js
+form.on('progress', (bytesReceived, bytesExpected) => {});
+```
+
+#### `'field'`
+
+Emitted whenever a field / value pair has been received.
+
+```js
+form.on('field', (name, value) => {});
+```
+
+#### `'fileBegin'`
+
+Emitted whenever a new file is detected in the upload stream. Use this event if
+you want to stream the file to somewhere else while buffering the upload on the
+file system.
+
+```js
+form.on('fileBegin', (formName, file) => {
+ // accessible here
+ // formName the name in the form () or http filename for octetstream
+ // file.originalFilename http filename or null if there was a parsing error
+ // file.newFilename generated hexoid or what options.filename returned
+ // file.filepath default pathnme as per options.uploadDir and options.filename
+ // file.filepath = CUSTOM_PATH // to change the final path
+});
+```
+
+#### `'file'`
+
+Emitted whenever a field / file pair has been received. `file` is an instance of
+`File`.
+
+```js
+form.on('file', (formname, file) => {
+ // same as fileBegin, except
+ // it is too late to change file.filepath
+ // file.hash is available if options.hash was used
+});
+```
+
+#### `'error'`
+
+Emitted when there is an error processing the incoming form. A request that
+experiences an error is automatically paused, you will have to manually call
+`request.resume()` if you want the request to continue firing `'data'` events.
+
+May have `error.httpCode` and `error.code` attached.
+
+```js
+form.on('error', (err) => {});
+```
+
+#### `'aborted'`
+
+Emitted when the request was aborted by the user. Right now this can be due to a
+'timeout' or 'close' event on the socket. After this event is emitted, an
+`error` event will follow. In the future there will be a separate 'timeout'
+event (needs a change in the node core).
+
+```js
+form.on('aborted', () => {});
+```
+
+#### `'end'`
+
+Emitted when the entire request has been received, and all contained files have
+finished flushing to disk. This is a great place for you to send your response.
+
+```js
+form.on('end', () => {});
+```
+
+## Changelog
+
+[./CHANGELOG.md](./CHANGELOG.md)
+
+## Ports & Credits
+
+- [multipart-parser](http://github.com/FooBarWidget/multipart-parser): a C++
+ parser based on formidable
+- [Ryan Dahl](http://twitter.com/ryah) for his work on
+ [http-parser](http://github.com/ry/http-parser) which heavily inspired the
+ initial `multipart_parser.js`.
+
+## Contributing
+
+If the documentation is unclear or has a typo, please click on the page's `Edit`
+button (pencil icon) and suggest a correction. If you would like to help us fix
+a bug or add a new feature, please check our [Contributing
+Guide][contributing-url]. Pull requests are welcome!
+
+Thanks goes to these wonderful people
+([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+