aboutsummaryrefslogtreecommitdiff
path: root/lib/Net.Http
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Net.Http')
-rw-r--r--lib/Net.Http/LICENSE.txt195
-rw-r--r--lib/Net.Http/readme.md0
-rw-r--r--lib/Net.Http/src/AlternateProtocolBase.cs96
-rw-r--r--lib/Net.Http/src/ConnectionInfo.cs166
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs170
-rw-r--r--lib/Net.Http/src/Core/HttpCookie.cs125
-rw-r--r--lib/Net.Http/src/Core/HttpEvent.cs141
-rw-r--r--lib/Net.Http/src/Core/HttpServerBase.cs312
-rw-r--r--lib/Net.Http/src/Core/HttpServerProcessing.cs387
-rw-r--r--lib/Net.Http/src/Core/IConnectionContext.cs62
-rw-r--r--lib/Net.Http/src/Core/IHttpEvent.cs104
-rw-r--r--lib/Net.Http/src/Core/IHttpLifeCycle.cs62
-rw-r--r--lib/Net.Http/src/Core/IHttpResponseBody.cs73
-rw-r--r--lib/Net.Http/src/Core/Request/HttpInputStream.cs222
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequest.cs284
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestBody.cs70
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs304
-rw-r--r--lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs533
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs228
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkedStream.cs252
-rw-r--r--lib/Net.Http/src/Core/Response/DirectStream.cs96
-rw-r--r--lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs157
-rw-r--r--lib/Net.Http/src/Core/Response/HttpContextExtensions.cs124
-rw-r--r--lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs253
-rw-r--r--lib/Net.Http/src/Core/Response/HttpResponse.cs307
-rw-r--r--lib/Net.Http/src/Core/Response/ResponseWriter.cs182
-rw-r--r--lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs85
-rw-r--r--lib/Net.Http/src/Core/VnHeaderCollection.cs75
-rw-r--r--lib/Net.Http/src/Exceptions/ContentTypeException.cs43
-rw-r--r--lib/Net.Http/src/Exceptions/TerminateConnectionException.cs57
-rw-r--r--lib/Net.Http/src/FileUpload.cs122
-rw-r--r--lib/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs53
-rw-r--r--lib/Net.Http/src/Helpers/ContentType.cs1180
-rw-r--r--lib/Net.Http/src/Helpers/CoreBufferHelpers.cs188
-rw-r--r--lib/Net.Http/src/Helpers/HelperTypes.cs138
-rw-r--r--lib/Net.Http/src/Helpers/HttpHelpers.cs445
-rw-r--r--lib/Net.Http/src/Helpers/MimeLookups.cs3237
-rw-r--r--lib/Net.Http/src/Helpers/TransportReader.cs114
-rw-r--r--lib/Net.Http/src/Helpers/VnWebHeaderCollection.cs43
-rw-r--r--lib/Net.Http/src/Helpers/WebHeaderExtensions.cs60
-rw-r--r--lib/Net.Http/src/HttpConfig.cs154
-rw-r--r--lib/Net.Http/src/IAlternateProtocol.cs46
-rw-r--r--lib/Net.Http/src/IConnectionInfo.cs150
-rw-r--r--lib/Net.Http/src/IHeaderCollection.cs85
-rw-r--r--lib/Net.Http/src/IMemoryResponseEntity.cs69
-rw-r--r--lib/Net.Http/src/ITransportContext.cs71
-rw-r--r--lib/Net.Http/src/ITransportProvider.cs51
-rw-r--r--lib/Net.Http/src/IWebRoot.cs58
-rw-r--r--lib/Net.Http/src/TransportSecurityInfo.cs121
-rw-r--r--lib/Net.Http/src/VNLib.Net.Http.csproj57
50 files changed, 11607 insertions, 0 deletions
diff --git a/lib/Net.Http/LICENSE.txt b/lib/Net.Http/LICENSE.txt
new file mode 100644
index 0000000..147bcd6
--- /dev/null
+++ b/lib/Net.Http/LICENSE.txt
@@ -0,0 +1,195 @@
+Copyright (c) 2022 Vaughn Nugent
+
+Contact information
+ Name: Vaughn Nugent
+ Email: public[at]vaughnnugent[dot]com
+ Website: https://www.vaughnnugent.com
+
+The software in this repository is licensed under the GNU Affero GPL version 3.0 (or any later version).
+
+GNU AFFERO GENERAL PUBLIC LICENSE
+
+Version 3, 19 November 2007
+
+Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
+Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+Preamble
+
+The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
+
+The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
+
+When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
+
+Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
+
+A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
+
+The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
+
+An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
+
+The precise terms and conditions for copying, distribution and modification follow.
+TERMS AND CONDITIONS
+0. Definitions.
+
+"This License" refers to version 3 of the GNU Affero General Public License.
+
+"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
+
+"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
+
+To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
+
+A "covered work" means either the unmodified Program or a work based on the Program.
+
+To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
+
+To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
+1. Source Code.
+
+The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
+
+A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
+
+The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
+
+The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same work.
+2. Basic Permissions.
+
+All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
+3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
+
+When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
+4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
+5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
+ b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
+ c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
+ d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
+
+A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
+6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
+ b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
+ c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
+ d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
+ e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
+
+A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
+
+"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
+
+The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
+
+Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
+7. Additional Terms.
+
+"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
+ b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
+ c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
+ d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
+ e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
+ f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
+
+All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
+8. Termination.
+
+You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
+
+However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
+
+Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
+9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
+10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
+11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
+12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
+13. Remote Network Interaction; Use with the GNU General Public License.
+
+Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
+
+Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
+14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
+
+Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
+15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS \ No newline at end of file
diff --git a/lib/Net.Http/readme.md b/lib/Net.Http/readme.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/Net.Http/readme.md
diff --git a/lib/Net.Http/src/AlternateProtocolBase.cs b/lib/Net.Http/src/AlternateProtocolBase.cs
new file mode 100644
index 0000000..929bc33
--- /dev/null
+++ b/lib/Net.Http/src/AlternateProtocolBase.cs
@@ -0,0 +1,96 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: AlternateProtocolBase.cs
+*
+* AlternateProtocolBase.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// A base class for all non-http protocol handlers
+ /// </summary>
+ public abstract class AlternateProtocolBase : MarshalByRefObject, IAlternateProtocol
+ {
+ /// <summary>
+ /// A cancelation source that allows for canceling running tasks, that is linked
+ /// to the server that called <see cref="RunAsync(Stream)"/>.
+ /// </summary>
+ /// <remarks>
+ /// This property is only available while the <see cref="RunAsync(Stream)"/>
+ /// method is executing
+ /// </remarks>
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ protected CancellationTokenSource CancelSource { get; private set; }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ ///<inheritdoc/>
+ async Task IAlternateProtocol.RunAsync(Stream transport, CancellationToken handlerToken)
+ {
+ //Create new cancel source
+ CancelSource ??= new();
+ //Register the token to cancel the source and save the registration for unregister on dispose
+ CancellationTokenRegistration Registration = handlerToken.Register(CancelSource.Cancel);
+ try
+ {
+ //Call child initialize method
+ await RunAsync(new AlternateProtocolTransportStreamWrapper(transport));
+ CancelSource.Cancel();
+ }
+ finally
+ {
+ //dispose the cancelation registration
+ await Registration.DisposeAsync();
+ //Dispose cancel source
+ CancelSource.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Is the current socket connected using transport security
+ /// </summary>
+ public virtual bool IsSecure { get; init; }
+
+ /// <summary>
+ /// Determines if the instance is pending cancelation
+ /// </summary>
+ public bool IsCancellationRequested => CancelSource.IsCancellationRequested;
+
+ /// <summary>
+ /// Cancels all pending operations. This session will be unusable after this function is called
+ /// </summary>
+ public virtual void CancelAll() => CancelSource?.Cancel();
+
+ /// <summary>
+ /// Called when the protocol swtich handshake has completed and the transport is
+ /// available for the new protocol
+ /// </summary>
+ /// <param name="transport">The transport stream</param>
+ /// <returns>A task that represents the active use of the transport, and when complete all operations are unwound</returns>
+ protected abstract Task RunAsync(Stream transport);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/ConnectionInfo.cs b/lib/Net.Http/src/ConnectionInfo.cs
new file mode 100644
index 0000000..6e1660d
--- /dev/null
+++ b/lib/Net.Http/src/ConnectionInfo.cs
@@ -0,0 +1,166 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ConnectionInfo.cs
+*
+* ConnectionInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+using VNLib.Net.Http.Core;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http
+{
+ ///<inheritdoc/>
+ internal sealed class ConnectionInfo : IConnectionInfo
+ {
+ private HttpContext Context;
+
+ ///<inheritdoc/>
+ public Uri RequestUri => Context.Request.Location;
+ ///<inheritdoc/>
+ public string Path => RequestUri.LocalPath;
+ ///<inheritdoc/>
+ public string? UserAgent => Context.Request.UserAgent;
+ ///<inheritdoc/>
+ public IHeaderCollection Headers { get; private set; }
+ ///<inheritdoc/>
+ public bool CrossOrigin { get; }
+ ///<inheritdoc/>
+ public bool IsWebSocketRequest { get; }
+ ///<inheritdoc/>
+ public ContentType ContentType => Context.Request.ContentType;
+ ///<inheritdoc/>
+ public HttpMethod Method => Context.Request.Method;
+ ///<inheritdoc/>
+ public HttpVersion ProtocolVersion => Context.Request.HttpVersion;
+ ///<inheritdoc/>
+ public bool IsSecure => Context.Request.EncryptionVersion != SslProtocols.None;
+ ///<inheritdoc/>
+ public SslProtocols SecurityProtocol => Context.Request.EncryptionVersion;
+ ///<inheritdoc/>
+ public Uri? Origin => Context.Request.Origin;
+ ///<inheritdoc/>
+ public Uri? Referer => Context.Request.Referrer;
+ ///<inheritdoc/>
+ public Tuple<long, long>? Range => Context.Request.Range;
+ ///<inheritdoc/>
+ public IPEndPoint LocalEndpoint => Context.Request.LocalEndPoint;
+ ///<inheritdoc/>
+ public IPEndPoint RemoteEndpoint => Context.Request.RemoteEndPoint;
+ ///<inheritdoc/>
+ public Encoding Encoding => Context.ParentServer.Config.HttpEncoding;
+ ///<inheritdoc/>
+ public IReadOnlyDictionary<string, string> RequestCookies => Context.Request.Cookies;
+ ///<inheritdoc/>
+ public IEnumerable<string> Accept => Context.Request.Accept;
+ ///<inheritdoc/>
+ public TransportSecurityInfo? TransportSecurity => Context.GetSecurityInfo();
+
+ ///<inheritdoc/>
+ public bool Accepts(ContentType type)
+ {
+ //Get the content type string from he specified content type
+ string contentType = HttpHelpers.GetContentTypeString(type);
+ return Accepts(contentType);
+ }
+ ///<inheritdoc/>
+ public bool Accepts(string contentType)
+ {
+ if (AcceptsAny())
+ {
+ return true;
+ }
+
+ //If client accepts exact requested encoding
+ if (Accept.Contains(contentType))
+ {
+ return true;
+ }
+
+ //Search accept types to determine if the content type is acceptable
+ bool accepted = Accept
+ .Where(ctype =>
+ {
+ //Get prinary side of mime type
+ ReadOnlySpan<char> primary = contentType.AsSpan().SliceBeforeParam('/');
+ ReadOnlySpan<char> ctSubType = ctype.AsSpan().SliceBeforeParam('/');
+ //See if accepts any subtype, or the primary sub-type matches
+ return ctSubType[0] == '*' || ctSubType.Equals(primary, StringComparison.OrdinalIgnoreCase);
+ }).Any();
+ return accepted;
+ }
+ /// <summary>
+ /// Determines if the connection accepts any content type
+ /// </summary>
+ /// <returns>true if the connection accepts any content typ, false otherwise</returns>
+ private bool AcceptsAny()
+ {
+ //Accept any if no accept header was present, or accept all value */*
+ return Context.Request.Accept.Count == 0 || Accept.Where(static t => t.StartsWith("*/*", StringComparison.OrdinalIgnoreCase)).Any();
+ }
+ ///<inheritdoc/>
+ public void SetCookie(string name, string value, string? domain, string? path, TimeSpan Expires, CookieSameSite sameSite, bool httpOnly, bool secure)
+ {
+ //Create the new cookie
+ HttpCookie cookie = new(name)
+ {
+ Value = value,
+ Domain = domain,
+ Path = path,
+ MaxAge = Expires,
+ //Set the session lifetime flag if the timeout is max value
+ IsSession = Expires == TimeSpan.MaxValue,
+ //If the connection is cross origin, then we need to modify the secure and samsite values
+ SameSite = CrossOrigin ? CookieSameSite.None : sameSite,
+ Secure = secure | CrossOrigin,
+ HttpOnly = httpOnly
+ };
+ //Set the cookie
+ Context.Response.AddCookie(cookie);
+ }
+
+ internal ConnectionInfo(HttpContext ctx)
+ {
+ //Create new header collection
+ Headers = new VnHeaderCollection(ctx);
+ //set co value
+ CrossOrigin = ctx.Request.IsCrossOrigin();
+ //Set websocket status
+ IsWebSocketRequest = ctx.Request.IsWebSocketRequest();
+ //Update the context referrence
+ Context = ctx;
+ }
+
+#nullable disable
+ internal void Clear()
+ {
+ Context = null;
+ (Headers as VnHeaderCollection).Clear();
+ Headers = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
new file mode 100644
index 0000000..43d1975
--- /dev/null
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -0,0 +1,170 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpContext.cs
+*
+* HttpContext.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory.Caching;
+
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed partial class HttpContext : IConnectionContext, IReusable
+ {
+ /// <summary>
+ /// When set as a response flag, disables response compression for
+ /// the current request/response flow
+ /// </summary>
+ public const ulong COMPRESSION_DISABLED_MSK = 0x01UL;
+
+ /// <summary>
+ /// The reusable http request container
+ /// </summary>
+ public readonly HttpRequest Request;
+ /// <summary>
+ /// The reusable response controler
+ /// </summary>
+ public readonly HttpResponse Response;
+ /// <summary>
+ /// The http server that this context is bound to
+ /// </summary>
+ public readonly HttpServer ParentServer;
+ /// <summary>
+ /// The shared transport header reader buffer
+ /// </summary>
+ public readonly SharedHeaderReaderBuffer RequestBuffer;
+
+ /// <summary>
+ /// The response entity body container
+ /// </summary>
+ public readonly IHttpResponseBody ResponseBody;
+
+ /// <summary>
+ /// A collection of flags that can be used to control the way the context
+ /// responds to client requests
+ /// </summary>
+ public readonly BitField ContextFlags;
+
+ /// <summary>
+ /// Gets or sets the alternate application protocol to swtich to
+ /// </summary>
+ public IAlternateProtocol? AlternateProtocol { get; set; }
+
+ private readonly ResponseWriter responseWriter;
+ private ITransportContext? _ctx;
+
+ public HttpContext(HttpServer server)
+ {
+ /*
+ * Local method for retreiving the transport stream,
+ * this adds protection/debug from response/request
+ * containers not allowed to maintain referrences
+ * to a transport stream after it has been released
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ Stream GetStream() => _ctx!.ConnectionStream;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ HttpVersion GetVersion() => Request.HttpVersion;
+
+ ParentServer = server;
+
+ //Create new request
+ Request = new HttpRequest(GetStream);
+
+ //create a new response object
+ Response = new HttpResponse(
+ server.Config.HttpEncoding,
+ ParentServer.Config.ResponseHeaderBufferSize,
+ ParentServer.Config.ChunkedResponseAccumulatorSize,
+ GetStream,
+ GetVersion);
+
+ //The shared request parsing buffer
+ RequestBuffer = new(server.Config.HeaderBufferSize);
+
+ //Init response writer
+ ResponseBody = responseWriter = new ResponseWriter();
+
+ ContextFlags = new(0);
+ }
+
+ public TransportSecurityInfo? GetSecurityInfo() => _ctx?.GetSecurityInfo();
+
+
+ #region LifeCycle Hooks
+
+ ///<inheritdoc/>
+ public void InitializeContext(ITransportContext ctx) => _ctx = ctx;
+
+ ///<inheritdoc/>
+ public void BeginRequest()
+ {
+ //Clear all flags
+ ContextFlags.ClearAll();
+
+ //Lifecycle on new request
+ Request.OnNewRequest();
+ Response.OnNewRequest();
+ RequestBuffer.OnNewRequest();
+
+ //Initialize the request
+ Request.Initialize(_ctx!, ParentServer.Config.DefaultHttpVersion);
+ }
+
+ ///<inheritdoc/>
+ public void EndRequest()
+ {
+ AlternateProtocol = null;
+
+ Request.OnComplete();
+ Response.OnComplete();
+ RequestBuffer.OnComplete();
+ responseWriter.OnComplete();
+ }
+
+ void IReusable.Prepare()
+ {
+ Request.OnPrepare();
+ Response.OnPrepare();
+ RequestBuffer.OnPrepare();
+ }
+
+ bool IReusable.Release()
+ {
+ _ctx = null;
+
+ //Release response/requqests
+ Request.OnRelease();
+ Response.OnRelease();
+ RequestBuffer.OnRelease();
+
+ return true;
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpCookie.cs b/lib/Net.Http/src/Core/HttpCookie.cs
new file mode 100644
index 0000000..c48ad00
--- /dev/null
+++ b/lib/Net.Http/src/Core/HttpCookie.cs
@@ -0,0 +1,125 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpCookie.cs
+*
+* HttpCookie.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed class HttpCookie : IStringSerializeable, IEquatable<HttpCookie>
+ {
+ public string Name { get; }
+ public string? Value { get; init; }
+ public string? Domain { get; init; }
+ public string? Path { get; init; }
+ public TimeSpan MaxAge { get; init; }
+ public CookieSameSite SameSite { get; init; }
+ public bool Secure { get; init; }
+ public bool HttpOnly { get; init; }
+ public bool IsSession { get; init; }
+
+ public HttpCookie(string name)
+ {
+ this.Name = name;
+ }
+
+ public string Compile()
+ {
+ throw new NotImplementedException();
+ }
+ public void Compile(ref ForwardOnlyWriter<char> writer)
+ {
+ //set the name of the cookie
+ writer.Append(Name);
+ writer.Append('=');
+ //set name
+ writer.Append(Value);
+ //Only set the max age parameter if the cookie is not a session cookie
+ if (!IsSession)
+ {
+ writer.Append("; Max-Age=");
+ writer.Append((int)MaxAge.TotalSeconds);
+ }
+ //Make sure domain is set
+ if (!string.IsNullOrWhiteSpace(Domain))
+ {
+ writer.Append("; Domain=");
+ writer.Append(Domain);
+ }
+ //Check and set path
+ if (!string.IsNullOrWhiteSpace(Path))
+ {
+ //Set path
+ writer.Append("; Path=");
+ writer.Append(Path);
+ }
+ writer.Append("; SameSite=");
+ //Set the samesite flag based on the enum value
+ switch (SameSite)
+ {
+ case CookieSameSite.None:
+ writer.Append("None");
+ break;
+ case CookieSameSite.SameSite:
+ writer.Append("Strict");
+ break;
+ case CookieSameSite.Lax:
+ default:
+ writer.Append("Lax");
+ break;
+ }
+ //Set httponly flag
+ if (HttpOnly)
+ {
+ writer.Append("; HttpOnly");
+ }
+ //Set secure flag
+ if (Secure)
+ {
+ writer.Append("; Secure");
+ }
+ }
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+
+ public override int GetHashCode() => Name.GetHashCode();
+
+ public override bool Equals(object? obj)
+ {
+ return obj is HttpCookie other && Equals(other);
+ }
+
+ public bool Equals(HttpCookie? other)
+ {
+ return other != null && Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpEvent.cs b/lib/Net.Http/src/Core/HttpEvent.cs
new file mode 100644
index 0000000..7d7c1e7
--- /dev/null
+++ b/lib/Net.Http/src/Core/HttpEvent.cs
@@ -0,0 +1,141 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpEvent.cs
+*
+* HttpEvent.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+ internal sealed class HttpEvent : MarshalByRefObject, IHttpEvent
+ {
+ private HttpContext Context;
+ private ConnectionInfo _ci;
+
+ internal HttpEvent(HttpContext ctx)
+ {
+ Context = ctx;
+ _ci = new ConnectionInfo(ctx);
+ }
+
+ ///<inheritdoc/>
+ IConnectionInfo IHttpEvent.Server => _ci;
+
+ ///<inheritdoc/>
+ HttpServer IHttpEvent.OriginServer => Context.ParentServer;
+
+ ///<inheritdoc/>
+ IReadOnlyDictionary<string, string> IHttpEvent.QueryArgs => Context.Request.RequestBody.QueryArgs;
+ ///<inheritdoc/>
+ IReadOnlyDictionary<string, string> IHttpEvent.RequestArgs => Context.Request.RequestBody.RequestArgs;
+ ///<inheritdoc/>
+ IReadOnlyList<FileUpload> IHttpEvent.Files => Context.Request.RequestBody.Uploads;
+
+ ///<inheritdoc/>
+ void IHttpEvent.DisableCompression() => Context.ContextFlags.Set(HttpContext.COMPRESSION_DISABLED_MSK);
+
+ ///<inheritdoc/>
+ void IHttpEvent.DangerousChangeProtocol(IAlternateProtocol protocolHandler)
+ {
+ if(Context.AlternateProtocol != null)
+ {
+ throw new InvalidOperationException("A protocol handler was already specified");
+ }
+
+ _ = protocolHandler ?? throw new ArgumentNullException(nameof(protocolHandler));
+
+ //Set 101 status code
+ Context.Respond(HttpStatusCode.SwitchingProtocols);
+ Context.AlternateProtocol = protocolHandler;
+ }
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IHttpEvent.CloseResponse(HttpStatusCode code) => Context.Respond(code);
+
+ ///<inheritdoc/>
+ void IHttpEvent.CloseResponse(HttpStatusCode code, ContentType type, Stream stream)
+ {
+ //Check if the stream is valid. We will need to read the stream, and we will also need to get the length property
+ if (!stream.CanSeek || !stream.CanRead)
+ {
+ throw new IOException("The stream.Length property must be available and the stream must be readable");
+ }
+
+ //If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
+ if (stream.Length == 0)
+ {
+ return;
+ }
+
+ //Set status code
+ Context.Response.SetStatusCode(code);
+
+ //Finally store the stream input
+ if(!(Context.ResponseBody as ResponseWriter)!.TrySetResponseBody(stream))
+ {
+ throw new InvalidOperationException("A response body has already been set");
+ }
+
+ //Set content type header after body
+ Context.Response.Headers[HttpResponseHeader.ContentType] = HttpHelpers.GetContentTypeString(type);
+ }
+
+ ///<inheritdoc/>
+ void IHttpEvent.CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity)
+ {
+ //If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
+ if (entity.Remaining == 0)
+ {
+ return;
+ }
+
+ //Set status code
+ Context.Response.SetStatusCode(code);
+
+ //Finally store the stream input
+ if (!(Context.ResponseBody as ResponseWriter)!.TrySetResponseBody(entity))
+ {
+ throw new InvalidOperationException("A response body has already been set");
+ }
+
+ //Set content type header after body
+ Context.Response.Headers[HttpResponseHeader.ContentType] = HttpHelpers.GetContentTypeString(type);
+ }
+
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+ internal void Clear()
+ {
+ //Clean up referrence types and cleanable objects
+ Context = null;
+ _ci.Clear();
+ _ci = null;
+ }
+#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs
new file mode 100644
index 0000000..d3f5e00
--- /dev/null
+++ b/lib/Net.Http/src/Core/HttpServerBase.cs
@@ -0,0 +1,312 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpServerBase.cs
+*
+* HttpServerBase.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This file is the base of the HTTP server class that provides
+ * consts, statics, fields, and properties of the HttpServer class.
+ *
+ * Processing of HTTP connections and entities is contained in the
+ * processing partial file.
+ *
+ * Processing is configured to be asynchronous, utilizing .NETs
+ * asynchronous compilation services. To facilitate this but continue
+ * to use object caching, reusable stores must be usable across threads
+ * to function safely with async programming practices.
+ */
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+using VNLib.Utils.Logging;
+using VNLib.Utils.Memory.Caching;
+
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+
+ /// <summary>
+ /// Provides a resource efficient, high performance, single library HTTP(s) server,
+ /// with extensable processors and transport providers.
+ /// This class cannot be inherited
+ /// </summary>
+ public sealed partial class HttpServer : ICacheHolder
+ {
+ /// <summary>
+ /// The host key that determines a "wildcard" host, meaning the
+ /// default connection handler when an incomming connection has
+ /// not specific route
+ /// </summary>
+ public const string WILDCARD_KEY = "*";
+
+ private readonly ITransportProvider Transport;
+ private readonly IReadOnlyDictionary<string, IWebRoot> ServerRoots;
+
+ #region caches
+ /// <summary>
+ /// The cached HTTP1/1 keepalive timeout header value
+ /// </summary>
+ private readonly string KeepAliveTimeoutHeaderValue;
+ /// <summary>
+ /// Reusable store for obtaining <see cref="HttpContext"/>
+ /// </summary>
+ private readonly ObjectRental<HttpContext> ContextStore;
+ /// <summary>
+ /// The cached header-line termination value
+ /// </summary>
+ private readonly ReadOnlyMemory<byte> HeaderLineTermination;
+ #endregion
+
+ /// <summary>
+ /// The <see cref="HttpConfig"/> for the current server
+ /// </summary>
+ public HttpConfig Config { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the server is listening for connections
+ /// </summary>
+ public bool Running { get; private set; }
+
+ private CancellationTokenSource? StopToken;
+
+ /// <summary>
+ /// Creates a new <see cref="HttpServer"/> with the specified configration copy (using struct).
+ /// Immutable data structures are initialzed.
+ /// </summary>
+ /// <param name="config">The configuration used to create the instance</param>
+ /// <param name="transport">The transport provider to listen to connections from</param>
+ /// <param name="sites">A collection of <see cref="IWebRoot"/>s that route incomming connetctions</param>
+ /// <exception cref="ArgumentException"></exception>
+ public HttpServer(HttpConfig config, ITransportProvider transport, IEnumerable<IWebRoot> sites)
+ {
+ //Validate the configuration
+ ValidateConfig(in config);
+
+ Config = config;
+ //Configure roots and their directories
+ ServerRoots = sites.ToDictionary(static r => r.Hostname, static tv => tv, StringComparer.OrdinalIgnoreCase);
+ //Compile and store the timeout keepalive header
+ KeepAliveTimeoutHeaderValue = $"timeout={(int)Config.ConnectionKeepAlive.TotalSeconds}";
+ //Store termination for the current instance
+ HeaderLineTermination = config.HttpEncoding.GetBytes(HttpHelpers.CRLF);
+ //Create a new context store
+ ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this));
+ //Setup config copy with the internal http pool
+ Transport = transport;
+ }
+
+ private static void ValidateConfig(in HttpConfig conf)
+ {
+ _ = conf.HttpEncoding ?? throw new ArgumentException("HttpEncoding cannot be null", nameof(conf));
+ _ = conf.ServerLog ?? throw new ArgumentException("ServerLog cannot be null", nameof(conf));
+
+ if (conf.ActiveConnectionRecvTimeout < -1)
+ {
+ throw new ArgumentException("ActiveConnectionRecvTimeout cannot be less than -1", nameof(conf));
+ }
+
+ //Chunked data accumulator must be at least 64 bytes (arbinrary value)
+ if (conf.ChunkedResponseAccumulatorSize < 64 || conf.ChunkedResponseAccumulatorSize == int.MaxValue)
+ {
+ throw new ArgumentException("ChunkedResponseAccumulatorSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.CompressionLimit < 0)
+ {
+ throw new ArgumentException("CompressionLimit cannot be less than 0, set to 0 to disable response compression", nameof(conf));
+ }
+
+ if (conf.ConnectionKeepAlive < TimeSpan.Zero)
+ {
+ throw new ArgumentException("ConnectionKeepAlive cannot be less than 0", nameof(conf));
+ }
+
+ if (conf.DefaultHttpVersion == HttpVersion.None)
+ {
+ throw new ArgumentException("DefaultHttpVersion cannot be NotSupported", nameof(conf));
+ }
+
+ if (conf.DiscardBufferSize < 64)
+ {
+ throw new ArgumentException("DiscardBufferSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.FormDataBufferSize < 64)
+ {
+ throw new ArgumentException("FormDataBufferSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.HeaderBufferSize < 128)
+ {
+ throw new ArgumentException("HeaderBufferSize cannot be less than 128 bytes", nameof(conf));
+ }
+
+ if (conf.MaxFormDataUploadSize < 0)
+ {
+ throw new ArgumentException("MaxFormDataUploadSize cannot be less than 0, set to 0 to disable form-data uploads", nameof(conf));
+ }
+
+ if (conf.MaxOpenConnections < 0)
+ {
+ throw new ArgumentException("MaxOpenConnections cannot be less than 0", nameof(conf));
+ }
+
+ if (conf.MaxRequestHeaderCount < 1)
+ {
+ throw new ArgumentException("MaxRequestHeaderCount cannot be less than 1", nameof(conf));
+ }
+
+ if (conf.MaxUploadSize < 0)
+ {
+ throw new ArgumentException("MaxUploadSize cannot be less than 0", nameof(conf));
+ }
+
+ if (conf.ResponseBufferSize < 64)
+ {
+ throw new ArgumentException("ResponseBufferSize cannot be less than 64 bytes", nameof(conf));
+ }
+
+ if (conf.ResponseHeaderBufferSize < 128)
+ {
+ throw new ArgumentException("ResponseHeaderBufferSize cannot be less than 128 bytes", nameof(conf));
+ }
+
+ if (conf.SendTimeout < 1)
+ {
+ throw new ArgumentException("SendTimeout cannot be less than 1 millisecond", nameof(conf));
+ }
+ }
+
+ /// <summary>
+ /// Begins listening for connections on configured interfaces for configured hostnames.
+ /// </summary>
+ /// <param name="token">A token used to stop listening for incomming connections and close all open websockets</param>
+ /// <returns>A task that resolves when the server has exited</returns>
+ /// <exception cref="SocketException"></exception>
+ /// <exception cref="ThreadStateException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public Task Start(CancellationToken token)
+ {
+ StopToken = CancellationTokenSource.CreateLinkedTokenSource(token);
+ //Start servers with the new token source
+ Transport.Start(token);
+ //Start the listen task
+ return Task.Run(ListenWorkerDoWork, token);
+ }
+
+ /*
+ * An SslStream may throw a win32 exception with HRESULT 0x80090327
+ * when processing a client certificate (I believe anyway) only
+ * an issue on some clients (browsers)
+ */
+
+ private const int UKNOWN_CERT_AUTH_HRESULT = unchecked((int)0x80090327);
+
+ /// <summary>
+ /// An invlaid frame size may happen if data is recieved on an open socket
+ /// but does not contain valid SSL handshake data
+ /// </summary>
+ private const int INVALID_FRAME_HRESULT = unchecked((int)0x80131620);
+
+ /*
+ * A worker task that listens for connections from the transport
+ */
+ private async Task ListenWorkerDoWork()
+ {
+ //Set running flag
+ Running = true;
+
+ Config.ServerLog.Information("HTTP server {hc} listening for connections", GetHashCode());
+ //Listen for connections until canceled
+ while (true)
+ {
+ try
+ {
+ //Listen for new connection
+ ITransportContext ctx = await Transport.AcceptAsync(StopToken!.Token);
+ //Try to dispatch the recieved event
+ _ = DataReceivedAsync(ctx).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ //Closing, exit loop
+ break;
+ }
+ catch (AuthenticationException ae)
+ {
+ Config.ServerLog.Error(ae);
+ }
+ catch (Exception ex)
+ {
+ Config.ServerLog.Error(ex);
+ }
+ }
+ //Clear all caches
+ CacheHardClear();
+ //Clear running flag
+ Running = false;
+ Config.ServerLog.Information("HTTP server {hc} exiting", GetHashCode());
+ }
+
+
+ ///<inheritdoc/>
+ ///<exception cref="ObjectDisposedException"></exception>
+ public void CacheClear() => ContextStore.CacheClear();
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public void CacheHardClear() => ContextStore.CacheHardClear();
+
+ /// <summary>
+ /// Writes the specialized log for a socket exception
+ /// </summary>
+ /// <param name="se">The socket exception to log</param>
+ public void WriteSocketExecption(SocketException se)
+ {
+ //When clause guards nulls
+ switch (se.SocketErrorCode)
+
+ {
+ //Ignore aborted messages
+ case SocketError.ConnectionAborted:
+ return;
+ case SocketError.ConnectionReset:
+ Config.ServerLog.Debug("Connecion reset by client");
+ return;
+ case SocketError.TimedOut:
+ Config.ServerLog.Debug("Socket operation timed out");
+ return;
+ default:
+ Config.ServerLog.Information(se);
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/HttpServerProcessing.cs b/lib/Net.Http/src/Core/HttpServerProcessing.cs
new file mode 100644
index 0000000..881b66c
--- /dev/null
+++ b/lib/Net.Http/src/Core/HttpServerProcessing.cs
@@ -0,0 +1,387 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpServerProcessing.cs
+*
+* HttpServerProcessing.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Logging;
+using VNLib.Net.Http.Core;
+
+namespace VNLib.Net.Http
+{
+ public sealed partial class HttpServer
+ {
+
+ private int OpenConnectionCount;
+
+ //Event handler method for processing incoming data events
+ private async Task DataReceivedAsync(ITransportContext transportContext)
+ {
+ //Increment open connection count
+ Interlocked.Increment(ref OpenConnectionCount);
+
+ //Rent a new context object to reuse
+ HttpContext context = ContextStore.Rent();
+
+ try
+ {
+ //Set write timeout
+ transportContext.ConnectionStream.WriteTimeout = Config.SendTimeout;
+
+ //Init stream
+ context.InitializeContext(transportContext);
+
+ //Keep the transport open and listen for messages as long as keepalive is enabled
+ do
+ {
+ //Set rx timeout low for initial reading
+ transportContext.ConnectionStream.ReadTimeout = Config.ActiveConnectionRecvTimeout;
+
+ //Process the request
+ ERRNO keepalive = await ProcessHttpEventAsync(transportContext, context);
+
+ //If the connection is closed, we can return
+ if (!keepalive)
+ {
+ break;
+ }
+
+ //Set inactive keeaplive timeout
+ transportContext.ConnectionStream.ReadTimeout = (int)Config.ConnectionKeepAlive.TotalMilliseconds;
+
+ //"Peek" or wait for more data to begin another request (may throw timeout exception when timmed out)
+ await transportContext.ConnectionStream.ReadAsync(Memory<byte>.Empty, StopToken!.Token);
+
+ } while (true);
+ }
+ //Catch wrapped socket exceptions
+ catch(IOException ioe) when(ioe.InnerException is SocketException se)
+ {
+ WriteSocketExecption(se);
+ }
+ catch(SocketException se)
+ {
+ WriteSocketExecption(se);
+ }
+ catch (OperationCanceledException oce)
+ {
+ Config.ServerLog.Debug("Failed to receive transport data within a timeout period {m}, connection closed", oce.Message);
+ }
+ catch(Exception ex)
+ {
+ Config.ServerLog.Error(ex);
+ }
+
+ //Dec open connection count
+ Interlocked.Decrement(ref OpenConnectionCount);
+
+ //Return context to store
+ ContextStore.Return(context);
+
+ //Close the transport async
+ try
+ {
+ await transportContext.CloseConnectionAsync();
+ }
+ catch(Exception ex)
+ {
+ Config.ServerLog.Error(ex);
+ }
+ }
+
+
+ /// <summary>
+ /// Main event handler for all incoming connections
+ /// </summary>
+ /// <param name="transportContext">The <see cref="ITransportContext"/> describing the incoming connection</param>
+ /// <param name="context">Reusable context object</param>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ private async Task<ERRNO> ProcessHttpEventAsync(ITransportContext transportContext, HttpContext context)
+ {
+ //Prepare http context to process a new message
+ context.BeginRequest();
+
+ try
+ {
+ //Try to parse the http request (may throw exceptions, let them propagate to the transport layer)
+ int status = (int)ParseRequest(transportContext, context);
+
+ //Check status code for socket error, if so, return false to close the connection
+ if (status >= 1000)
+ {
+ return false;
+ }
+#if DEBUG
+ //Write debug request log
+ if (Config.RequestDebugLog != null)
+ {
+ Config.RequestDebugLog.Verbose(context.Request.ToString());
+ }
+#endif
+ //process the request
+ ERRNO keepalive = await ProcessRequestAsync(context, (HttpStatusCode)status);
+ //Store alternate protocol if set
+ IAlternateProtocol? alternateProtocol = context.AlternateProtocol;
+ //Close the response
+ await context.WriteResponseAsync(StopToken!.Token);
+ //See if an alterate protocol was specified
+ if (alternateProtocol != null)
+ {
+ //Disable transport timeouts
+ transportContext.ConnectionStream.WriteTimeout = Timeout.Infinite;
+ transportContext.ConnectionStream.ReadTimeout = Timeout.Infinite;
+ //Exec the protocol handler and pass the transport stream
+ await alternateProtocol.RunAsync(transportContext.ConnectionStream, StopToken!.Token);
+
+ //Clear keepalive flag to close the connection
+ keepalive = false;
+ }
+
+ return keepalive;
+ }
+ finally
+ {
+ //Clean end request
+ context.EndRequest();
+ }
+ }
+
+ /// <summary>
+ /// Reads data synchronously from the transport and attempts to parse an HTTP message and
+ /// built a request.
+ /// </summary>
+ /// <param name="transport"></param>
+ /// <param name="ctx"></param>
+ /// <returns>0 if the request was successfully parsed, the <see cref="HttpStatusCode"/>
+ /// to return to the client because the entity could not be processed</returns>
+ /// <remarks>
+ /// <para>
+ /// This method is synchronous for multiple memory optimization reasons,
+ /// and performance is not expected to be reduced as the transport layer should
+ /// <br></br>
+ /// only raise an event when a socket has data available to be read, and entity
+ /// header sections are expected to fit within a single TCP buffer.
+ /// </para>
+ /// </remarks>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ private HttpStatusCode ParseRequest(ITransportContext transport, HttpContext ctx)
+ {
+ //Init parser
+ TransportReader reader = new (transport.ConnectionStream, ctx.RequestBuffer, Config.HttpEncoding, HeaderLineTermination);
+
+ try
+ {
+ Span<char> lineBuf = ctx.RequestBuffer.CharBuffer;
+
+ Http11ParseExtensions.Http1ParseState parseState = new();
+
+ //Parse the request line
+ HttpStatusCode code = ctx.Request.Http1ParseRequestLine(ref parseState, ref reader, in lineBuf);
+
+ if (code > 0)
+ {
+ return code;
+ }
+ //Parse the headers
+ code = ctx.Request.Http1ParseHeaders(ref parseState, ref reader, Config, in lineBuf);
+ if (code > 0)
+ {
+ return code;
+ }
+ //Prepare entity body for request
+ code = ctx.Request.Http1PrepareEntityBody(ref parseState, ref reader, Config);
+ if (code > 0)
+ {
+ return code;
+ }
+ //Success!
+ return 0;
+ }
+ //Catch exahusted buffer request
+ catch (OutOfMemoryException)
+ {
+ return HttpStatusCode.RequestHeaderFieldsTooLarge;
+ }
+ catch (UriFormatException)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ private async ValueTask<ERRNO> ProcessRequestAsync(HttpContext context, HttpStatusCode status)
+ {
+ //Check status
+ if (status != 0)
+ {
+ /*
+ * If the status of the parsing was not successfull the transnport is considered
+ * an unknowns state and could still have data which could corrupt communications
+ * or worse, contatin an attack. I am choosing to drop the transport and close the
+ * connection if parsing the request fails
+ */
+ //Close the connection when we exit
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ //Return status code, if the the expect header was set, return expectation failed, otherwise return the result status code
+ context.Respond(context.Request.Expect ? HttpStatusCode.ExpectationFailed : status);
+ //exit and close connection (default result will close the context)
+ return false;
+ }
+ //We only support version 1 and 1/1
+ if ((context.Request.HttpVersion & (HttpVersion.Http11 | HttpVersion.Http1)) == 0)
+ {
+ //Close the connection when we exit
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ context.Respond(HttpStatusCode.HttpVersionNotSupported);
+ return false;
+ }
+ //Check open connection count (not super accurate, or might not be atomic)
+ if (OpenConnectionCount > Config.MaxOpenConnections)
+ {
+ //Close the connection and return 503
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ context.Respond(HttpStatusCode.ServiceUnavailable);
+ return false;
+ }
+
+ //Store keepalive value from request, and check if keepalives are enabled by the configuration
+ bool keepalive = context.Request.KeepAlive & Config.ConnectionKeepAlive > TimeSpan.Zero;
+
+ //Set connection header (only for http1 and 1.1)
+ if (keepalive)
+ {
+ context.Response.Headers[HttpResponseHeader.Connection] = "keep-alive";
+ context.Response.Headers[HttpResponseHeader.KeepAlive] = KeepAliveTimeoutHeaderValue;
+ }
+ else
+ {
+ //Set connection closed
+ context.Response.Headers[HttpResponseHeader.Connection] = "closed";
+ }
+ //Get the server root for the specified location
+ if (!ServerRoots.TryGetValue(context.Request.Location.DnsSafeHost, out IWebRoot? root) && !ServerRoots.TryGetValue(WILDCARD_KEY, out root))
+ {
+ context.Respond(HttpStatusCode.NotFound);
+ //make sure control leaves
+ return keepalive;
+ }
+ //check for redirects
+ if (root.Redirects.TryGetValue(context.Request.Location.LocalPath, out Redirect? r))
+ {
+ //301
+ context.Redirect301(r.RedirectUrl);
+ //Return keepalive
+ return keepalive;
+ }
+ //Check the expect header and return an early status code
+ if (context.Request.Expect)
+ {
+ //send a 100 status code
+ await context.Response.SendEarly100ContinueAsync();
+ }
+ /*
+ * Initialze the request body state, which may read/buffer the request
+ * entity body. When doing so, the only exceptions that should be
+ * generated are IO, OutOfMemory, and Overflow. IOE should
+ * be raised to the transport as it will only be thrown if the transport
+ * is in an unusable state.
+ *
+ * OOM and Overflow should only be raised if an over-sized entity
+ * body was allowed to be read in. The Parse method should have guarded
+ * form data size so oom or overflow would be bugs, and we can let
+ * them get thrown
+ */
+ await context.Request.InitRequestBodyAsync(Config.FormDataBufferSize, Config.HttpEncoding);
+ try
+ {
+ await ProcessAsync(root, context);
+ return keepalive;
+ }
+ //The user-code requested termination of the connection
+ catch (TerminateConnectionException tce)
+ {
+ //Log the event as a debug so user can see the result
+ Config.ServerLog.Debug(tce, "User-code requested a connection termination");
+ //See if the exception requested an error code response
+ if (tce.Code > 0)
+ {
+ //close response with status code
+ context.Respond(tce.Code);
+ }
+ else
+ {
+ //Clear any currently set headers since no response is requested
+ context.Response.Headers.Clear();
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Processes a client connection after pre-processing has completed
+ /// </summary>
+ /// <param name="root">The <see cref="IWebRoot"/> to process the event on</param>
+ /// <param name="ctx">The <see cref="HttpContext"/> to process</param>
+ /// <returns>A task that resolves when the user-code has completed processing the entity</returns>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="TerminateConnectionException"></exception>
+ private static async ValueTask ProcessAsync(IWebRoot root, HttpContext ctx)
+ {
+ /*
+ * The event object should be cleared when it is no longer in use, IE before
+ * this procedure returns.
+ */
+ HttpEvent ev = new (ctx);
+ try
+ {
+ await root.ClientConnectedAsync(ev);
+ }
+ //User code requested exit, elevate the exception
+ catch (TerminateConnectionException)
+ {
+ throw;
+ }
+ //Transport exception
+ catch(IOException ioe) when (ioe.InnerException is SocketException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ ctx.ParentServer.Config.ServerLog.Warn(ex, "Unhandled exception during application code execution.");
+ }
+ finally
+ {
+ ev.Clear();
+ }
+ }
+
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/IConnectionContext.cs b/lib/Net.Http/src/Core/IConnectionContext.cs
new file mode 100644
index 0000000..2e3ca46
--- /dev/null
+++ b/lib/Net.Http/src/Core/IConnectionContext.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IConnectionContext.cs
+*
+* IConnectionContext.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// A request-response stream oriented connection state
+ /// </summary>
+ internal interface IConnectionContext
+ {
+ /// <summary>
+ /// Initializes the context to work with the specified
+ /// transport context
+ /// </summary>
+ /// <param name="tranpsort">A referrence to the transport context to use</param>
+ void InitializeContext(ITransportContext tranpsort);
+
+ /// <summary>
+ /// Signals the context that it should prepare to process a new request
+ /// for the current transport
+ /// </summary>
+ void BeginRequest();
+
+ /// <summary>
+ /// Sends any pending data associated with the request to the
+ /// connection that begun the request
+ /// </summary>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A Task that completes when the response has completed</returns>
+ Task WriteResponseAsync(CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Signals to the context that it will release any request specific
+ /// resources
+ /// </summary>
+ void EndRequest();
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/IHttpEvent.cs b/lib/Net.Http/src/Core/IHttpEvent.cs
new file mode 100644
index 0000000..ec1dbb5
--- /dev/null
+++ b/lib/Net.Http/src/Core/IHttpEvent.cs
@@ -0,0 +1,104 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpEvent.cs
+*
+* IHttpEvent.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Contains an http request and session information.
+ /// </summary>
+ public interface IHttpEvent
+ {
+ /// <summary>
+ /// Current connection information. (Like "$_SERVER" superglobal in PHP)
+ /// </summary>
+ IConnectionInfo Server { get; }
+ /// <summary>
+ /// The <see cref="HttpServer"/> that this connection originated from
+ /// </summary>
+ HttpServer OriginServer { get; }
+
+ /// <summary>
+ /// If the request has query arguments they are stored in key value format
+ /// </summary>
+ /// <remarks>Keys are case-insensitive</remarks>
+ IReadOnlyDictionary<string, string> QueryArgs { get; }
+ /// <summary>
+ /// If the request body has form data or url encoded arguments they are stored in key value format
+ /// </summary>
+ IReadOnlyDictionary<string, string> RequestArgs { get; }
+ /// <summary>
+ /// Contains all files upladed with current request
+ /// </summary>
+ /// <remarks>Keys are case-insensitive</remarks>
+ IReadOnlyList<FileUpload> Files { get; }
+
+ /// <summary>
+ /// Complete the session and respond to user
+ /// </summary>
+ /// <param name="code">Status code of operation</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code);
+
+ /// <summary>
+ /// Responds to a client with a <see cref="Stream"/> containing data to be sent to user of a given contentType.
+ /// Runtime will dispose of the stream during closing event
+ /// </summary>
+ /// <param name="code">Response status code</param>
+ /// <param name="type">MIME ContentType of data</param>
+ /// <param name="stream">Data to be sent to client</param>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code, ContentType type, Stream stream);
+
+ /// <summary>
+ /// Responds to a client with an in-memory <see cref="IMemoryResponseReader"/> containing data
+ /// to be sent to user of a given contentType.
+ /// </summary>
+ /// <param name="code">The status code to set</param>
+ /// <param name="type">The entity content-type</param>
+ /// <param name="entity">The in-memory response data</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ void CloseResponse(HttpStatusCode code, ContentType type, IMemoryResponseReader entity);
+
+ /// <summary>
+ /// Configures the server to change protocols from HTTP to the specified
+ /// custom protocol handler.
+ /// </summary>
+ /// <param name="protocolHandler">The custom protocol handler</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ void DangerousChangeProtocol(IAlternateProtocol protocolHandler);
+
+ /// <summary>
+ /// Disables response compression
+ /// </summary>
+ void DisableCompression();
+
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/IHttpLifeCycle.cs b/lib/Net.Http/src/Core/IHttpLifeCycle.cs
new file mode 100644
index 0000000..135219d
--- /dev/null
+++ b/lib/Net.Http/src/Core/IHttpLifeCycle.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpLifeCycle.cs
+*
+* IHttpLifeCycle.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Represents a interface of lifecycle hooks that correspond
+ /// with HTTP lifecycle events.
+ /// </summary>
+ internal interface IHttpLifeCycle
+ {
+ /// <summary>
+ /// Raised when the context is being prepare for reuse,
+ /// "revived from storage"
+ /// </summary>
+ void OnPrepare();
+
+ /// <summary>
+ /// Raised when the context is being released back to the pool
+ /// for reuse at a later time
+ /// </summary>
+ void OnRelease();
+
+ /// <summary>
+ /// Raised when a new request is about to be processed
+ /// on the current context
+ /// </summary>
+ void OnNewRequest();
+
+ /// <summary>
+ /// Raised when the request has been processed and the
+ /// response has been sent. Used to perform per-request
+ /// cleanup/reset for another request.
+ /// </summary>
+ /// <remarks>
+ /// This method is guarunteed to be called regardless of an http error, this
+ /// method should not throw exceptions
+ /// </remarks>
+ void OnComplete();
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/IHttpResponseBody.cs b/lib/Net.Http/src/Core/IHttpResponseBody.cs
new file mode 100644
index 0000000..aa2dd34
--- /dev/null
+++ b/lib/Net.Http/src/Core/IHttpResponseBody.cs
@@ -0,0 +1,73 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHttpResponseBody.cs
+*
+* IHttpResponseBody.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Represents a rseponse entity body
+ /// </summary>
+ internal interface IHttpResponseBody
+ {
+ /// <summary>
+ /// A value that indicates if there is data
+ /// to send to the client
+ /// </summary>
+ bool HasData { get; }
+
+ /// <summary>
+ /// A value that indicates if response data requires buffering
+ /// </summary>
+ bool BufferRequired { get; }
+
+ /// <summary>
+ /// Writes internal response entity data to the destination stream
+ /// </summary>
+ /// <param name="dest">The response stream to write data to</param>
+ /// <param name="buffer">An optional buffer used to buffer responses</param>
+ /// <param name="count">The maximum length of the response data to write</param>
+ /// <param name="token">A token to cancel the operation</param>
+ /// <returns>A task that resolves when the response is completed</returns>
+ Task WriteEntityAsync(Stream dest, long count, Memory<byte>? buffer, CancellationToken token);
+
+ /// <summary>
+ /// Writes internal response entity data to the destination stream
+ /// </summary>
+ /// <param name="dest">The response stream to write data to</param>
+ /// <param name="buffer">An optional buffer used to buffer responses</param>
+ /// <param name="token">A token to cancel the operation</param>
+ /// <returns>A task that resolves when the response is completed</returns>
+ Task WriteEntityAsync(Stream dest, Memory<byte>? buffer, CancellationToken token);
+
+ /// <summary>
+ /// The length of the content
+ /// </summary>
+ long Length { get; }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpInputStream.cs b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
new file mode 100644
index 0000000..61d215f
--- /dev/null
+++ b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
@@ -0,0 +1,222 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpInputStream.cs
+*
+* HttpInputStream.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Specialized stream to allow reading a request entity body with a fixed content length.
+ /// </summary>
+ internal sealed class HttpInputStream : Stream
+ {
+ private readonly Func<Stream> GetTransport;
+
+ private long ContentLength;
+ private Stream? InputStream;
+ private long _position;
+
+ private ISlindingWindowBuffer<byte>? _initalData;
+
+ public HttpInputStream(Func<Stream> getTransport) => GetTransport = getTransport;
+
+ internal void OnComplete()
+ {
+ //Dispose the inigial data buffer
+ _initalData?.Close();
+ _initalData = null;
+ //Remove stream cache copy
+ InputStream = null;
+ //Reset position
+ _position = 0;
+ //reset content length
+ ContentLength = 0;
+ }
+
+ /// <summary>
+ /// Creates a new input stream object configured to allow reading of the specified content length
+ /// bytes from the stream and consumes the initial buffer to read data from on initial read calls
+ /// </summary>
+ /// <param name="contentLength">The number of bytes to allow being read from the transport or initial buffer</param>
+ /// <param name="initial">Entity body data captured on initial read</param>
+ internal void Prepare(long contentLength, ISlindingWindowBuffer<byte>? initial)
+ {
+ ContentLength = contentLength;
+ _initalData = initial;
+
+ //Cache transport
+ InputStream = GetTransport();
+ }
+
+ public override void Close() => throw new NotSupportedException("The HTTP input stream should never be closed!");
+ private long Remaining => Math.Max(ContentLength - _position, 0);
+ public override bool CanRead => true;
+ public override bool CanSeek => true;
+ public override bool CanWrite => false;
+ public override long Length => ContentLength;
+ public override long Position { get => _position; set { } }
+
+ public override void Flush(){}
+
+ public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count));
+ public override int Read(Span<byte> buffer)
+ {
+ //Calculate the amount of data that can be read into the buffer
+ int bytesToRead = (int)Math.Min(buffer.Length, Remaining);
+ if (bytesToRead == 0)
+ {
+ return 0;
+ }
+
+ //Clamp output buffer size and create buffer writer
+ ForwardOnlyWriter<byte> writer = new(buffer[..bytesToRead]);
+
+ //See if all data is internally buffered
+ if (_initalData != null && _initalData.AccumulatedSize > 0)
+ {
+ //Read as much as possible from internal buffer
+ ERRNO read = _initalData.Read(writer.Remaining);
+
+ //Advance writer
+ writer.Advance(read);
+
+ //Update position
+ _position += read;
+ }
+
+ //See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
+ if (writer.RemainingSize > 0)
+ {
+ //Read from transport
+ ERRNO read = InputStream!.Read(writer.Remaining);
+
+ //Update writer position
+ writer.Advance(read);
+
+ _position += read;
+ }
+
+ //Return number of bytes written to the buffer
+ return writer.Written;
+ }
+
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
+ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ //Calculate the amount of data that can be read into the buffer
+ int bytesToRead = (int)Math.Min(buffer.Length, Remaining);
+ if (bytesToRead == 0)
+ {
+ return 0;
+ }
+
+ //Clamp output buffer size and create buffer writer
+ ForwardOnlyMemoryWriter<byte> writer = new(buffer[..bytesToRead]);
+
+ //See if all data is internally buffered
+ if (_initalData != null && _initalData.AccumulatedSize > 0)
+ {
+ //Read as much as possible from internal buffer
+ ERRNO read = _initalData.Read(writer.Remaining.Span);
+
+ //Advance writer
+ writer.Advance(read);
+
+ //Update position
+ _position += read;
+ }
+
+ //See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
+ if (writer.RemainingSize > 0)
+ {
+ //Read from transport
+ ERRNO read = await InputStream!.ReadAsync(writer.Remaining, cancellationToken).ConfigureAwait(false);
+
+ //Update writer position
+ writer.Advance(read);
+
+ _position += read;
+ }
+
+ //Return number of bytes written to the buffer
+ return writer.Written;
+ }
+
+ /// <summary>
+ /// Asynchronously discards all remaining data in the stream
+ /// </summary>
+ /// <param name="heap">The heap to alloc buffers from</param>
+ /// <param name="maxBufferSize">The maxium size of the buffer to allocate</param>
+ /// <returns>A task that represents the discard operations</returns>
+ public async ValueTask DiscardRemainingAsync(int maxBufferSize)
+ {
+ long remaining = Remaining;
+ if(remaining == 0)
+ {
+ return;
+ }
+ //See if all data has already been buffered
+ if(_initalData != null && remaining <= _initalData.AccumulatedSize)
+ {
+ //All data has been buffred, so just clear the buffer
+ _position = Length;
+ }
+ //We must actaully disacrd data from the stream
+ else
+ {
+ //Calcuate a buffer size to allocate (will never be larger than an int)
+ int bufferSize = (int)Math.Min(remaining, maxBufferSize);
+ //Alloc a discard buffer to reset the transport
+ using IMemoryOwner<byte> discardBuffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+ int read = 0;
+ do
+ {
+ //Read data to the discard buffer until reading is completed
+ read = await ReadAsync(discardBuffer.Memory, CancellationToken.None).ConfigureAwait(true);
+
+ } while (read != 0);
+ }
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ //Ignore seek control
+ return _position;
+ }
+ public override void SetLength(long value) => throw new NotSupportedException();
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs
new file mode 100644
index 0000000..593275d
--- /dev/null
+++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs
@@ -0,0 +1,284 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpRequest.cs
+*
+* HttpRequest.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal class HttpRequest : IHttpLifeCycle
+#if DEBUG
+ ,IStringSerializeable
+#endif
+ {
+ public readonly VnWebHeaderCollection Headers;
+ public readonly Dictionary<string, string> Cookies;
+ public readonly List<string> Accept;
+ public readonly List<string> AcceptLanguage;
+ public readonly HttpRequestBody RequestBody;
+
+ public HttpVersion HttpVersion { get; set; }
+ public HttpMethod Method { get; set; }
+ public string? UserAgent { get; set; }
+ public string? Boundry { get; set; }
+ public ContentType ContentType { get; set; }
+ public string? Charset { get; set; }
+ public Uri Location { get; set; }
+ public Uri? Origin { get; set; }
+ public Uri? Referrer { get; set; }
+ internal bool KeepAlive { get; set; }
+ public IPEndPoint RemoteEndPoint { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; }
+ public SslProtocols EncryptionVersion { get; set; }
+ public Tuple<long, long>? Range { get; set; }
+ /// <summary>
+ /// A value indicating whether the connection contained a request entity body.
+ /// </summary>
+ public bool HasEntityBody { get; set; }
+ /// <summary>
+ /// A transport stream wrapper that is positioned for reading
+ /// the entity body from the input stream
+ /// </summary>
+ public HttpInputStream InputStream { get; }
+ /// <summary>
+ /// A value indicating if the client's request had an Expect-100-Continue header
+ /// </summary>
+ public bool Expect { get; set; }
+
+#nullable disable
+ public HttpRequest(Func<Stream> getTransport)
+ {
+ //Create new collection for headers
+ Headers = new();
+ //Create new collection for request cookies
+ Cookies = new();
+ //New list for accept
+ Accept = new();
+ AcceptLanguage = new();
+ //New reusable input stream
+ InputStream = new(getTransport);
+ RequestBody = new();
+ }
+
+
+ public void OnPrepare()
+ {}
+
+ public void OnRelease()
+ {}
+
+ public void OnNewRequest()
+ {
+ //Set to defaults
+ ContentType = ContentType.NonSupported;
+ EncryptionVersion = default;
+ Method = HttpMethod.None;
+ HttpVersion = HttpVersion.None;
+ }
+
+ public void OnComplete()
+ {
+ //release the input stream
+ InputStream.OnComplete();
+ RequestBody.OnComplete();
+ //Make sure headers, cookies, and accept are cleared for reuse
+ Headers.Clear();
+ Cookies.Clear();
+ Accept.Clear();
+ AcceptLanguage.Clear();
+ //Clear request flags
+ this.Expect = false;
+ this.KeepAlive = false;
+ this.HasEntityBody = false;
+ //We need to clean up object refs
+ this.Boundry = default;
+ this.Charset = default;
+ this.LocalEndPoint = default;
+ this.Location = default;
+ this.Origin = default;
+ this.Referrer = default;
+ this.RemoteEndPoint = default;
+ this.UserAgent = default;
+ this.Range = default;
+ //We are all set to reuse the instance
+ }
+
+
+#if DEBUG
+ public string Compile()
+ {
+ //Alloc char buffer for compilation
+ using UnsafeMemoryHandle<char> buffer = Memory.UnsafeAlloc<char>(16 * 1024, true);
+ ForwardOnlyWriter<char> writer = new(buffer.Span);
+ Compile(ref writer);
+ return writer.ToString();
+ }
+
+ public void Compile(ref ForwardOnlyWriter<char> writer)
+ {
+ //Request line
+ writer.Append(Method.ToString());
+ writer.Append(" ");
+ writer.Append(Location?.PathAndQuery);
+ writer.Append(" HTTP/");
+ switch (HttpVersion)
+ {
+ case HttpVersion.None:
+ writer.Append("Unsuppored Http version");
+ break;
+ case HttpVersion.Http1:
+ writer.Append("1.0");
+ break;
+ case HttpVersion.Http11:
+ writer.Append("1.1");
+ break;
+ case HttpVersion.Http2:
+ writer.Append("2.0");
+ break;
+ case HttpVersion.Http09:
+ writer.Append("0.9");
+ break;
+ }
+ writer.Append("\r\n");
+ //write host
+ writer.Append("Host: ");
+ writer.Append(Location?.Authority);
+ writer.Append("\r\n");
+
+ //Write headers
+ foreach (string header in Headers.Keys)
+ {
+ writer.Append(header);
+ writer.Append(": ");
+ writer.Append(Headers[header]);
+ writer.Append("\r\n");
+ }
+ //Write cookies
+ foreach (string cookie in Cookies.Keys)
+ {
+ writer.Append("Cookie: ");
+ writer.Append(cookie);
+ writer.Append("=");
+ writer.Append(Cookies[cookie]);
+ writer.Append("\r\n");
+ }
+
+ //Write accept
+ if (Accept.Count > 0)
+ {
+ writer.Append("Accept: ");
+ foreach (string accept in Accept)
+ {
+ writer.Append(accept);
+ writer.Append(", ");
+ }
+ writer.Append("\r\n");
+ }
+ //Write accept language
+ if (AcceptLanguage.Count > 0)
+ {
+ writer.Append("Accept-Language: ");
+ foreach (string acceptLanguage in AcceptLanguage)
+ {
+ writer.Append(acceptLanguage);
+ writer.Append(", ");
+ }
+ writer.Append("\r\n");
+ }
+ //Write user agent
+ if (UserAgent != null)
+ {
+ writer.Append("User-Agent: ");
+ writer.Append(UserAgent);
+ writer.Append("\r\n");
+ }
+ //Write content type
+ if (ContentType != ContentType.NonSupported)
+ {
+ writer.Append("Content-Type: ");
+ writer.Append(HttpHelpers.GetContentTypeString(ContentType));
+ writer.Append("\r\n");
+ }
+ //Write content length
+ if (ContentType != ContentType.NonSupported)
+ {
+ writer.Append("Content-Length: ");
+ writer.Append(InputStream.Length);
+ writer.Append("\r\n");
+ }
+ if (KeepAlive)
+ {
+ writer.Append("Connection: keep-alive\r\n");
+ }
+ if (Expect)
+ {
+ writer.Append("Expect: 100-continue\r\n");
+ }
+ if(Origin != null)
+ {
+ writer.Append("Origin: ");
+ writer.Append(Origin.ToString());
+ writer.Append("\r\n");
+ }
+ if (Referrer != null)
+ {
+ writer.Append("Referrer: ");
+ writer.Append(Referrer.ToString());
+ writer.Append("\r\n");
+ }
+ writer.Append("from ");
+ writer.Append(RemoteEndPoint.ToString());
+ writer.Append("\r\n");
+ writer.Append("Received on ");
+ writer.Append(LocalEndPoint.ToString());
+ //Write end of headers
+ writer.Append("\r\n");
+ }
+
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+ public override string ToString()
+ {
+ return Compile();
+ }
+#else
+ public override string ToString()
+ {
+ return "Request debug output only available when compiled in DEBUG mode";
+ }
+#endif
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestBody.cs b/lib/Net.Http/src/Core/Request/HttpRequestBody.cs
new file mode 100644
index 0000000..824ca24
--- /dev/null
+++ b/lib/Net.Http/src/Core/Request/HttpRequestBody.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpRequestBody.cs
+*
+* HttpRequestBody.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Represents a higher-level request entity body (query arguments, request body etc)
+ /// that has been parsed and captured
+ /// </summary>
+ internal class HttpRequestBody
+ {
+ public readonly List<FileUpload> Uploads;
+ public readonly Dictionary<string, string> RequestArgs;
+ public readonly Dictionary<string, string> QueryArgs;
+
+ public HttpRequestBody()
+ {
+ Uploads = new(1);
+
+ //Request/query args should not be request sensitive
+ RequestArgs = new(StringComparer.OrdinalIgnoreCase);
+ QueryArgs = new(StringComparer.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Releases all resources used by the current instance
+ /// </summary>
+ public void OnComplete()
+ {
+ //Only enumerate/clear if file uplaods are present
+ if (Uploads.Count > 0)
+ {
+ //Dispose all initialized files
+ for (int i = 0; i < Uploads.Count; i++)
+ {
+ Uploads[i].Free();
+ }
+ //Emtpy list
+ Uploads.Clear();
+ }
+ //Clear request args and file uplaods
+ RequestArgs.Clear();
+ QueryArgs.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
new file mode 100644
index 0000000..6a93192
--- /dev/null
+++ b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -0,0 +1,304 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpRequestExtensions.cs
+*
+* HttpRequestExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http.Core
+{
+ internal static class HttpRequestExtensions
+ {
+ public enum CompressionType
+ {
+ None,
+ Gzip,
+ Deflate,
+ Brotli
+ }
+
+ /// <summary>
+ /// Gets the <see cref="CompressionType"/> that the connection accepts
+ /// in a default order, or none if not enabled
+ /// </summary>
+ /// <param name="request"></param>
+ /// <returns>A <see cref="CompressionType"/> with a value the connection support</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static CompressionType GetCompressionSupport(this HttpRequest request)
+ {
+ string? acceptEncoding = request.Headers[HttpRequestHeader.AcceptEncoding];
+
+ if (acceptEncoding == null)
+ {
+ return CompressionType.None;
+ }
+ else if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase))
+ {
+ return CompressionType.Gzip;
+ }
+ else if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase))
+ {
+ return CompressionType.Deflate;
+ }
+ else if (acceptEncoding.Contains("br", StringComparison.OrdinalIgnoreCase))
+ {
+ return CompressionType.Brotli;
+ }
+ else
+ {
+ return CompressionType.None;
+ }
+ }
+
+
+ /// <summary>
+ /// Tests the connection's origin header against the location URL by authority.
+ /// An origin matches if its scheme, host, and port match
+ /// </summary>
+ /// <returns>true if the origin header was set and does not match the current locations origin</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsCrossOrigin(this HttpRequest Request)
+ {
+ return Request.Origin != null
+ && (!Request.Origin.Authority.Equals(Request.Location.Authority, StringComparison.Ordinal)
+ || !Request.Origin.Scheme.Equals(Request.Location.Scheme, StringComparison.Ordinal));
+ }
+ /// <summary>
+ /// Is the current connection a websocket upgrade request handshake
+ /// </summary>
+ /// <returns>true if the connection is a websocket upgrade request, false otherwise</returns>
+ public static bool IsWebSocketRequest(this HttpRequest Request)
+ {
+ string? upgrade = Request.Headers[HttpRequestHeader.Upgrade];
+ if (!string.IsNullOrWhiteSpace(upgrade) && upgrade.Contains("websocket", StringComparison.OrdinalIgnoreCase))
+ {
+ //This request is a websocket request
+ //Check connection header
+ string? connection = Request.Headers[HttpRequestHeader.Connection];
+ //Must be a web socket request
+ return !string.IsNullOrWhiteSpace(connection) && connection.Contains("upgrade", StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Initializes the <see cref="HttpRequest"/> for an incomming connection
+ /// </summary>
+ /// <param name="server"></param>
+ /// <param name="ctx">The <see cref="TransportEventContext"/> to attach the request to</param>
+ /// <param name="defaultHttpVersion">The default http version</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Initialize(this HttpRequest server, ITransportContext ctx, HttpVersion defaultHttpVersion)
+ {
+ server.LocalEndPoint = ctx.LocalEndPoint;
+ server.RemoteEndPoint = ctx.RemoteEndpoint;
+ server.EncryptionVersion = ctx.SslVersion;
+ //Set to default http version so the response can be configured properly
+ server.HttpVersion = defaultHttpVersion;
+ }
+
+
+ /// <summary>
+ /// Initializes the <see cref="HttpRequest.RequestBody"/> for the current request
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="maxBufferSize">The maxium buffer size allowed while parsing reqeust body data</param>
+ /// <param name="encoding">The request data encoding for url encoded or form data bodies</param>
+ /// <exception cref="IOException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ internal static ValueTask InitRequestBodyAsync(this HttpRequest Request, int maxBufferSize, Encoding encoding)
+ {
+ /*
+ * Parses query parameters from the request location query
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void ParseQueryArgs(HttpRequest Request)
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ //Query string parse method
+ static void QueryParser(ReadOnlySpan<char> queryArgument, HttpRequest Request)
+ {
+ //Split spans after the '=' character
+ ReadOnlySpan<char> key = queryArgument.SliceBeforeParam('=');
+ ReadOnlySpan<char> value = queryArgument.SliceAfterParam('=');
+ //Insert into dict
+ Request.RequestBody.QueryArgs[key.ToString()] = value.ToString();
+ }
+
+ //if the request has query args, parse and store them
+ ReadOnlySpan<char> queryString = Request.Location.Query;
+ if (!queryString.IsEmpty)
+ {
+ //trim leading '?' if set
+ queryString = queryString.TrimStart('?');
+ //Split args by '&'
+ queryString.Split('&', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, QueryParser, Request);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static async ValueTask ParseInputStream(HttpRequest Request, int maxBufferSize, Encoding encoding)
+ {
+ /*
+ * Reads all available data from the request input stream
+ * If the stream size is smaller than a TCP buffer size, will complete synchronously
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static ValueTask<VnString> ReadInputStreamAsync(HttpRequest Request, int maxBufferSize, Encoding encoding)
+ {
+ //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
+ int bufferSize = (int)Math.Min(Request.InputStream.Length, maxBufferSize);
+ //Read the stream into a vnstring
+ return VnString.FromStreamAsync(Request.InputStream, encoding, HttpPrivateHeap, bufferSize);
+ }
+ /*
+ * SpanSplit callback function for storing UrlEncoded request entity
+ * bodies in key-value pairs and writing them to the
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void UrlEncodedSplitCb(ReadOnlySpan<char> kvArg, HttpRequest Request)
+ {
+ //Get key side of agument (or entire argument if no value is set)
+ ReadOnlySpan<char> key = kvArg.SliceBeforeParam('=');
+ ReadOnlySpan<char> value = kvArg.SliceAfterParam('=');
+ //trim, allocate strings, and store in the request arg dict
+ Request.RequestBody.RequestArgs[key.TrimCRLF().ToString()] = value.TrimCRLF().ToString();
+ }
+
+ /*
+ * Parses a Form-Data content type request entity body and stores those arguments in
+ * Request uploads or request args
+ */
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void FormDataBodySplitCb(ReadOnlySpan<char> formSegment, ValueTuple<HttpRequestBody, Encoding> state)
+ {
+ //Form data arguments
+ string? DispType = null, Name = null, FileName = null;
+ ContentType ctHeaderVal = ContentType.NonSupported;
+ //Get sliding window for parsing data
+ ForwardOnlyReader<char> reader = new(formSegment.TrimCRLF());
+ //Read content headers
+ do
+ {
+ //Get the index of the next crlf
+ int index = reader.Window.IndexOf(HttpHelpers.CRLF);
+ //end of headers
+ if (index < 1)
+ {
+ break;
+ }
+ //Get header data
+ ReadOnlySpan<char> header = reader.Window[..index];
+ //Split header at colon
+ int colon = header.IndexOf(':');
+ //If no data is available after the colon the header is not valid, so move on to the next body
+ if (colon < 1)
+ {
+ return;
+ }
+ //Hash the header value into a header enum
+ HttpRequestHeader headerType = HttpHelpers.GetRequestHeaderEnumFromValue(header[..colon]);
+ //get the header value
+ ReadOnlySpan<char> headerValue = header[(colon + 1)..];
+ //Check for content dispositon header
+ if (headerType == HttpHelpers.ContentDisposition)
+ {
+ //Parse the content dispostion
+ HttpHelpers.ParseDisposition(headerValue, out DispType, out Name, out FileName);
+ }
+ //Check for content type
+ else if (headerType == HttpRequestHeader.ContentType)
+ {
+ //The header value for content type should be an MIME content type
+ ctHeaderVal = HttpHelpers.GetContentType(headerValue.Trim().ToString());
+ }
+ //Shift window to the next line
+ reader.Advance(index + HttpHelpers.CRLF.Length);
+ } while (true);
+ //Remaining data should be the body data (will have leading and trailing CRLF characters
+ //If filename is set, this must be a file
+ if (!string.IsNullOrWhiteSpace(FileName))
+ {
+ //Store the file in the uploads
+ state.Item1.Uploads.Add(FileUpload.FromString(reader.Window.TrimCRLF(), state.Item2, FileName, ctHeaderVal));
+ }
+ //Make sure the name parameter was set and store the message body as a string
+ else if (!string.IsNullOrWhiteSpace(Name))
+ {
+ //String data as body
+ state.Item1.RequestArgs[Name] = reader.Window.TrimCRLF().ToString();
+ }
+ }
+
+ switch (Request.ContentType)
+ {
+ //CT not supported, dont read it
+ case ContentType.NonSupported:
+ break;
+ case ContentType.UrlEncoded:
+ //Create a vnstring from the message body and parse it (assuming url encoded bodies are small so a small stack buffer will be fine)
+ using (VnString urlbody = await ReadInputStreamAsync(Request, maxBufferSize, encoding))
+ {
+ //Get the body as a span, and split the 'string' at the & character
+ urlbody.AsSpan().Split('&', StringSplitOptions.RemoveEmptyEntries, UrlEncodedSplitCb, Request);
+ }
+ break;
+ case ContentType.MultiPart:
+ //Make sure we have a boundry specified
+ if (string.IsNullOrWhiteSpace(Request.Boundry))
+ {
+ break;
+ }
+ //Read all data from stream into string
+ using (VnString body = await ReadInputStreamAsync(Request, maxBufferSize, encoding))
+ {
+ //Split the body as a span at the boundries
+ body.AsSpan().Split($"--{Request.Boundry}", StringSplitOptions.RemoveEmptyEntries, FormDataBodySplitCb, (Request.RequestBody, encoding));
+ }
+ break;
+ //Default case is store as a file
+ default:
+ //add upload
+ Request.RequestBody.Uploads.Add(new(Request.InputStream, string.Empty, Request.ContentType, false));
+ break;
+ }
+ }
+
+ //Parse query
+ ParseQueryArgs(Request);
+
+ //Decode requests from body
+ return !Request.HasEntityBody ? ValueTask.CompletedTask : ParseInputStream(Request, maxBufferSize, encoding);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
new file mode 100644
index 0000000..f876528
--- /dev/null
+++ b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
@@ -0,0 +1,533 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: Http11ParseExtensions.cs
+*
+* Http11ParseExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Linq;
+using System.Collections.Generic;
+using System.Security.Authentication;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal static class Http11ParseExtensions
+ {
+
+ /// <summary>
+ /// Stores the state of an HTTP/1.1 parsing operation
+ /// </summary>
+ public ref struct Http1ParseState
+ {
+ internal UriBuilder? Location;
+ internal bool IsAbsoluteRequestUrl;
+ internal long ContentLength;
+ }
+
+
+ /// <summary>
+ /// Reads the first line from the transport stream using the specified buffer
+ /// and parses the HTTP request line components: Method, resource, Http Version
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="reader">The reader to read lines from the transport</param>
+ /// <param name="parseState">The HTTP1 parsing state</param>
+ /// <param name="lineBuf">The buffer to use when parsing the request data</param>
+ /// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
+ /// <exception cref="UriFormatException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static HttpStatusCode Http1ParseRequestLine(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in Span<char> lineBuf)
+ {
+ //Locals
+ ERRNO requestResult;
+ int index, endloc;
+
+ //Read the start line
+ requestResult = reader.ReadLine(lineBuf);
+ //Must be able to parse the verb and location
+ if (requestResult < 1)
+ {
+ //empty request
+ return (HttpStatusCode)1000;
+ }
+
+ //true up the request line to actual size
+ ReadOnlySpan<char> requestLine = lineBuf[..(int)requestResult].Trim();
+ //Find the first white space character ("GET / HTTP/1.1")
+ index = requestLine.IndexOf(' ');
+ if (index == -1)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Decode the verb (function requires the string be the exact characters of the request method)
+ Request.Method = HttpHelpers.GetRequestMethod(requestLine[0..index]);
+ //Make sure the method is supported
+ if (Request.Method == HttpMethod.None)
+ {
+ return HttpStatusCode.MethodNotAllowed;
+ }
+
+ //location string should be from end of verb to HTTP/ NOTE: Only supports http... this is an http server
+ endloc = requestLine.LastIndexOf(" HTTP/", StringComparison.OrdinalIgnoreCase);
+ //Client must specify an http version prepended by a single whitespace(rfc2612)
+ if (endloc == -1)
+ {
+ return HttpStatusCode.HttpVersionNotSupported;
+ }
+
+ //Try to parse the version and only accept the 3 major versions of http
+ Request.HttpVersion = HttpHelpers.ParseHttpVersion(requestLine[endloc..]);
+ //Check to see if the version was parsed succesfully
+ if (Request.HttpVersion == HttpVersion.None)
+ {
+ //Return not supported
+ return HttpStatusCode.HttpVersionNotSupported;
+ }
+
+ //Set keepalive flag if http11
+ Request.KeepAlive = Request.HttpVersion == HttpVersion.Http11;
+
+ //Get the location segment from the request line
+ ReadOnlySpan<char> paq = requestLine[(index + 1)..endloc].TrimCRLF();
+
+ //Process an absolute uri,
+ if (paq.Contains("://", StringComparison.Ordinal))
+ {
+ //Convert the location string to a .net string and init the location builder (will perform validation when the Uri propery is used)
+ parseState.Location = new(paq.ToString());
+ parseState.IsAbsoluteRequestUrl = true;
+ return 0;
+ }
+ //Try to capture a realative uri
+ else if (paq.Length > 0 && paq[0] == '/')
+ {
+ //Create a default location uribuilder
+ parseState.Location = new()
+ {
+ //Set a default scheme
+ Scheme = Request.EncryptionVersion == SslProtocols.None ? Uri.UriSchemeHttp : Uri.UriSchemeHttps,
+ };
+ //Need to manually parse the query string
+ int q = paq.IndexOf('?');
+ //has query?
+ if (q == -1)
+ {
+ parseState.Location.Path = paq.ToString();
+ }
+ //Does have query argument
+ else
+ {
+ //separate the path from the query
+ parseState.Location.Path = paq[0..q].ToString();
+ parseState.Location.Query = paq[(q + 1)..].ToString();
+ }
+ return 0;
+ }
+ //Cannot service an unknonw location
+ return HttpStatusCode.BadRequest;
+ }
+
+ /// <summary>
+ /// Reads headers from the transport using the supplied character buffer, and updates the current request
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="parseState">The HTTP1 parsing state</param>
+ /// <param name="Config">The current server <see cref="HttpConfig"/></param>
+ /// <param name="reader">The <see cref="VnStreamReader"/> to read lines from the transport</param>
+ /// <param name="lineBuf">The buffer read data from the transport with</param>
+ /// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static HttpStatusCode Http1ParseHeaders(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in HttpConfig Config, in Span<char> lineBuf)
+ {
+ try
+ {
+ int headerCount = 0, colon;
+ bool hostFound = false;
+ ERRNO charsRead;
+ ReadOnlySpan<char> headerName, requestHeaderValue;
+
+ /*
+ * This loop will read "lines" from the transport/reader buffer as headers
+ * and store them in the rented character buffer with 0 allocations.
+ *
+ * Lines will be read from the transport reader until an empty line is read,
+ * or an exception occurs. The VnStreamReader class will search for lines
+ * directly in the binary rather than converting the data then parsing it.
+ * When a line is parsed, its assumed to be an HTTP header at this point in
+ * the parsing, and is separated into its key-value pair determined by the
+ * first ':' character to appear.
+ *
+ * The header length will be limited by the size of the character buffer,
+ * or the reader binary buffer while reading lines. Buffer sizes are fixed
+ * to the system memory page size. Depending on the encoding the user chooses
+ * this should not be an issue for most configurations. This strategy is
+ * most efficient for realtivly small header sizes.
+ *
+ * The header's key is hashed by the HttpHelpers class and the hash is used to
+ * index a lookup table to return its enumeration value which is used in the swtich
+ * statement to reduce the number of strings added to the request header container.
+ * This was a major effort to reduce memory and CPU overhead while using the
+ * WebHeaderCollection .NET class, which I think is still worth using instead of a
+ * custom header data structure class.
+ *
+ * Some case statments are custom HttpRequestHeader enum values via internal casted
+ * constants to be consistant with he .NET implementation.
+ */
+ do
+ {
+ //Read a line until we reach the end of headers, this call will block if end of characters is reached and a new string will be read
+ charsRead = reader.ReadLine(lineBuf);
+
+ //If the result is less than 1, no line is available (end of headers) or could not be read
+ if (charsRead < 1)
+ {
+ break;
+ }
+
+ //Header count exceeded or header larger than header buffer size
+ if (charsRead < 0 || headerCount > Config.MaxRequestHeaderCount)
+ {
+ return HttpStatusCode.RequestHeaderFieldsTooLarge;
+ }
+
+ {
+ //Get the true size of the read header line as a readonly span
+ ReadOnlySpan<char> header = lineBuf[..(int)charsRead];
+
+ /*
+ * RFC 7230, ignore headers with preceeding whitespace
+ *
+ * If the first character is whitespace that is enough to
+ * ignore the rest of the header
+ */
+ if (header[0] == ' ')
+ {
+ //Move on to next header
+ continue;
+ }
+
+ //Find the first colon
+ colon = header.IndexOf(':');
+ //No colon was found, this is an invalid string, try to skip it and keep reading
+ if (colon <= 0)
+ {
+ continue;
+ }
+
+ //Store header and its value (sections before and after colon)
+ headerName = header[..colon].TrimCRLF();
+ requestHeaderValue = header[(colon + 1)..].TrimCRLF();
+ }
+
+ //Hash the header key and lookup the request header value
+ switch (HttpHelpers.GetRequestHeaderEnumFromValue(headerName))
+ {
+ case HttpRequestHeader.Connection:
+ {
+ //Update keepalive, if the connection header contains "closed" and with the current value of keepalive
+ Request.KeepAlive &= !requestHeaderValue.Contains("close", StringComparison.OrdinalIgnoreCase);
+ //Also store the connecion header into the store
+ Request.Headers.Add(HttpRequestHeader.Connection, requestHeaderValue.ToString());
+ }
+ break;
+ case HttpRequestHeader.ContentType:
+ {
+ if (!HttpHelpers.TryParseContentType(requestHeaderValue.ToString(), out string? ct, out string? charset, out string? boundry) || ct == null)
+ {
+ //Invalid content type header value
+ return HttpStatusCode.UnsupportedMediaType;
+ }
+ Request.Boundry = boundry;
+ Request.Charset = charset;
+ //Get the content type enum from mime type
+ Request.ContentType = HttpHelpers.GetContentType(ct);
+ }
+ break;
+ case HttpRequestHeader.ContentLength:
+ {
+ //Content length has already been calculated, ERROR, rfc 7230
+ if(parseState.ContentLength > 0)
+ {
+ Config.ServerLog.Debug("Message warning, recieved multiple content length headers");
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Only capture positive values, and if length is negative we are supposed to ignore it
+ if (ulong.TryParse(requestHeaderValue, out ulong len) && len < long.MaxValue)
+ {
+ parseState.ContentLength = (long)len;
+ }
+ else
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Request size it too large to service
+ if (parseState.ContentLength > Config.MaxUploadSize)
+ {
+ return HttpStatusCode.RequestEntityTooLarge;
+ }
+ }
+ break;
+ case HttpRequestHeader.Host:
+ {
+ //Set host found flag
+ hostFound = true;
+
+ //Split the host value by the port parameter
+ ReadOnlySpan<char> port = requestHeaderValue.SliceAfterParam(':').Trim();
+ //Slicing beofre the colon should always provide a useable hostname, so allocate a string for it
+ string host = requestHeaderValue.SliceBeforeParam(':').Trim().ToString();
+
+ //Verify that the host is usable
+ if (Uri.CheckHostName(host) == UriHostNameType.Unknown)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Verify that the host matches the host header if absolue uri is set
+ if (parseState.IsAbsoluteRequestUrl)
+ {
+ if (!host.Equals(parseState.Location!.Host, StringComparison.OrdinalIgnoreCase))
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ }
+
+ //store the host value
+ parseState.Location!.Host = host;
+
+ //If the port span is empty, no colon was found or the port is invalid
+ if (!port.IsEmpty)
+ {
+ //try to parse the port number
+ if (!int.TryParse(port, out int p) || p < 0 || p > ushort.MaxValue)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ //Store port
+ parseState.Location.Port = p;
+ }
+ }
+ break;
+ case HttpRequestHeader.Cookie:
+ {
+ //Local function to break cookie segments into key-value pairs
+ static void AddCookiesCallback(ReadOnlySpan<char> cookie, Dictionary<string, string> cookieContainer)
+ {
+ //Get the name parameter and alloc a string
+ string name = cookie.SliceBeforeParam('=').Trim().ToString();
+ //Get the value parameter and alloc a string
+ string value = cookie.SliceAfterParam('=').Trim().ToString();
+ //Add the cookie to the dictionary
+ _ = cookieContainer.TryAdd(name, value);
+ }
+ //Split all cookies by ; with trailing whitespace
+ requestHeaderValue.Split("; ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, AddCookiesCallback, Request.Cookies);
+ }
+ break;
+ case HttpRequestHeader.AcceptLanguage:
+ //Capture accept languages and store in the request accept collection
+ requestHeaderValue.Split(',', Request.AcceptLanguage, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ break;
+ case HttpRequestHeader.Accept:
+ //Capture accept content types and store in request accept collection
+ requestHeaderValue.Split(',', Request.Accept, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ break;
+ case HttpRequestHeader.Referer:
+ {
+ //Check the referer header and capture its uri instance, it should be absolutely parseable
+ if (!requestHeaderValue.IsEmpty && Uri.TryCreate(requestHeaderValue.ToString(), UriKind.Absolute, out Uri? refer))
+ {
+ Request.Referrer = refer;
+ }
+ }
+ break;
+ case HttpRequestHeader.Range:
+ {
+ //See if range bytes value has been set
+ ReadOnlySpan<char> rawRange = requestHeaderValue.SliceAfterParam("bytes=").TrimCRLF();
+ //Make sure the bytes parameter is set
+ if (rawRange.IsEmpty)
+ {
+ break;
+ }
+ //Get start range
+ ReadOnlySpan<char> startRange = rawRange.SliceBeforeParam('-');
+ //Get end range (empty if no - exists)
+ ReadOnlySpan<char> endRange = rawRange.SliceAfterParam('-');
+ //See if a range end is specified
+ if (endRange.IsEmpty)
+ {
+ //No end range specified, so only range start
+ if (long.TryParse(startRange, out long start) && start > -1)
+ {
+ //Create new range
+ Request.Range = new(start, -1);
+ break;
+ }
+ }
+ //Range has a start and end
+ else if (long.TryParse(startRange, out long start) && long.TryParse(endRange, out long end) && end > -1)
+ {
+ //get start and end components from range header
+ Request.Range = new(start, end);
+ break;
+ }
+ }
+ //Could not parse start range from header
+ return HttpStatusCode.RequestedRangeNotSatisfiable;
+ case HttpRequestHeader.UserAgent:
+ //Store user-agent
+ Request.UserAgent = requestHeaderValue.IsEmpty ? string.Empty : requestHeaderValue.TrimCRLF().ToString();
+ break;
+ //Special code for origin header
+ case HttpHelpers.Origin:
+ {
+ //Alloc a string for origin
+ string origin = requestHeaderValue.ToString();
+ //Origin headers should always be absolute address "parsable"
+ if (Uri.TryCreate(origin, UriKind.Absolute, out Uri? org))
+ {
+ Request.Origin = org;
+ }
+ }
+ break;
+ case HttpRequestHeader.Expect:
+ //Accept 100-continue for the Expect header value
+ Request.Expect = requestHeaderValue.Equals("100-continue", StringComparison.OrdinalIgnoreCase);
+ break;
+ default:
+ //By default store the header in the request header store
+ Request.Headers.Add(headerName.ToString(), requestHeaderValue.ToString());
+ break;
+ }
+ //Increment header count
+ headerCount++;
+ } while (true);
+
+ //If request is http11 then host is required
+ if (Request.HttpVersion == HttpVersion.Http11 && !hostFound)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ }
+ //Catch an arugment exception within the header add function to cause a bad request result
+ catch (ArgumentException)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Check the final location to make sure data was properly sent
+ if (string.IsNullOrWhiteSpace(parseState.Location?.Host)
+ || string.IsNullOrWhiteSpace(parseState.Location.Scheme)
+ || string.IsNullOrWhiteSpace(parseState.Location.Path)
+ )
+ {
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Store the finalized location
+ Request.Location = parseState.Location.Uri;
+
+ return 0;
+ }
+ /// <summary>
+ /// Prepares the entity body for the current HTTP1 request
+ /// </summary>
+ /// <param name="Request"></param>
+ /// <param name="Config">The current server <see cref="HttpConfig"/></param>
+ /// <param name="parseState">The HTTP1 parsing state</param>
+ /// <param name="reader">The <see cref="VnStreamReader"/> to read lines from the transport</param>
+ /// <returns>0 if the request line was successfully parsed, a status code if the request could not be processed</returns>
+ [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
+ public static HttpStatusCode Http1PrepareEntityBody(this HttpRequest Request, ref Http1ParseState parseState, ref TransportReader reader, in HttpConfig Config)
+ {
+ //If the content type is multipart, make sure its not too large to ingest
+ if (Request.ContentType == ContentType.MultiPart && parseState.ContentLength > Config.MaxFormDataUploadSize)
+ {
+ return HttpStatusCode.RequestEntityTooLarge;
+ }
+
+ //Only ingest the rest of the message body if the request is not a head, get, or trace methods
+ if ((Request.Method & (HttpMethod.GET | HttpMethod.HEAD | HttpMethod.TRACE)) != 0)
+ {
+ //Bad format to include a message body with a GET, HEAD, or TRACE request
+ if (parseState.ContentLength > 0)
+ {
+ Config.ServerLog.Debug("Message body received from {ip} with GET, HEAD, or TRACE request, was considered an error and the request was dropped", Request.RemoteEndPoint);
+ return HttpStatusCode.BadRequest;
+ }
+ else
+ {
+ //Success!
+ return 0;
+ }
+ }
+
+ //Check for chuncked transfer encoding
+ ReadOnlySpan<char> transfer = Request.Headers[HttpRequestHeader.TransferEncoding];
+ if (!transfer.IsEmpty && transfer.Contains("chunked", StringComparison.OrdinalIgnoreCase))
+ {
+ //Not a valid http version for chunked transfer encoding
+ if (Request.HttpVersion != HttpVersion.Http11)
+ {
+ return HttpStatusCode.BadRequest;
+ }
+ /*
+ * Was a content length also specified?
+ * This is an issue and is likely an attack. I am choosing not to support
+ * the HTTP 1.1 standard and will deny reading the rest of the data from the
+ * transport.
+ */
+ if (parseState.ContentLength > 0)
+ {
+ Config.ServerLog.Debug("Possible attempted desync, Content length + chunked encoding specified. RemoteEP: {ip}", Request.RemoteEndPoint);
+ return HttpStatusCode.BadRequest;
+ }
+
+ //Handle chunked transfer encoding (not implemented yet)
+ return HttpStatusCode.NotImplemented;
+ }
+ //Make sure non-zero cl header was provided
+ else if (parseState.ContentLength > 0)
+ {
+ //Open a temp buffer to store initial data in
+ ISlindingWindowBuffer<byte>? initData = reader.GetReminaingData(parseState.ContentLength);
+ //Setup the input stream and capture the initial data from the reader, and wrap the transport stream to read data directly
+ Request.InputStream.Prepare(parseState.ContentLength, initData);
+ Request.HasEntityBody = true;
+ }
+ //Success!
+ return 0;
+ }
+ }
+}
diff --git a/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
new file mode 100644
index 0000000..35c0275
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/ChunkDataAccumulator.cs
@@ -0,0 +1,228 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ChunkDataAccumulator.cs
+*
+* ChunkDataAccumulator.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// A specialized <see cref="IDataAccumulator{T}"/> for buffering data
+ /// in Http/1.1 chunks
+ /// </summary>
+ internal class ChunkDataAccumulator : IDataAccumulator<byte>, IHttpLifeCycle
+ {
+ public const int RESERVED_CHUNK_SUGGESTION = 32;
+
+ private readonly int BufferSize;
+ private readonly int ReservedSize;
+ private readonly Encoding Encoding;
+ private readonly ReadOnlyMemory<byte> CRLFBytes;
+
+ public ChunkDataAccumulator(Encoding encoding, int bufferSize)
+ {
+ Encoding = encoding;
+ CRLFBytes = encoding.GetBytes(HttpHelpers.CRLF);
+
+ ReservedSize = RESERVED_CHUNK_SUGGESTION;
+ BufferSize = bufferSize;
+ }
+
+ private byte[]? _buffer;
+ private int _reservedOffset;
+
+
+ ///<inheritdoc/>
+ public int RemainingSize => _buffer!.Length - AccumulatedSize;
+ ///<inheritdoc/>
+ public Span<byte> Remaining => _buffer!.AsSpan(AccumulatedSize);
+ ///<inheritdoc/>
+ public Span<byte> Accumulated => _buffer!.AsSpan(_reservedOffset, AccumulatedSize);
+ ///<inheritdoc/>
+ public int AccumulatedSize { get; set; }
+
+ private Memory<byte> CompleteChunk => _buffer.AsMemory(_reservedOffset, (AccumulatedSize - _reservedOffset));
+
+ /// <summary>
+ /// Attempts to buffer as much data as possible from the specified data
+ /// </summary>
+ /// <param name="data">The data to copy</param>
+ /// <returns>The number of bytes that were buffered</returns>
+ public ERRNO TryBufferChunk(ReadOnlySpan<byte> data)
+ {
+ //Calc data size and reserve space for final crlf
+ int dataToCopy = Math.Min(data.Length, RemainingSize - CRLFBytes.Length);
+
+ //Write as much data as possible
+ data[..dataToCopy].CopyTo(Remaining);
+ //Advance buffer
+ Advance(dataToCopy);
+
+ //Return number of bytes not written
+ return dataToCopy;
+ }
+
+ ///<inheritdoc/>
+ public void Advance(int count) => AccumulatedSize += count;
+
+ private void InitReserved()
+ {
+ //First reserve the chunk window by advancing the accumulator to the size
+ Advance(ReservedSize);
+ }
+
+ ///<inheritdoc/>
+ public void Reset()
+ {
+ //zero offsets
+ _reservedOffset = 0;
+ AccumulatedSize = 0;
+ //Init reserved segment
+ InitReserved();
+ }
+
+ /// <summary>
+ /// Writes the buffered data as a single chunk to the stream asynchronously. The internal
+ /// state is reset if writing compleded successfully
+ /// </summary>
+ /// <param name="output">The stream to write data to</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A value task that resolves when the data has been written to the stream</returns>
+ public async ValueTask FlushAsync(Stream output, CancellationToken cancellation)
+ {
+ //Update the chunk size
+ UpdateChunkSize();
+
+ //Write trailing chunk delimiter
+ this.Append(CRLFBytes.Span);
+
+ //write to stream
+ await output.WriteAsync(CompleteChunk, cancellation);
+
+ //Reset for next chunk
+ Reset();
+ }
+
+ /// <summary>
+ /// Writes the buffered data as a single chunk to the stream. The internal
+ /// state is reset if writing compleded successfully
+ /// </summary>
+ /// <param name="output">The stream to write data to</param>
+ /// <returns>A value task that resolves when the data has been written to the stream</returns>
+ public void Flush(Stream output)
+ {
+ //Update the chunk size
+ UpdateChunkSize();
+
+ //Write trailing chunk delimiter
+ this.Append(CRLFBytes.Span);
+
+ //write to stream
+ output.Write(CompleteChunk.Span);
+
+ //Reset for next chunk
+ Reset();
+ }
+
+ private void UpdateChunkSize()
+ {
+ const int CharBufSize = 2 * sizeof(int);
+
+ /*
+ * Alloc stack buffer to store chunk size hex chars
+ * the size of the buffer should be at least the number
+ * of bytes of the max chunk size
+ */
+ Span<char> s = stackalloc char[CharBufSize];
+
+ //Chunk size is the accumulated size without the reserved segment
+ int chunkSize = (AccumulatedSize - ReservedSize);
+
+ //format the chunk size
+ chunkSize.TryFormat(s, out int written, "x");
+
+ //temp buffer to store encoded data in
+ Span<byte> encBuf = stackalloc byte[ReservedSize];
+ //Encode the chunk size chars
+ int initOffset = Encoding.GetBytes(s[..written], encBuf);
+
+ Span<byte> encoded = encBuf[..initOffset];
+
+ /*
+ * We need to calcuate how to store the encoded buffer directly
+ * before the accumulated chunk data.
+ *
+ * This requires us to properly upshift the reserved buffer to
+ * the exact size required to store the encoded chunk size
+ */
+
+ _reservedOffset = (ReservedSize - (initOffset + CRLFBytes.Length));
+
+ Span<byte> upshifted = _buffer!.AsSpan(_reservedOffset, ReservedSize);
+
+ //First write the chunk size
+ encoded.CopyTo(upshifted);
+
+ //Upshift again to write the crlf
+ upshifted = upshifted[initOffset..];
+
+ //Copy crlf
+ CRLFBytes.Span.CopyTo(upshifted);
+ }
+
+
+ public void OnNewRequest()
+ {
+ InitReserved();
+ }
+
+ public void OnComplete()
+ {
+ //Zero offsets
+ _reservedOffset = 0;
+ AccumulatedSize = 0;
+ }
+
+ public void OnPrepare()
+ {
+ //Alloc buffer
+ _buffer = HttpBinBufferPool.Rent(BufferSize);
+ }
+
+ public void OnRelease()
+ {
+ HttpBinBufferPool.Return(_buffer!);
+ _buffer = null;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/ChunkedStream.cs b/lib/Net.Http/src/Core/Response/ChunkedStream.cs
new file mode 100644
index 0000000..953d763
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/ChunkedStream.cs
@@ -0,0 +1,252 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ChunkedStream.cs
+*
+* ChunkedStream.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+* Provides a Chunked data-encoding stream for writing data-chunks to
+* the transport using the basic chunked encoding format from MDN
+* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding#directives
+*
+* This stream will buffer entire chunks to avoid multiple writes to the
+* transport which can block or at minium cause overhead in context switching
+* which should be mostly avoided but cause overhead in copying. Time profiling
+* showed nearly equivalent performance for small chunks for synchronous writes.
+*
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+
+#pragma warning disable CA2215 // Dispose methods should call base class dispose
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal sealed partial class HttpResponse
+ {
+ /// <summary>
+ /// Writes chunked HTTP message bodies to an underlying streamwriter
+ /// </summary>
+ private sealed class ChunkedStream : Stream, IHttpLifeCycle
+ {
+ private const string LAST_CHUNK_STRING = "0\r\n\r\n";
+
+ private readonly ReadOnlyMemory<byte> LastChunk;
+ private readonly ChunkDataAccumulator ChunckAccumulator;
+ private readonly Func<Stream> GetTransport;
+
+ private Stream? TransportStream;
+ private bool HadError;
+
+ internal ChunkedStream(Encoding encoding, int chunkBufferSize, Func<Stream> getStream)
+ {
+ //Convert and store cached versions of the last chunk bytes
+ LastChunk = encoding.GetBytes(LAST_CHUNK_STRING);
+
+ //get the min buffer by rounding to the nearest page
+ int actualBufSize = (chunkBufferSize / 4096 + 1) * 4096;
+
+ //Init accumulator
+ ChunckAccumulator = new(encoding, actualBufSize);
+
+ GetTransport = getStream;
+ }
+
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+ public override long Length => throw new NotSupportedException();
+ public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("This stream cannot be read from");
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException("This stream does not support seeking");
+ public override void SetLength(long value) => throw new NotSupportedException("This stream does not support seeking");
+ public override void Flush() { }
+ public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+
+ public override void Write(byte[] buffer, int offset, int count) => Write(new ReadOnlySpan<byte>(buffer, offset, count));
+ public override void Write(ReadOnlySpan<byte> chunk)
+ {
+ //Only write non-zero chunks
+ if (chunk.Length <= 0)
+ {
+ return;
+ }
+
+ //Init reader
+ ForwardOnlyReader<byte> reader = new(in chunk);
+ try
+ {
+ do
+ {
+ //try to accumulate the chunk data
+ ERRNO written = ChunckAccumulator.TryBufferChunk(reader.Window);
+
+ //Not all data was buffered
+ if (written < reader.WindowSize)
+ {
+ //Advance reader
+ reader.Advance(written);
+
+ //Flush accumulator
+ ChunckAccumulator.Flush(TransportStream!);
+ //Continue to buffer / flush as needed
+ continue;
+ }
+ break;
+ }
+ while (true);
+ }
+ catch
+ {
+ HadError = true;
+ throw;
+ }
+ }
+
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
+
+ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> chunk, CancellationToken cancellationToken = default)
+ {
+ //Only write non-zero chunks
+ if (chunk.Length <= 0)
+ {
+ return;
+ }
+
+ try
+ {
+ //Init reader
+ ForwardOnlyMemoryReader<byte> reader = new(in chunk);
+
+ do
+ {
+ //try to accumulate the chunk data
+ ERRNO written = ChunckAccumulator.TryBufferChunk(reader.Window.Span);
+
+ //Not all data was buffered
+ if (written < reader.WindowSize)
+ {
+ //Advance reader
+ reader.Advance(written);
+
+ //Flush accumulator async
+ await ChunckAccumulator.FlushAsync(TransportStream!, cancellationToken);
+ //Continue to buffer / flush as needed
+ continue;
+ }
+ break;
+ }
+ while (true);
+ }
+ catch
+ {
+ HadError = true;
+ throw;
+ }
+ }
+
+
+ public override async ValueTask DisposeAsync()
+ {
+ //If write error occured, then do not write the last chunk
+ if (HadError)
+ {
+ return;
+ }
+
+ //Write remaining data to stream
+ await ChunckAccumulator.FlushAsync(TransportStream!, CancellationToken.None);
+
+ //Write final chunk
+ await TransportStream!.WriteAsync(LastChunk, CancellationToken.None);
+
+ //Flush base stream
+ await TransportStream!.FlushAsync(CancellationToken.None);
+ }
+
+ protected override void Dispose(bool disposing) => Close();
+
+ public override void Close()
+ {
+ //If write error occured, then do not write the last chunk
+ if (HadError)
+ {
+ return;
+ }
+
+ //Write remaining data to stream
+ ChunckAccumulator.Flush(TransportStream!);
+
+ //Write final chunk
+ TransportStream!.Write(LastChunk.Span);
+
+ //Flush base stream
+ TransportStream!.Flush();
+ }
+
+
+ #region Hooks
+
+ public void OnPrepare()
+ {
+ ChunckAccumulator.OnPrepare();
+ }
+
+ public void OnRelease()
+ {
+ ChunckAccumulator.OnRelease();
+ }
+
+ public void OnNewRequest()
+ {
+ ChunckAccumulator.OnNewRequest();
+
+ //Get transport stream even if not used
+ TransportStream = GetTransport();
+ }
+
+ public void OnComplete()
+ {
+ ChunckAccumulator.OnComplete();
+ TransportStream = null;
+
+ //Clear error flag
+ HadError = false;
+ }
+
+ #endregion
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/DirectStream.cs b/lib/Net.Http/src/Core/Response/DirectStream.cs
new file mode 100644
index 0000000..3c984ef
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/DirectStream.cs
@@ -0,0 +1,96 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: DirectStream.cs
+*
+* DirectStream.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed partial class HttpResponse
+ {
+ private sealed class DirectStream : Stream
+ {
+ private Stream? BaseStream;
+
+ public void Prepare(Stream transport)
+ {
+ BaseStream = transport;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ BaseStream!.Write(buffer, offset, count);
+ }
+
+ public override void Write(ReadOnlySpan<byte> buffer)
+ {
+ BaseStream!.Write(buffer);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return BaseStream!.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return BaseStream!.WriteAsync(buffer, cancellationToken);
+ }
+
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+ public override long Length => throw new InvalidOperationException("Stream does not have a length property");
+ public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException("Stream does not support reading");
+ public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException("Stream does not support seeking");
+ public override void SetLength(long value) => throw new InvalidOperationException("Stream does not support seeking");
+
+ public override void Flush() => BaseStream!.Flush();
+ public override Task FlushAsync(CancellationToken cancellationToken) => BaseStream!.FlushAsync(cancellationToken);
+
+
+ public override void Close()
+ {
+ BaseStream = null;
+ }
+
+
+ protected override void Dispose(bool disposing)
+ {
+ //Do not call base dispose
+ Close();
+ }
+
+ public override ValueTask DisposeAsync()
+ {
+ Close();
+ return ValueTask.CompletedTask;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
new file mode 100644
index 0000000..c43441c
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
@@ -0,0 +1,157 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HeaderDataAccumulator.cs
+*
+* HeaderDataAccumulator.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http.Core
+{
+ internal partial class HttpResponse
+ {
+
+ /// <summary>
+ /// Specialized data accumulator for compiling response headers
+ /// </summary>
+ private sealed class HeaderDataAccumulator : IDataAccumulator<char>, IStringSerializeable, IHttpLifeCycle
+ {
+ private readonly int BufferSize;
+
+ public HeaderDataAccumulator(int bufferSize)
+ {
+ //Calc correct char buffer size from bin buffer
+ this.BufferSize = bufferSize * sizeof(char);
+ }
+
+ /*
+ * May be an issue but wanted to avoid alloc
+ * if possible since this is a field in a ref
+ * type
+ */
+
+ private UnsafeMemoryHandle<byte>? _handle;
+
+ public void Advance(int count)
+ {
+ //Advance writer
+ AccumulatedSize += count;
+ }
+
+ public void WriteLine() => this.Append(HttpHelpers.CRLF);
+
+ public void WriteLine(ReadOnlySpan<char> data)
+ {
+ this.Append(data);
+ WriteLine();
+ }
+
+ /*Use bin buffers and cast to char buffer*/
+ private Span<char> Buffer => MemoryMarshal.Cast<byte, char>(_handle!.Value.Span);
+
+ public int RemainingSize => Buffer.Length - AccumulatedSize;
+ public Span<char> Remaining => Buffer[AccumulatedSize..];
+ public Span<char> Accumulated => Buffer[..AccumulatedSize];
+ public int AccumulatedSize { get; set; }
+
+ /// <summary>
+ /// Encodes the buffered data and writes it to the stream,
+ /// attemts to avoid further allocation where possible
+ /// </summary>
+ /// <param name="enc"></param>
+ /// <param name="baseStream"></param>
+ public void Flush(Encoding enc, Stream baseStream)
+ {
+ ReadOnlySpan<char> span = Accumulated;
+ //Calc the size of the binary buffer
+ int byteSize = enc.GetByteCount(span);
+ //See if there is enough room in the current char buffer
+ if (RemainingSize < (byteSize / sizeof(char)))
+ {
+ //We need to alloc a binary buffer to write data to
+ using UnsafeMemoryHandle<byte> bin = GetBinBuffer(byteSize, false);
+ //encode data
+ int encoded = enc.GetBytes(span, bin.Span);
+ //Write to stream
+ baseStream.Write(bin.Span[..encoded]);
+ }
+ else
+ {
+ //Get bin buffer by casting remaining accumulator buffer
+ Span<byte> bin = MemoryMarshal.Cast<char, byte>(Remaining);
+ //encode data
+ int encoded = enc.GetBytes(span, bin);
+ //Write to stream
+ baseStream.Write(bin[..encoded]);
+ }
+ Reset();
+ }
+
+ public void Reset() => AccumulatedSize = 0;
+
+
+
+ public void OnPrepare()
+ {
+ //Alloc buffer
+ _handle = GetBinBuffer(BufferSize, false);
+ }
+
+ public void OnRelease()
+ {
+ _handle!.Value.Dispose();
+ _handle = null;
+ }
+
+ public void OnNewRequest()
+ {}
+
+ public void OnComplete()
+ {
+ Reset();
+ }
+
+
+ ///<inheritdoc/>
+ public string Compile() => Accumulated.ToString();
+ ///<inheritdoc/>
+ public void Compile(ref ForwardOnlyWriter<char> writer) => writer.Append(Accumulated);
+ ///<inheritdoc/>
+ public ERRNO Compile(in Span<char> buffer)
+ {
+ ForwardOnlyWriter<char> writer = new(buffer);
+ Compile(ref writer);
+ return writer.Written;
+ }
+ ///<inheritdoc/>
+ public override string ToString() => Compile();
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/HttpContextExtensions.cs b/lib/Net.Http/src/Core/Response/HttpContextExtensions.cs
new file mode 100644
index 0000000..12702b3
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/HttpContextExtensions.cs
@@ -0,0 +1,124 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpContextExtensions.cs
+*
+* HttpContextExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+ /// <summary>
+ /// Provides extended funcionality of an <see cref="HttpContext"/>
+ /// </summary>
+ internal static class HttpContextExtensions
+ {
+ /// <summary>
+ /// Responds to a connection with the given status code
+ /// </summary>
+ /// <param name="ctx"></param>
+ /// <param name="code">The status code to send</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static void Respond(this HttpContext ctx, HttpStatusCode code) => ctx.Response.SetStatusCode(code);
+
+ /// <summary>
+ /// Begins a 301 redirection by sending status code and message heaaders to client.
+ /// </summary>
+ /// <param name="ctx"></param>
+ /// <param name="location">Location to direct client to, sets the "Location" header</param>
+ /// <exception cref="InvalidOperationException"></exception>
+ public static void Redirect301(this HttpContext ctx, Uri location)
+ {
+ ctx.Response.SetStatusCode(HttpStatusCode.MovedPermanently);
+ //Encode the string for propery http url formatting and set the location header
+ ctx.Response.Headers[HttpResponseHeader.Location] = location.ToString();
+ }
+
+ public const string NO_CACHE_STRING = "no-cache";
+ private static readonly string CACHE_CONTROL_VALUE = HttpHelpers.GetCacheString(CacheType.NoCache | CacheType.NoStore);
+
+ /// <summary>
+ /// Sets CacheControl and Pragma headers to no-cache
+ /// </summary>
+ /// <param name="Response"></param>
+ public static void SetNoCache(this HttpResponse Response)
+ {
+ Response.Headers[HttpResponseHeader.Pragma] = NO_CACHE_STRING;
+ Response.Headers[HttpResponseHeader.CacheControl] = CACHE_CONTROL_VALUE;
+ }
+
+ /// <summary>
+ /// Sets the content-range header to the specified parameters
+ /// </summary>
+ /// <param name="Response"></param>
+ /// <param name="start">The content range start</param>
+ /// <param name="end">The content range end</param>
+ /// <param name="length">The total content length</param>
+ public static void SetContentRange(this HttpResponse Response, long start, long end, long length)
+ {
+ //Alloc enough space to hold the string
+ Span<char> buffer = stackalloc char[64];
+ ForwardOnlyWriter<char> rangeBuilder = new(buffer);
+ //Build the range header in this format "bytes <begin>-<end>/<total>"
+ rangeBuilder.Append("bytes ");
+ rangeBuilder.Append(start);
+ rangeBuilder.Append('-');
+ rangeBuilder.Append(end);
+ rangeBuilder.Append('/');
+ rangeBuilder.Append(length);
+ //Print to a string and set the content range header
+ Response.Headers[HttpResponseHeader.ContentRange] = rangeBuilder.ToString();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ReadOnlyMemory<byte> GetRemainingConstrained(this IMemoryResponseReader reader, int limit)
+ {
+ //Calc the remaining bytes
+ int size = Math.Min(reader.Remaining, limit);
+ //get segment and slice
+ return reader.GetMemory()[..size];
+ }
+
+ /// <summary>
+ /// If an end-range is set, returns the remaining bytes up to the end-range, otherwise returns the entire request body length
+ /// </summary>
+ /// <param name="body"></param>
+ /// <param name="range">The data range</param>
+ /// <returns></returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static long GetResponseLengthWithRange(this IHttpResponseBody body, Tuple<long, long> range)
+ {
+ /*
+ * If end range is defined, then calculate the length of the response
+ *
+ * The length is the end range minus the start range plus 1 because range
+ * is an inclusve value
+ */
+
+ return range.Item2 < 0 ? body.Length : Math.Min(body.Length, range.Item2 - range.Item1 + 1);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
new file mode 100644
index 0000000..b03363e
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/HttpContextResponseWriting.cs
@@ -0,0 +1,253 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpContextResponseWriting.cs
+*
+* HttpContextResponseWriting.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.IO.Compression;
+using System.IO;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core
+{
+
+ internal partial class HttpContext
+ {
+ ///<inheritdoc/>
+ public async Task WriteResponseAsync(CancellationToken cancellation)
+ {
+ /*
+ * If exceptions are raised, the transport is unusable, the connection is terminated,
+ * and the release method will be called so the context can be reused
+ */
+
+ ValueTask discardTask = Request.InputStream.DiscardRemainingAsync(ParentServer.Config.DiscardBufferSize);
+
+ //See if discard is needed
+ if (ResponseBody.HasData)
+ {
+ //Parallel the write and discard
+ Task response = WriteResponseInternalAsync(cancellation);
+ Task discard = discardTask.AsTask();
+
+ await Task.WhenAll(response, discard);
+ }
+ else
+ {
+ await discardTask;
+ }
+
+ //Close response
+ await Response.CloseAsync();
+ }
+
+ /// <summary>
+ /// If implementing application set a response entity body, it is written to the output stream
+ /// </summary>
+ /// <param name="token">A token to cancel the operation</param>
+ private async Task WriteResponseInternalAsync(CancellationToken token)
+ {
+ //Adjust/append vary header
+ Response.Headers.Add(HttpResponseHeader.Vary, "Accept-Encoding");
+
+ //For head methods
+ if (Request.Method == HttpMethod.HEAD)
+ {
+ if (Request.Range != null)
+ {
+ //Get local range
+ Tuple<long, long> range = Request.Range;
+
+ //Calc constrained content length
+ long length = ResponseBody.GetResponseLengthWithRange(range);
+
+ //End range is inclusive so substract 1
+ long endRange = (range.Item1 + length) - 1;
+
+ //Set content-range header
+ Response.SetContentRange(range.Item1, endRange, length);
+
+ //Specify what the content length would be
+ Response.Headers[HttpResponseHeader.ContentLength] = length.ToString();
+
+ }
+ else
+ {
+ //If the request method is head, do everything but send the body
+ Response.Headers[HttpResponseHeader.ContentLength] = ResponseBody.Length.ToString();
+ }
+
+ //We must send headers here so content length doesnt get overwritten
+ Response.FlushHeaders();
+ }
+ else
+ {
+ Stream outputStream;
+ /*
+ * Process range header, data will not be compressed because that would
+ * require buffering, not a feature yet, and since the range will tell
+ * us the content length, we can get a direct stream to write to
+ */
+ if (Request.Range != null)
+ {
+ //Get local range
+ Tuple<long, long> range = Request.Range;
+
+ //Calc constrained content length
+ long length = ResponseBody.GetResponseLengthWithRange(range);
+
+ //End range is inclusive so substract 1
+ long endRange = (range.Item1 + length) - 1;
+
+ //Set content-range header
+ Response.SetContentRange(range.Item1, endRange, length);
+
+ //Get the raw output stream and set the length to the number of bytes
+ outputStream = Response.GetStream(length);
+
+ await WriteEntityDataAsync(outputStream, length, token);
+ }
+ else
+ {
+ //Determine if compression should be used
+ bool compressionDisabled =
+ //disabled because app code disabled it
+ ContextFlags.IsSet(COMPRESSION_DISABLED_MSK)
+ //Disabled because too large or too small
+ || ResponseBody.Length >= ParentServer.Config.CompressionLimit
+ || ResponseBody.Length < ParentServer.Config.CompressionMinimum
+ //Disabled because lower than http11 does not support chunked encoding
+ || Request.HttpVersion < HttpVersion.Http11;
+
+ //Get first compression method or none if disabled
+ HttpRequestExtensions.CompressionType ct = compressionDisabled ? HttpRequestExtensions.CompressionType.None : Request.GetCompressionSupport();
+
+ switch (ct)
+ {
+ case HttpRequestExtensions.CompressionType.Gzip:
+ {
+ //Specify gzip encoding (using chunked encoding)
+ Response.Headers[HttpResponseHeader.ContentEncoding] = "gzip";
+ //get the chunked output stream
+ Stream chunked = Response.GetStream();
+ //Use chunked encoding and send data as its written
+ outputStream = new GZipStream(chunked, ParentServer.Config.CompressionLevel, false);
+ }
+ break;
+ case HttpRequestExtensions.CompressionType.Deflate:
+ {
+ //Specify gzip encoding (using chunked encoding)
+ Response.Headers[HttpResponseHeader.ContentEncoding] = "deflate";
+ //get the chunked output stream
+ Stream chunked = Response.GetStream();
+ //Use chunked encoding and send data as its written
+ outputStream = new DeflateStream(chunked, ParentServer.Config.CompressionLevel, false);
+ }
+ break;
+ case HttpRequestExtensions.CompressionType.Brotli:
+ {
+ //Specify Brotli encoding (using chunked encoding)
+ Response.Headers[HttpResponseHeader.ContentEncoding] = "br";
+ //get the chunked output stream
+ Stream chunked = Response.GetStream();
+ //Use chunked encoding and send data as its written
+ outputStream = new BrotliStream(chunked, ParentServer.Config.CompressionLevel, false);
+ }
+ break;
+ //Default is no compression
+ case HttpRequestExtensions.CompressionType.None:
+ default:
+ //Since we know how long the response will be, we can submit it now (see note above for same issues)
+ outputStream = Response.GetStream(ResponseBody.Length);
+ break;
+ }
+
+ //Write entity to output
+ await WriteEntityDataAsync(outputStream, token);
+ }
+ }
+ }
+
+ private async Task WriteEntityDataAsync(Stream outputStream, CancellationToken token)
+ {
+ try
+ {
+ //Determine if buffer is required
+ if (ResponseBody.BufferRequired)
+ {
+ //Calc a buffer size (always a safe cast since rbs is an integer)
+ int bufferSize = (int)Math.Min((long)ParentServer.Config.ResponseBufferSize, ResponseBody.Length);
+
+ //Alloc buffer, and dispose when completed
+ using IMemoryOwner<byte> buffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+
+ //Write response
+ await ResponseBody.WriteEntityAsync(outputStream, buffer.Memory, token);
+ }
+ //No buffer is required, write response directly
+ else
+ {
+ //Write without buffer
+ await ResponseBody.WriteEntityAsync(outputStream, null, token);
+ }
+ }
+ finally
+ {
+ //always dispose output stream
+ await outputStream.DisposeAsync();
+ }
+ }
+
+ private async Task WriteEntityDataAsync(Stream outputStream, long length, CancellationToken token)
+ {
+ try
+ {
+ //Determine if buffer is required
+ if (ResponseBody.BufferRequired)
+ {
+ //Calc a buffer size (always a safe cast since rbs is an integer)
+ int bufferSize = (int)Math.Min((long)ParentServer.Config.ResponseBufferSize, ResponseBody.Length);
+
+ //Alloc buffer, and dispose when completed
+ using IMemoryOwner<byte> buffer = CoreBufferHelpers.GetMemory(bufferSize, false);
+
+ //Write response
+ await ResponseBody.WriteEntityAsync(outputStream, length, buffer.Memory, token);
+ }
+ //No buffer is required, write response directly
+ else
+ {
+ //Write without buffer
+ await ResponseBody.WriteEntityAsync(outputStream, length, null, token);
+ }
+ }
+ finally
+ {
+ //always dispose output stream
+ await outputStream.DisposeAsync();
+ }
+ }
+ }
+}
diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs
new file mode 100644
index 0000000..ab0971d
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs
@@ -0,0 +1,307 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpResponse.cs
+*
+* HttpResponse.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+using VNLib.Utils.IO;
+
+namespace VNLib.Net.Http.Core
+{
+ internal partial class HttpResponse : IHttpLifeCycle
+ {
+ private readonly HashSet<HttpCookie> Cookies;
+ private readonly HeaderDataAccumulator Writer;
+
+ private readonly DirectStream ReusableDirectStream;
+ private readonly ChunkedStream ReusableChunkedStream;
+ private readonly Func<Stream> _getStream;
+ private readonly Encoding ResponseEncoding;
+ private readonly Func<HttpVersion> GetVersion;
+
+ private bool HeadersSent;
+ private bool HeadersBegun;
+
+ private HttpStatusCode _code;
+
+ /// <summary>
+ /// Response header collection
+ /// </summary>
+ public VnWebHeaderCollection Headers { get; }
+
+ public HttpResponse(Encoding encoding, int headerBufferSize, int chunkedBufferSize, Func<Stream> getStream, Func<HttpVersion> getVersion)
+ {
+ //Initialize a new header collection and a cookie jar
+ Headers = new();
+ Cookies = new();
+ //Create a new reusable writer stream
+ Writer = new(headerBufferSize);
+
+ _getStream = getStream;
+ ResponseEncoding = encoding;
+
+ //Create a new chunked stream
+ ReusableChunkedStream = new(encoding, chunkedBufferSize, getStream);
+ ReusableDirectStream = new();
+ GetVersion = getVersion;
+ }
+
+
+ /// <summary>
+ /// Sets the status code of the response
+ /// </summary>
+ /// <exception cref="InvalidOperationException"></exception>
+ internal void SetStatusCode(HttpStatusCode code)
+ {
+ if (HeadersBegun)
+ {
+ throw new InvalidOperationException("Status code has already been sent");
+ }
+
+ _code = code;
+ }
+
+ /// <summary>
+ /// Adds a new http-cookie to the collection
+ /// </summary>
+ /// <param name="cookie">Cookie to add</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void AddCookie(HttpCookie cookie) => Cookies.Add(cookie);
+
+ /// <summary>
+ /// Allows sending an early 100-Continue status message to the client
+ /// </summary>
+ /// <exception cref="InvalidOperationException"></exception>
+ internal async Task SendEarly100ContinueAsync()
+ {
+ Check();
+ //Send a status message with the continue response status
+ Writer.WriteLine(HttpHelpers.GetResponseString(GetVersion(), HttpStatusCode.Continue));
+ //Trailing crlf
+ Writer.WriteLine();
+ //get base stream
+ Stream bs = _getStream();
+ //Flush writer to stream (will reset the buffer)
+ Writer.Flush(ResponseEncoding, bs);
+ //Flush the base stream
+ await bs.FlushAsync();
+ }
+
+ /// <summary>
+ /// Sends the status message and all available headers to the client.
+ /// Headers set after method returns will be sent when output stream is requested or scope exits
+ /// </summary>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public void FlushHeaders()
+ {
+ Check();
+ //If headers havent been sent yet, start with status code
+ if (!HeadersBegun)
+ {
+ //write status code first
+ Writer.WriteLine(HttpHelpers.GetResponseString(GetVersion(), _code));
+
+ //Write the date to header buffer
+ Writer.Append("Date: ");
+ Writer.Append(DateTimeOffset.UtcNow, "R");
+ Writer.WriteLine();
+ //Set begun flag
+ HeadersBegun = true;
+ }
+ //Write headers
+ for (int i = 0; i < Headers.Count; i++)
+ {
+ Writer.Append(Headers.Keys[i]); //Write header key
+ Writer.Append(": "); //Write separator
+ Writer.WriteLine(Headers[i]); //Write the header value
+ }
+ //Remove writen headers
+ Headers.Clear();
+ //Write cookies if any are set
+ if (Cookies.Count > 0)
+ {
+ //Write cookies if any have been set
+ foreach (HttpCookie cookie in Cookies)
+ {
+ Writer.Append("Set-Cookie: ");
+ Writer.Append(in cookie);
+ Writer.WriteLine();
+ }
+ //Clear all current cookies
+ Cookies.Clear();
+ }
+ }
+ private void EndFlushHeaders(Stream transport)
+ {
+ //Sent all available headers
+ FlushHeaders();
+ //Last line to end headers
+ Writer.WriteLine();
+
+ //Flush writer
+ Writer.Flush(ResponseEncoding, transport);
+ //Update sent headers
+ HeadersSent = true;
+ }
+
+ /// <summary>
+ /// Gets a stream for writing data of a specified length directly to the client
+ /// </summary>
+ /// <param name="ContentLength"></param>
+ /// <returns>A <see cref="Stream"/> configured for writing data to client</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public Stream GetStream(long ContentLength)
+ {
+ Check();
+ //Add content length header
+ Headers[HttpResponseHeader.ContentLength] = ContentLength.ToString();
+ //End sending headers so the user can write to the ouput stream
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+
+ //Init direct stream
+ ReusableDirectStream.Prepare(transport);
+
+ //Return the direct stream
+ return ReusableDirectStream;
+ }
+
+ /// <summary>
+ /// Sets up the client for chuncked encoding and gets a stream that allows for chuncks to be sent. User must call dispose on stream when done writing data
+ /// </summary>
+ /// <returns><see cref="Stream"/> supporting chunked encoding</returns>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="InvalidOperationException"></exception>
+ public Stream GetStream()
+ {
+#if DEBUG
+ if (GetVersion() != HttpVersion.Http11)
+ {
+ throw new InvalidOperationException("Chunked transfer encoding is not acceptable for this http version");
+ }
+#endif
+ Check();
+ //Set encoding type to chunked with user-defined compression
+ Headers[HttpResponseHeader.TransferEncoding] = "chunked";
+ //End sending headers so the user can write to the ouput stream
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+
+ //Return the reusable stream
+ return ReusableChunkedStream;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void Check()
+ {
+ if (HeadersSent)
+ {
+ throw new InvalidOperationException("Headers have already been sent!");
+ }
+ }
+
+ /// <summary>
+ /// Finalzies the response to a client by sending all available headers if
+ /// they have not been sent yet
+ /// </summary>
+ /// <exception cref="OutOfMemoryException"></exception>
+ internal async ValueTask CloseAsync()
+ {
+ //If headers havent been sent yet, send them and there must be no content
+ if (!HeadersBegun)
+ {
+ //RFC 7230, length only set on 200 + but not 204
+ if ((int)_code >= 200 && (int)_code != 204)
+ {
+ //If headers havent been sent by this stage there is no content, so set length to 0
+ Headers[HttpResponseHeader.ContentLength] = "0";
+ }
+ //Flush transport
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+ //Flush transport
+ await transport.FlushAsync();
+ }
+ //Headers have been started but not finished yet
+ else if (!HeadersSent)
+ {
+ //RFC 7230, length only set on 200 + but not 204
+ if ((int)_code >= 200 && (int)_code != 204)
+ {
+ //If headers havent been sent by this stage there is no content, so set length to 0
+ Headers[HttpResponseHeader.ContentLength] = "0";
+ }
+ //If headers arent done sending yet, conclude headers
+ Stream transport = _getStream();
+ EndFlushHeaders(transport);
+ //Flush transport
+ await transport.FlushAsync();
+ }
+ }
+
+
+ public void OnPrepare()
+ {
+ //Propagate all child lifecycle hooks
+ Writer.OnPrepare();
+ ReusableChunkedStream.OnPrepare();
+ }
+
+ public void OnRelease()
+ {
+ Writer.OnRelease();
+ ReusableChunkedStream.OnRelease();
+ }
+
+ public void OnNewRequest()
+ {
+ //Default to okay status code
+ _code = HttpStatusCode.OK;
+
+ Writer.OnNewRequest();
+ ReusableChunkedStream.OnNewRequest();
+ }
+
+ public void OnComplete()
+ {
+ //Clear headers and cookies
+ Headers.Clear();
+ Cookies.Clear();
+ //Reset status values
+ HeadersBegun = false;
+ HeadersSent = false;
+
+ //Call child lifecycle hooks
+ Writer.OnComplete();
+ ReusableChunkedStream.OnComplete();
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/ResponseWriter.cs b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
new file mode 100644
index 0000000..c9f20b5
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/ResponseWriter.cs
@@ -0,0 +1,182 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ResponseWriter.cs
+*
+* ResponseWriter.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Extensions;
+
+
+namespace VNLib.Net.Http.Core
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "<Pending>")]
+ internal sealed class ResponseWriter : IHttpResponseBody, IHttpLifeCycle
+ {
+ private Stream? _streamResponse;
+ private IMemoryResponseReader? _memoryResponse;
+
+ ///<inheritdoc/>
+ public bool HasData { get; private set; }
+
+ //Buffering is required when a stream is set
+ bool IHttpResponseBody.BufferRequired => _streamResponse != null;
+
+ ///<inheritdoc/>
+ public long Length { get; private set; }
+
+ /// <summary>
+ /// Attempts to set the response body as a stream
+ /// </summary>
+ /// <param name="response">The stream response body to read</param>
+ /// <returns>True if the response entity could be set, false if it has already been set</returns>
+ internal bool TrySetResponseBody(Stream response)
+ {
+ if (HasData)
+ {
+ return false;
+ }
+
+ //Get relative length of the stream, IE the remaning bytes in the stream if position has been modified
+ Length = (response.Length - response.Position);
+ //Store ref to stream
+ _streamResponse = response;
+ //update has-data flag
+ HasData = true;
+ return true;
+ }
+
+ /// <summary>
+ /// Attempts to set the response entity
+ /// </summary>
+ /// <param name="response">The memory response to set</param>
+ /// <returns>True if the response entity could be set, false if it has already been set</returns>
+ internal bool TrySetResponseBody(IMemoryResponseReader response)
+ {
+ if (HasData)
+ {
+ return false;
+ }
+
+ //Get length
+ Length = response.Remaining;
+ //Store ref to stream
+ _memoryResponse = response;
+ //update has-data flag
+ HasData = true;
+ return true;
+ }
+
+ ///<inheritdoc/>
+ async Task IHttpResponseBody.WriteEntityAsync(Stream dest, long count, Memory<byte>? buffer, CancellationToken token)
+ {
+ //Write a sliding window response
+ if (_memoryResponse != null)
+ {
+ //Get min value from count/range length
+ int remaining = (int)Math.Min(count, _memoryResponse.Remaining);
+
+ //Write response body from memory
+ while (remaining > 0)
+ {
+ //Get remaining segment
+ ReadOnlyMemory<byte> segment = _memoryResponse.GetRemainingConstrained(remaining);
+
+ //Write segment to output stream
+ await dest.WriteAsync(segment, token);
+
+ int written = segment.Length;
+
+ //Advance by the written ammount
+ _memoryResponse.Advance(written);
+
+ //Update remaining
+ remaining -= written;
+ }
+ }
+ else
+ {
+ //Buffer is required, and count must be supplied
+ await _streamResponse!.CopyToAsync(dest, buffer!.Value, count, token);
+ }
+ }
+
+ ///<inheritdoc/>
+ async Task IHttpResponseBody.WriteEntityAsync(Stream dest, Memory<byte>? buffer, CancellationToken token)
+ {
+ //Write a sliding window response
+ if (_memoryResponse != null)
+ {
+ //Write response body from memory
+ while (_memoryResponse.Remaining > 0)
+ {
+ //Get segment
+ ReadOnlyMemory<byte> segment = _memoryResponse.GetMemory();
+
+ await dest.WriteAsync(segment, token);
+
+ //Advance by
+ _memoryResponse.Advance(segment.Length);
+ }
+ }
+ else
+ {
+ //Buffer is required
+ await _streamResponse!.CopyToAsync(dest, buffer!.Value, token);
+
+ //Try to dispose the response stream
+ await _streamResponse!.DisposeAsync();
+
+ //remove ref
+ _streamResponse = null;
+ }
+ }
+
+ ///<inheritdoc/>
+ void IHttpLifeCycle.OnPrepare()
+ {}
+
+ ///<inheritdoc/>
+ void IHttpLifeCycle.OnRelease()
+ {}
+
+ ///<inheritdoc/>
+ void IHttpLifeCycle.OnNewRequest()
+ {}
+
+ public void OnComplete()
+ {
+ //Clear has data flag
+ HasData = false;
+ Length = 0;
+
+ //Clear rseponse containers
+ _streamResponse?.Dispose();
+ _streamResponse = null;
+ _memoryResponse?.Close();
+ _memoryResponse = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
new file mode 100644
index 0000000..36ebb66
--- /dev/null
+++ b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: SharedHeaderReaderBuffer.cs
+*
+* SharedHeaderReaderBuffer.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Runtime.InteropServices;
+
+using VNLib.Utils.Memory;
+
+
+
+namespace VNLib.Net.Http.Core
+{
+ sealed class SharedHeaderReaderBuffer : IHttpLifeCycle
+ {
+ private UnsafeMemoryHandle<byte>? Handle;
+
+ /// <summary>
+ /// The size of the binary buffer
+ /// </summary>
+ public int BinLength { get; }
+
+ private readonly int _bufferSize;
+
+ internal SharedHeaderReaderBuffer(int length)
+ {
+ _bufferSize = length + (length * sizeof(char));
+
+ //Bin buffer is the specified size
+ BinLength = length;
+ }
+
+ /// <summary>
+ /// The binary buffer to store reader information
+ /// </summary>
+ public Span<byte> BinBuffer => Handle!.Value.Span[..BinLength];
+
+ /// <summary>
+ /// The char buffer to store read characters in
+ /// </summary>
+ public Span<char> CharBuffer => MemoryMarshal.Cast<byte, char>(Handle!.Value.Span[BinLength..]);
+
+ public void OnPrepare()
+ {
+ //Alloc the shared buffer
+ Handle = CoreBufferHelpers.GetBinBuffer(_bufferSize, true);
+ }
+
+ public void OnRelease()
+ {
+ //Free buffer
+ Handle?.Dispose();
+ Handle = null;
+ }
+
+ public void OnNewRequest()
+ {}
+
+ public void OnComplete()
+ {
+ //Zero buffer
+ Handle!.Value.Span.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/VnHeaderCollection.cs b/lib/Net.Http/src/Core/VnHeaderCollection.cs
new file mode 100644
index 0000000..8ce3c88
--- /dev/null
+++ b/lib/Net.Http/src/Core/VnHeaderCollection.cs
@@ -0,0 +1,75 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: VnHeaderCollection.cs
+*
+* VnHeaderCollection.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed class VnHeaderCollection : IHeaderCollection
+ {
+ private VnWebHeaderCollection _RequestHeaders;
+ private VnWebHeaderCollection _ResponseHeaders;
+
+
+ IEnumerable<KeyValuePair<string, string>> IHeaderCollection.RequestHeaders => _RequestHeaders!;
+
+ IEnumerable<KeyValuePair<string, string>> IHeaderCollection.ResponseHeaders => _ResponseHeaders!;
+
+ internal VnHeaderCollection(HttpContext context)
+ {
+ _RequestHeaders = context.Request.Headers;
+ _ResponseHeaders = context.Response.Headers;
+ }
+
+ string? IHeaderCollection.this[string index]
+ {
+ get => _RequestHeaders[index];
+ set => _ResponseHeaders[index] = value;
+ }
+
+ string IHeaderCollection.this[HttpResponseHeader index]
+ {
+ set => _ResponseHeaders[index] = value;
+ }
+
+ string? IHeaderCollection.this[HttpRequestHeader index] => _RequestHeaders[index];
+
+ bool IHeaderCollection.HeaderSet(HttpResponseHeader header) => !string.IsNullOrEmpty(_ResponseHeaders[header]);
+
+ bool IHeaderCollection.HeaderSet(HttpRequestHeader header) => !string.IsNullOrEmpty(_RequestHeaders[header]);
+
+ void IHeaderCollection.Append(HttpResponseHeader header, string? value) => _ResponseHeaders.Add(header, value);
+
+ void IHeaderCollection.Append(string header, string? value) => _ResponseHeaders.Add(header, value);
+
+#nullable disable
+ internal void Clear()
+ {
+ _RequestHeaders = null;
+ _ResponseHeaders = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Exceptions/ContentTypeException.cs b/lib/Net.Http/src/Exceptions/ContentTypeException.cs
new file mode 100644
index 0000000..abff151
--- /dev/null
+++ b/lib/Net.Http/src/Exceptions/ContentTypeException.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ContentTypeException.cs
+*
+* ContentTypeException.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Thrown when the application attempts to submit a response to a client
+ /// when the client does not accept the given content type
+ /// </summary>
+ public sealed class ContentTypeUnacceptableException:FormatException
+ {
+ public ContentTypeUnacceptableException(string message) : base(message) {}
+
+ public ContentTypeUnacceptableException()
+ {}
+
+ public ContentTypeUnacceptableException(string message, Exception innerException) : base(message, innerException)
+ {}
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Exceptions/TerminateConnectionException.cs b/lib/Net.Http/src/Exceptions/TerminateConnectionException.cs
new file mode 100644
index 0000000..b854b6e
--- /dev/null
+++ b/lib/Net.Http/src/Exceptions/TerminateConnectionException.cs
@@ -0,0 +1,57 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TerminateConnectionException.cs
+*
+* TerminateConnectionException.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// User code may throw this exception to signal the <see cref="HttpServer"/> to drop
+ /// the transport connection and return an optional status code
+ /// </summary>
+ public class TerminateConnectionException : Exception
+ {
+ internal HttpStatusCode Code { get; }
+
+ /// <summary>
+ /// Creates a new instance that terminates the connection without sending a response to the connection
+ /// </summary>
+ public TerminateConnectionException() : base(){}
+ /// <summary>
+ /// Creates a new instance of the connection exception with an error code to respond to the connection with
+ /// </summary>
+ /// <param name="responseCode">The status code to send to the user</param>
+ public TerminateConnectionException(HttpStatusCode responseCode)
+ {
+ this.Code = responseCode;
+ }
+
+ public TerminateConnectionException(string message) : base(message)
+ {}
+
+ public TerminateConnectionException(string message, Exception innerException) : base(message, innerException)
+ {}
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/FileUpload.cs b/lib/Net.Http/src/FileUpload.cs
new file mode 100644
index 0000000..654d682
--- /dev/null
+++ b/lib/Net.Http/src/FileUpload.cs
@@ -0,0 +1,122 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: FileUpload.cs
+*
+* FileUpload.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+using static VNLib.Net.Http.Core.CoreBufferHelpers;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents an file that was received as an entity body, either using Multipart/FormData or as the entity body itself
+ /// </summary>
+ public readonly struct FileUpload
+ {
+ /// <summary>
+ /// Content type of uploaded file
+ /// </summary>
+ public readonly ContentType ContentType;
+ /// <summary>
+ /// Name of file uploaded
+ /// </summary>
+ public readonly string FileName;
+ /// <summary>
+ /// The file data captured on upload
+ /// </summary>
+ public readonly Stream FileData;
+
+ private readonly bool OwnsHandle;
+
+ /// <summary>
+ /// Allocates a new binary buffer, encodes, and copies the specified data to a new <see cref="FileUpload"/>
+ /// structure of the specified content type
+ /// </summary>
+ /// <param name="data">The string data to copy</param>
+ /// <param name="dataEncoding">The encoding instance to encode the string data from</param>
+ /// <param name="filename">The name of the file</param>
+ /// <param name="ct">The content type of the file data</param>
+ /// <returns>The <see cref="FileUpload"/> container</returns>
+ internal static FileUpload FromString(ReadOnlySpan<char> data, Encoding dataEncoding, string filename, ContentType ct)
+ {
+ //get number of bytes
+ int bytes = dataEncoding.GetByteCount(data);
+ //get a buffer from the HTTP heap
+ MemoryHandle<byte> buffHandle = HttpPrivateHeap.Alloc<byte>(bytes);
+ try
+ {
+ //Convert back to binary
+ bytes = dataEncoding.GetBytes(data, buffHandle);
+
+ //Create a new memory stream encapsulating the file data
+ VnMemoryStream vms = VnMemoryStream.ConsumeHandle(buffHandle, bytes, true);
+
+ //Create new upload wrapper
+ return new (vms, filename, ct, true);
+ }
+ catch
+ {
+ //Make sure the hanle gets disposed if there is an error
+ buffHandle.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Initialzes a new <see cref="FileUpload"/> structure from the specified data
+ /// and file information.
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="filename"></param>
+ /// <param name="ct"></param>
+ /// <param name="ownsHandle"></param>
+ public FileUpload(Stream data, string filename, ContentType ct, bool ownsHandle)
+ {
+ FileName = filename;
+ ContentType = ct;
+ //Store handle ownership
+ OwnsHandle = ownsHandle;
+ //Store the stream
+ FileData = data;
+ }
+
+ /// <summary>
+ /// Releases any memory the current instance holds if it owns the handles
+ /// </summary>
+ internal readonly void Free()
+ {
+ //Dispose the handle if we own it
+ if (OwnsHandle)
+ {
+ //This should always be synchronous
+ FileData.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs b/lib/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs
new file mode 100644
index 0000000..d81b7eb
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/AlternateProtocolTransportStreamWrapper.cs
@@ -0,0 +1,53 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: AlternateProtocolTransportStreamWrapper.cs
+*
+* AlternateProtocolTransportStreamWrapper.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+using VNLib.Utils.IO;
+
+#pragma warning disable CA2215 // Dispose methods should call base class dispose
+
+namespace VNLib.Net.Http.Core
+{
+ internal sealed class AlternateProtocolTransportStreamWrapper : BackingStream<Stream>
+ {
+ public AlternateProtocolTransportStreamWrapper(Stream transport)
+ {
+ this.BaseStream = transport;
+ }
+
+ //Do not allow the caller to dispose the transport stream
+
+ protected override void Dispose(bool disposing)
+ { }
+ public override ValueTask DisposeAsync()
+ {
+ return ValueTask.CompletedTask;
+ }
+ public override void Close()
+ {}
+ }
+}
diff --git a/lib/Net.Http/src/Helpers/ContentType.cs b/lib/Net.Http/src/Helpers/ContentType.cs
new file mode 100644
index 0000000..ce7b7ce
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/ContentType.cs
@@ -0,0 +1,1180 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ContentType.cs
+*
+* ContentType.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Mime content type
+ /// </summary>
+ public enum ContentType
+ {
+ NonSupported,
+ MultiPart,
+ UrlEncoded,
+ Aab,
+ Aac,
+ Aam,
+ Aas,
+ Abw,
+ Ac,
+ Acc,
+ Ace,
+ Acu,
+ Acutc,
+ Adp,
+ Aep,
+ Afm,
+ Afp,
+ Ahead,
+ Ai,
+ Aif,
+ Aifc,
+ Aiff,
+ Air,
+ Ait,
+ Ami,
+ Amr,
+ Apk,
+ Apng,
+ Appcache,
+ Apr,
+ Arc,
+ Arj,
+ Asc,
+ Asf,
+ Asm,
+ Aso,
+ Asx,
+ Atc,
+ Atom,
+ Atomcat,
+ Atomsvc,
+ Atx,
+ Au,
+ Avi,
+ Avif,
+ Aw,
+ Azf,
+ Azs,
+ Azv,
+ Azw,
+ B16,
+ Bat,
+ Bcpio,
+ Bdf,
+ Bdm,
+ Bdoc,
+ Bed,
+ Bh2,
+ Binary,
+ Blb,
+ Blorb,
+ Bmi,
+ Bmml,
+ Bmp,
+ Book,
+ Box,
+ Boz,
+ Bpk,
+ Bsp,
+ Btif,
+ Buffer,
+ Bz,
+ Bz2,
+ C,
+ C11amc,
+ C11amz,
+ C4d,
+ C4f,
+ C4g,
+ C4p,
+ C4u,
+ Cab,
+ Caf,
+ Cap,
+ Car,
+ Cat,
+ Cb7,
+ Cba,
+ Cbr,
+ Cbt,
+ Cbz,
+ Cc,
+ Cco,
+ Cct,
+ Ccxml,
+ Cdbcmsg,
+ Cdf,
+ Cdfx,
+ Cdkey,
+ Cdmia,
+ Cdmic,
+ Cdmid,
+ Cdmio,
+ Cdmiq,
+ Cdx,
+ Cdxml,
+ Cdy,
+ Cer,
+ Cfs,
+ Cgm,
+ Chat,
+ Chm,
+ Chrt,
+ Cif,
+ Cii,
+ Cil,
+ Cjs,
+ Cla,
+ Clkk,
+ Clkp,
+ Clkt,
+ Clkw,
+ Clkx,
+ Clp,
+ Cmc,
+ Cmdf,
+ Cml,
+ Cmp,
+ Cmx,
+ Cod,
+ Coffee,
+ Com,
+ Conf,
+ Cpio,
+ Cpp,
+ Cpt,
+ Crd,
+ Crl,
+ Crt,
+ Crx,
+ Csh,
+ Csl,
+ Csml,
+ Csp,
+ Css,
+ Cst,
+ Csv,
+ Cu,
+ Curl,
+ Cww,
+ Cxt,
+ Cxx,
+ Dae,
+ Daf,
+ Dart,
+ Dataless,
+ Davmount,
+ Dbf,
+ Dbk,
+ Dcr,
+ Dcurl,
+ Dd2,
+ Ddd,
+ Ddf,
+ Dds,
+ Deb,
+ Def,
+ Deploy,
+ Der,
+ Dfac,
+ Dgc,
+ Dic,
+ Dir,
+ Dis,
+ Dist,
+ Distz,
+ Djv,
+ Djvu,
+ Dll,
+ Dmg,
+ Dmp,
+ Dms,
+ Dna,
+ Doc,
+ Docm,
+ Docx,
+ Dot,
+ Dotm,
+ Dotx,
+ Dp,
+ Dpg,
+ Dra,
+ Drle,
+ Dsc,
+ Dssc,
+ Dtb,
+ Dtd,
+ Dts,
+ Dtshd,
+ Dump,
+ Dvb,
+ Dvi,
+ Dwd,
+ Dwf,
+ Dwg,
+ Dxf,
+ Dxp,
+ Dxr,
+ Ear,
+ Ecma,
+ Edm,
+ Edx,
+ Efif,
+ Ei6,
+ Elc,
+ Emf,
+ Eml,
+ Emma,
+ Emz,
+ Eol,
+ Eot,
+ Eps,
+ Epub,
+ Es,
+ Es3,
+ Esa,
+ Esf,
+ Et3,
+ Etx,
+ Eva,
+ Evy,
+ Exe,
+ Exi,
+ Exp,
+ Exr,
+ Ext,
+ Ez,
+ Ez2,
+ Ez3,
+ F,
+ F4v,
+ Fortran,
+ F90,
+ Fbs,
+ Fcdt,
+ Fcs,
+ Fdf,
+ Fdt,
+ Fg5,
+ Fgd,
+ Fh,
+ Fh4,
+ Fh5,
+ Fh7,
+ Fhc,
+ Fig,
+ Fits,
+ Flac,
+ Fli,
+ Flo,
+ Flv,
+ Flw,
+ Flx,
+ Fly,
+ Fm,
+ Fnc,
+ Fo,
+ For,
+ Fpx,
+ Frame,
+ Fsc,
+ Fst,
+ Ftc,
+ Fti,
+ Fvt,
+ Fxp,
+ Fxpl,
+ Fzs,
+ G2w,
+ G3,
+ G3w,
+ Gac,
+ Gam,
+ Gbr,
+ Gca,
+ Gdl,
+ Gdoc,
+ Geo,
+ Geojson,
+ Gex,
+ Ggb,
+ Ggt,
+ Ghf,
+ Gif,
+ Gim,
+ Glb,
+ Gltf,
+ Gml,
+ Gmx,
+ Gnumeric,
+ Gph,
+ Gpx,
+ Gqf,
+ Gqs,
+ Gram,
+ Gramps,
+ Gre,
+ Grv,
+ Grxml,
+ Gsf,
+ Gsheet,
+ Gslides,
+ Gtar,
+ Gtm,
+ Gtw,
+ Gv,
+ Gxf,
+ Gxt,
+ Gz,
+ H,
+ H261,
+ H263,
+ H264,
+ Hal,
+ Hbci,
+ Hbs,
+ Hdd,
+ Hdf,
+ Heic,
+ Heics,
+ Heif,
+ Heifs,
+ Hej2,
+ Held,
+ Hh,
+ Hjson,
+ Hlp,
+ Hpgl,
+ Hpid,
+ Hps,
+ Hqx,
+ Hsj2,
+ Htc,
+ Htke,
+ Htm,
+ Html,
+ Hvd,
+ Hvp,
+ Hvs,
+ I2g,
+ Icc,
+ Ice,
+ Icm,
+ Ico,
+ Ics,
+ Ief,
+ Ifb,
+ Ifm,
+ Iges,
+ Igl,
+ Igm,
+ Igs,
+ Igx,
+ Iif,
+ Img,
+ Imp,
+ Ims,
+ Ini,
+ Ink,
+ Inkml,
+ Install,
+ Iota,
+ Ipfix,
+ Ipk,
+ Irm,
+ Irp,
+ Iso,
+ Itp,
+ Its,
+ Ivp,
+ Ivu,
+ Jad,
+ Jade,
+ Jam,
+ Jar,
+ Jardiff,
+ Java,
+ Jhc,
+ Jisp,
+ Jls,
+ Jlt,
+ Jng,
+ Jnlp,
+ Joda,
+ Jp2,
+ Jpe,
+ Jpeg,
+ Jpf,
+ Jpg,
+ Jpg2,
+ Jpgm,
+ Jpgv,
+ Jph,
+ Jpm,
+ Jpx,
+ Javascript,
+ Json,
+ Json5,
+ Jsonld,
+ Jsonml,
+ Jsx,
+ Jxr,
+ Jxra,
+ Jxrs,
+ Jxs,
+ Jxsc,
+ Jxsi,
+ Jxss,
+ Kar,
+ Karbon,
+ Kdbx,
+ Key,
+ Kfo,
+ Kia,
+ Kml,
+ Kmz,
+ Kne,
+ Knp,
+ Kon,
+ Kpr,
+ Kpt,
+ Kpxx,
+ Ksp,
+ Ktr,
+ Ktx,
+ Ktx2,
+ Ktz,
+ Kwd,
+ Kwt,
+ Lasxml,
+ Latex,
+ Lbd,
+ Lbe,
+ Les,
+ Less,
+ Lgr,
+ Lha,
+ Link66,
+ List,
+ List3820,
+ Listafp,
+ Lnk,
+ Log,
+ Lostxml,
+ Lrf,
+ Lrm,
+ Ltf,
+ Lua,
+ Luac,
+ Lvp,
+ Lwp,
+ Lzh,
+ M13,
+ M14,
+ M1v,
+ M21,
+ M2a,
+ M2v,
+ M3a,
+ M3u,
+ M3u8,
+ M4a,
+ M4p,
+ M4s,
+ M4u,
+ M4v,
+ Ma,
+ Mads,
+ Maei,
+ Mag,
+ Maker,
+ Man,
+ Manifest,
+ Map,
+ Mar,
+ Markdown,
+ Mathml,
+ Mb,
+ Mbk,
+ Mbox,
+ Mc1,
+ Mcd,
+ Mcurl,
+ Md,
+ Mdb,
+ Mdi,
+ Mdx,
+ Me,
+ Mesh,
+ Meta4,
+ Metalink,
+ Mets,
+ Mfm,
+ Mft,
+ Mgp,
+ Mgz,
+ Mid,
+ Midi,
+ Mie,
+ Mif,
+ Mime,
+ Mj2,
+ Mjp2,
+ Mjs,
+ Mk3d,
+ Mka,
+ Mkd,
+ Mks,
+ Mkv,
+ Mlp,
+ Mmd,
+ Mmf,
+ Mml,
+ Mmr,
+ Mng,
+ Mny,
+ Mobi,
+ Mods,
+ Mov,
+ Movie,
+ Mp2,
+ Mp21,
+ Mp2a,
+ Mp3,
+ Mp4,
+ Mp4a,
+ Mp4s,
+ Mp4v,
+ Mpc,
+ Mpd,
+ Mpe,
+ Mpeg,
+ Mpg,
+ Mpg4,
+ Mpga,
+ Mpkg,
+ Mpm,
+ Mpn,
+ Mpp,
+ Mpt,
+ Mpy,
+ Mqy,
+ Mrc,
+ Mrcx,
+ Ms,
+ Mscml,
+ Mseed,
+ Mseq,
+ Msf,
+ Msg,
+ Msh,
+ Msi,
+ Msl,
+ Msm,
+ Msp,
+ Msty,
+ Mtl,
+ Mts,
+ Mus,
+ Musd,
+ Musicxml,
+ Mvb,
+ Mvt,
+ Mwf,
+ Mxf,
+ Mxl,
+ Mxmf,
+ Mxml,
+ Mxs,
+ Mxu,
+ N3,
+ Nb,
+ Nbp,
+ Nc,
+ Ncx,
+ Nfo,
+ Ngdat,
+ Nitf,
+ Nlu,
+ Nml,
+ Nnd,
+ Nns,
+ Nnw,
+ Npx,
+ Nq,
+ Nsc,
+ Nsf,
+ Nt,
+ Ntf,
+ Numbers,
+ Nzb,
+ Oa2,
+ Oa3,
+ Oas,
+ Obd,
+ Obgx,
+ Obj,
+ Oda,
+ Odb,
+ Odc,
+ Odf,
+ Odft,
+ Odg,
+ Odi,
+ Odm,
+ Odp,
+ Ods,
+ Odt,
+ Oga,
+ Ogex,
+ Ogg,
+ Ogv,
+ Ogx,
+ Omdoc,
+ Onepkg,
+ Onetmp,
+ Onetoc,
+ Onetoc2,
+ Opf,
+ Opml,
+ Oprc,
+ Opus,
+ Org,
+ Osf,
+ Osfpvg,
+ Osm,
+ Otc,
+ Otf,
+ Otg,
+ Oth,
+ Oti,
+ Otp,
+ Ots,
+ Ott,
+ Ova,
+ Ovf,
+ Owl,
+ Oxps,
+ Oxt,
+ P,
+ P10,
+ P12,
+ P7b,
+ P7c,
+ P7m,
+ P7r,
+ P7s,
+ P8,
+ Pac,
+ Pages,
+ Pas,
+ Paw,
+ Pbd,
+ Pbm,
+ Pcap,
+ Pcf,
+ Pcl,
+ Pclxl,
+ Pct,
+ Pcurl,
+ Pcx,
+ Pdb,
+ Pde,
+ Pdf,
+ Pem,
+ Pfa,
+ Pfb,
+ Pfm,
+ Pfr,
+ Pfx,
+ Pgm,
+ Pgn,
+ Pgp,
+ Php,
+ Pic,
+ Pkg,
+ Pki,
+ Pkipath,
+ Pkpass,
+ Pl,
+ Plb,
+ Plc,
+ Plf,
+ Pls,
+ Pm,
+ Pml,
+ Png,
+ Pnm,
+ Portpkg,
+ Pot,
+ Potm,
+ Potx,
+ Ppam,
+ Ppd,
+ Ppm,
+ Pps,
+ Ppsm,
+ Ppsx,
+ Ppt,
+ Pptm,
+ Pptx,
+ Pqa,
+ Prc,
+ Pre,
+ Prf,
+ Provx,
+ Ps,
+ Psb,
+ Psd,
+ Psf,
+ Pskcxml,
+ Pti,
+ Ptid,
+ Pub,
+ Pvb,
+ Pwn,
+ Pya,
+ Pyv,
+ Qam,
+ Qbo,
+ Qfx,
+ Qps,
+ Qt,
+ Qwd,
+ Qwt,
+ Qxb,
+ Qxd,
+ Qxl,
+ Qxt,
+ Ra,
+ Ram,
+ Raml,
+ Rapd,
+ Rar,
+ Ras,
+ Rdf,
+ Rdz,
+ Relo,
+ Rep,
+ Res,
+ Rgb,
+ Rif,
+ Rip,
+ Ris,
+ Rl,
+ Rlc,
+ Rld,
+ Rm,
+ Rmi,
+ Rmp,
+ Rms,
+ Rmvb,
+ Rnc,
+ Rng,
+ Roa,
+ Roff,
+ Rp9,
+ Rpm,
+ Rpss,
+ Rpst,
+ Rq,
+ Rs,
+ Rsat,
+ Rsd,
+ Rsheet,
+ Rss,
+ Rtf,
+ Rtx,
+ Run,
+ Rusd,
+ S,
+ S3m,
+ Saf,
+ Sass,
+ Sbml,
+ Sc,
+ Scd,
+ Scm,
+ Scq,
+ Scs,
+ Scss,
+ Scurl,
+ Sda,
+ Sdc,
+ Sdd,
+ Sdkd,
+ Sdkm,
+ Sdp,
+ Sdw,
+ Sea,
+ See,
+ Seed,
+ Sema,
+ Semd,
+ Semf,
+ Senmlx,
+ Sensmlx,
+ Ser,
+ Setpay,
+ Setreg,
+ Sfs,
+ Sfv,
+ Sgi,
+ Sgl,
+ Sgm,
+ Sgml,
+ Sh,
+ Shar,
+ Shex,
+ Shf,
+ Shtml,
+ Sid,
+ Sieve,
+ Sig,
+ Sil,
+ Silo,
+ Sis,
+ Sisx,
+ Sit,
+ Sitx,
+ Siv,
+ Skd,
+ Skm,
+ Skp,
+ Skt,
+ Sldm,
+ Sldx,
+ Slim,
+ Slm,
+ Sls,
+ Slt,
+ Sm,
+ Smf,
+ Smi,
+ Smil,
+ Smv,
+ Smzip,
+ Snd,
+ Snf,
+ So,
+ Spc,
+ Spdx,
+ Spf,
+ Spl,
+ Spot,
+ Spp,
+ Spq,
+ Spx,
+ Sql,
+ Src,
+ Srt,
+ Sru,
+ Srx,
+ Ssdl,
+ Sse,
+ Ssf,
+ Ssml,
+ St,
+ Stc,
+ Std,
+ Stf,
+ Sti,
+ Stk,
+ Stl,
+ Stpx,
+ Stpxz,
+ Stpz,
+ Str,
+ Stw,
+ Styl,
+ Stylus,
+ Sub,
+ Sus,
+ Susp,
+ Sv4cpio,
+ Sv4crc,
+ Svc,
+ Svd,
+ Svg,
+ Svgz,
+ Swa,
+ Swf,
+ Swi,
+ Swidtag,
+ Sxc,
+ Sxd,
+ Sxg,
+ Sxi,
+ Sxm,
+ Sxw,
+ T,
+ T3,
+ T38,
+ Taglet,
+ Tao,
+ Tap,
+ Tar,
+ Tcap,
+ Tcl,
+ Td,
+ Teacher,
+ Tei,
+ Tex,
+ Texi,
+ Texinfo,
+ Text,
+ Tfi,
+ Tfm,
+ Tfx,
+ Tga,
+ Thmx,
+ Tif,
+ Tiff,
+ Tk,
+ Tmo,
+ Toml,
+ Torrent,
+ Tpl,
+ Tpt,
+ Tr,
+ Tra,
+ Trig,
+ Trm,
+ Ts,
+ Tsd,
+ Tsv,
+ Ttc,
+ Ttf,
+ Ttl,
+ Ttml,
+ Twd,
+ Twds,
+ Txd,
+ Txf,
+ Txt,
+ U32,
+ U8dsn,
+ U8hdr,
+ U8mdn,
+ U8msg,
+ Ubj,
+ Udeb,
+ Ufd,
+ Ufdl,
+ Ulx,
+ Umj,
+ Unityweb,
+ Uoml,
+ Uri,
+ Uris,
+ Urls,
+ Usdz,
+ Ustar,
+ Utz,
+ Uu,
+ Uva,
+ Uvd,
+ Uvf,
+ Uvg,
+ Uvh,
+ Uvi,
+ Uvm,
+ Uvp,
+ Uvs,
+ Uvt,
+ Uvu,
+ Uvv,
+ Uvva,
+ Uvvd,
+ Uvvf,
+ Uvvg,
+ Uvvh,
+ Uvvi,
+ Uvvm,
+ Uvvp,
+ Uvvs,
+ Uvvt,
+ Uvvu,
+ Uvvv,
+ Uvvx,
+ Uvvz,
+ Uvx,
+ Uvz,
+ Vbox,
+ Vcard,
+ Vcd,
+ Vcf,
+ Vcg,
+ Vcs,
+ Vcx,
+ Vdi,
+ Vds,
+ Vhd,
+ Vis,
+ Viv,
+ Vmdk,
+ Vob,
+ Vor,
+ Vox,
+ Vrml,
+ Vsd,
+ Vsf,
+ Vss,
+ Vst,
+ Vsw,
+ Vtf,
+ Vtt,
+ Vtu,
+ Vxml,
+ W3d,
+ Wad,
+ Wadl,
+ War,
+ Wasm,
+ Wav,
+ Wax,
+ Wbmp,
+ Wbs,
+ Wbxml,
+ Wcm,
+ Wdb,
+ Wdp,
+ Weba,
+ Webapp,
+ Webm,
+ Webp,
+ Wg,
+ Wgt,
+ Wks,
+ Wm,
+ Wma,
+ Wmd,
+ Wmf,
+ Wml,
+ Wmlc,
+ Wmls,
+ Wmlsc,
+ Wmv,
+ Wmx,
+ Wmz,
+ Woff,
+ Woff2,
+ Wpd,
+ Wpl,
+ Wps,
+ Wqd,
+ Wri,
+ Wrl,
+ Wsc,
+ Wsdl,
+ Wspolicy,
+ Wtb,
+ Wvx,
+ X32,
+ X3d,
+ X3db,
+ X3dbz,
+ X3dv,
+ X3dvz,
+ X3dz,
+ Xaml,
+ Xap,
+ Xar,
+ Xav,
+ Xbap,
+ Xbd,
+ Xbm,
+ Xca,
+ Xcs,
+ Xdf,
+ Xdm,
+ Xdp,
+ Xdssc,
+ Xdw,
+ Xel,
+ Xenc,
+ Xer,
+ Xfdf,
+ Xfdl,
+ Xht,
+ Xhtml,
+ Xhvml,
+ Xif,
+ Xla,
+ Xlam,
+ Xlc,
+ Xlf,
+ Xlm,
+ Xls,
+ Xlsb,
+ Xlsm,
+ Xlsx,
+ Xlt,
+ Xltm,
+ Xltx,
+ Xlw,
+ Xm,
+ Xml,
+ Xns,
+ Xo,
+ Xop,
+ Xpi,
+ Xpl,
+ Xpm,
+ Xpr,
+ Xps,
+ Xpw,
+ Xpx,
+ Xsd,
+ Xsl,
+ Xslt,
+ Xsm,
+ Xspf,
+ Xul,
+ Xvm,
+ Xvml,
+ Xwd,
+ Xyz,
+ Xz,
+ Yaml,
+ Yang,
+ Yin,
+ Yml,
+ Ymp,
+ Z1,
+ Z2,
+ Z3,
+ Z4,
+ Z5,
+ Z6,
+ Z7,
+ Z8,
+ Zaz,
+ Zip,
+ Zir,
+ Zirz,
+ Zmm,
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs b/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs
new file mode 100644
index 0000000..5cc5ed9
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/CoreBufferHelpers.cs
@@ -0,0 +1,188 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: CoreBufferHelpers.cs
+*
+* CoreBufferHelpers.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+/*
+ * This class is meant to provide memory helper methods
+ * as a centralized HTTP local memory api.
+ *
+ * Pools and heaps are privatized to help avoid
+ * leaking sensitive HTTP data across other application
+ * allocations and help provide memory optimization.
+ */
+
+
+
+using System;
+using System.IO;
+using System.Buffers;
+using System.Security;
+using System.Threading;
+
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http.Core
+{
+
+ /// <summary>
+ /// Provides memory pools and an internal heap for allocations.
+ /// </summary>
+ internal static class CoreBufferHelpers
+ {
+ private sealed class InitDataBuffer : ISlindingWindowBuffer<byte>
+ {
+ private readonly ArrayPool<byte> pool;
+ private readonly int size;
+
+ private byte[]? buffer;
+
+ public InitDataBuffer(ArrayPool<byte> pool, int size)
+ {
+ this.buffer = pool.Rent(size, true);
+ this.pool = pool;
+ this.size = size;
+ WindowStartPos = 0;
+ WindowEndPos = 0;
+ }
+
+ public int WindowStartPos { get; set; }
+ public int WindowEndPos { get; set; }
+ Memory<byte> ISlindingWindowBuffer<byte>.Buffer => buffer.AsMemory(0, size);
+
+ public void Advance(int count)
+ {
+ WindowEndPos += count;
+ }
+
+ public void AdvanceStart(int count)
+ {
+ WindowStartPos += count;
+ }
+
+ public void Reset()
+ {
+ WindowStartPos = 0;
+ WindowEndPos = 0;
+ }
+
+ //Release the buffer back to the pool
+ void ISlindingWindowBuffer<byte>.Close()
+ {
+ pool.Return(buffer!);
+ buffer = null;
+ }
+ }
+
+ /// <summary>
+ /// An internal HTTP character binary pool for HTTP specific internal buffers
+ /// </summary>
+ public static ArrayPool<byte> HttpBinBufferPool { get; } = ArrayPool<byte>.Create();
+ /// <summary>
+ /// An <see cref="IUnmangedHeap"/> used for internal HTTP buffers
+ /// </summary>
+ public static IUnmangedHeap HttpPrivateHeap => _lazyHeap.Value;
+
+ private static readonly Lazy<IUnmangedHeap> _lazyHeap = new(Memory.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly);
+
+ /// <summary>
+ /// Alloctes an unsafe block of memory from the internal heap, or buffer pool
+ /// </summary>
+ /// <param name="size">The number of elemnts to allocate</param>
+ /// <param name="zero">A value indicating of the block should be zeroed before returning</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="SecurityException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ public static UnsafeMemoryHandle<byte> GetBinBuffer(int size, bool zero)
+ {
+ //Calc buffer size to the nearest page size
+ size = (size / 4096 + 1) * 4096;
+
+ //If rpmalloc lib is loaded, use it
+ if (Memory.IsRpMallocLoaded)
+ {
+ return Memory.Shared.UnsafeAlloc<byte>(size, zero);
+ }
+ else if (size > Memory.MAX_UNSAFE_POOL_SIZE)
+ {
+ return HttpPrivateHeap.UnsafeAlloc<byte>(size, zero);
+ }
+ else
+ {
+ return new(HttpBinBufferPool, size, zero);
+ }
+ }
+
+ public static IMemoryOwner<byte> GetMemory(int size, bool zero)
+ {
+ //Calc buffer size to the nearest page size
+ size = (size / 4096 + 1) * 4096;
+
+ //If rpmalloc lib is loaded, use it
+ if (Memory.IsRpMallocLoaded)
+ {
+ return Memory.Shared.DirectAlloc<byte>(size, zero);
+ }
+ //Avoid locking in heap unless the buffer is too large to alloc array
+ else if (size > Memory.MAX_UNSAFE_POOL_SIZE)
+ {
+ return HttpPrivateHeap.DirectAlloc<byte>(size, zero);
+ }
+ else
+ {
+ //Convert temp buffer to memory owner
+
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ return new VnTempBuffer<byte>(HttpBinBufferPool, size, zero).ToMemoryManager();
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ }
+ }
+
+ /// <summary>
+ /// Gets the remaining data in the reader buffer and prepares a
+ /// sliding window buffer to read data from
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="reader"></param>
+ /// <param name="maxContentLength">Maximum content size to clamp the remaining buffer window to</param>
+ /// <returns></returns>
+ public static ISlindingWindowBuffer<byte>? GetReminaingData<T>(this ref T reader, long maxContentLength) where T: struct, IVnTextReader
+ {
+ //clamp max available to max content length
+ int available = Math.Clamp(reader.Available, 0, (int)maxContentLength);
+ if (available <= 0)
+ {
+ return null;
+ }
+ //Alloc sliding window buffer
+ ISlindingWindowBuffer<byte> buffer = new InitDataBuffer(HttpBinBufferPool, available);
+ //Read remaining data
+ reader.ReadRemaining(buffer.RemainingBuffer.Span);
+ //Advance the buffer to the end of available data
+ buffer.Advance(available);
+ return buffer;
+ }
+
+ }
+}
diff --git a/lib/Net.Http/src/Helpers/HelperTypes.cs b/lib/Net.Http/src/Helpers/HelperTypes.cs
new file mode 100644
index 0000000..a8aeb1f
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/HelperTypes.cs
@@ -0,0 +1,138 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HelperTypes.cs
+*
+* HelperTypes.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// HTTP request method
+ /// </summary>
+ [Flags]
+ public enum HttpMethod
+ {
+ None,
+ GET = 0x01,
+ POST = 0x02,
+ PUT = 0x04,
+ OPTIONS = 0x08,
+ HEAD = 0x10,
+ MERGE = 0x20,
+ COPY = 0x40,
+ DELETE = 0x80,
+ PATCH = 0x100,
+ TRACE = 0x200,
+ MOVE = 0x400,
+ LOCK = 0x800
+ }
+ /// <summary>
+ /// HTTP protocol version
+ /// </summary>
+ [Flags]
+ public enum HttpVersion
+ {
+ None,
+ /// <summary>
+ /// Http Version 1
+ /// </summary>
+ Http1 = 0x01,
+ /// <summary>
+ /// Http Version 1.1
+ /// </summary>
+ Http11 = 0x02,
+ /// <summary>
+ /// Http Version 2.0
+ /// </summary>
+ Http2 = 0x04,
+ /// <summary>
+ /// Http Version 0.9
+ /// </summary>
+ Http09 = 0x08,
+ /// <summary>
+ /// Http Version 3.0
+ /// </summary>
+ Http3 = 0x0A
+ }
+ /// <summary>
+ /// HTTP response entity cache flags
+ /// </summary>
+ [Flags]
+ public enum CacheType
+ {
+ None = 0x00,
+ NoCache = 0x01,
+ Private = 0x02,
+ Public = 0x04,
+ NoStore = 0x08,
+ Revalidate = 0x10
+ }
+
+ /// <summary>
+ /// Specifies an HTTP cookie SameSite type
+ /// </summary>
+ public enum CookieSameSite
+ {
+ /// <summary>
+ /// Cookie samesite property lax mode
+ /// </summary>
+ Lax,
+ /// <summary>
+ /// Cookie samesite property, None mode
+ /// </summary>
+ None,
+ /// <summary>
+ /// Cookie samesite property, Same-Site mode
+ /// </summary>
+ SameSite
+ }
+
+ /// <summary>
+ /// Low level 301 "hard" redirect
+ /// </summary>
+ public class Redirect
+ {
+ public readonly string Url;
+ public readonly Uri RedirectUrl;
+ /// <summary>
+ /// Quickly redirects a url to another url before sessions are established
+ /// </summary>
+ /// <param name="url">Url to redirect on</param>
+ /// <param name="redirecturl">Url to redirect to</param>
+ public Redirect(string url, string redirecturl)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException($"'{nameof(url)}' cannot be null or empty.", nameof(url));
+ }
+
+ if (string.IsNullOrEmpty(redirecturl))
+ {
+ throw new ArgumentException($"'{nameof(redirecturl)}' cannot be null or empty.", nameof(redirecturl));
+ }
+
+ Url = url;
+ RedirectUrl = new(redirecturl);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Helpers/HttpHelpers.cs b/lib/Net.Http/src/Helpers/HttpHelpers.cs
new file mode 100644
index 0000000..0937981
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/HttpHelpers.cs
@@ -0,0 +1,445 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpHelpers.cs
+*
+* HttpHelpers.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+using VNLib.Net.Http.Core;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Provides a set of HTTP helper functions
+ /// </summary>
+ public static partial class HttpHelpers
+ {
+ /// <summary>
+ /// Carrage return + line feed characters used within the VNLib.Net.Http namespace to delimit http messages/lines
+ /// </summary>
+ public const string CRLF = "\r\n";
+
+ public const string WebsocketRFC4122Guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+ public const string EVENT_STREAM_ACCEPT_TYPE = "text/event-stream";
+
+ /// <summary>
+ /// Extended <see cref="HttpRequestHeader"/> for origin header, DO NOT USE IN <see cref="WebHeaderCollection"/>
+ /// </summary>
+ internal const HttpRequestHeader Origin = (HttpRequestHeader)42;
+ /// <summary>
+ /// Extended <see cref="HttpRequestHeader"/> for Content-Disposition, DO NOT USE IN <see cref="WebHeaderCollection"/>
+ /// </summary>
+ internal const HttpRequestHeader ContentDisposition = (HttpRequestHeader)41;
+
+ private static readonly Regex HttpRequestBuilderRegex = new("(?<=[a-z])([A-Z])", RegexOptions.Compiled);
+
+ /*
+ * Provides a hashable lookup table from a method string's hashcode to output
+ * an HttpMethod enum value,
+ */
+
+ private static readonly IReadOnlyDictionary<int, HttpMethod> MethodHashLookup;
+
+ /*
+ * Provides a constant lookup table from an MIME http request header string to a .NET
+ * enum value (with some extra support)
+ */
+
+ private static readonly IReadOnlyDictionary<string, HttpRequestHeader> RequestHeaderLookup = new Dictionary<string, HttpRequestHeader>()
+ {
+ {"CacheControl", HttpRequestHeader.CacheControl },
+ {"Connection", HttpRequestHeader.Connection },
+ {"Date", HttpRequestHeader.Date },
+ {"Keep-Alive", HttpRequestHeader.KeepAlive },
+ {"Pragma", HttpRequestHeader.Pragma },
+ {"Trailer", HttpRequestHeader.Trailer },
+ {"Transfer-Encoding", HttpRequestHeader.TransferEncoding },
+ {"Upgrade", HttpRequestHeader.Upgrade },
+ {"Via", HttpRequestHeader.Via },
+ {"Warning", HttpRequestHeader.Warning },
+ {"Allow", HttpRequestHeader.Allow },
+ {"Content-Length", HttpRequestHeader.ContentLength },
+ {"Content-Type", HttpRequestHeader.ContentType },
+ {"Content-Encoding", HttpRequestHeader.ContentEncoding },
+ {"Content-Language", HttpRequestHeader.ContentLanguage },
+ {"Content-Location", HttpRequestHeader.ContentLocation },
+ {"Content-Md5", HttpRequestHeader.ContentMd5 },
+ {"Content-Range", HttpRequestHeader.ContentRange },
+ {"Expires", HttpRequestHeader.Expires },
+ {"Last-Modified", HttpRequestHeader.LastModified },
+ {"Accept", HttpRequestHeader.Accept },
+ {"Accept-Charset", HttpRequestHeader.AcceptCharset },
+ {"Accept-Encoding", HttpRequestHeader.AcceptEncoding },
+ {"Accept-Language", HttpRequestHeader.AcceptLanguage },
+ {"Authorization", HttpRequestHeader.Authorization },
+ {"Cookie", HttpRequestHeader.Cookie },
+ {"Expect", HttpRequestHeader.Expect },
+ {"From", HttpRequestHeader.From },
+ {"Host", HttpRequestHeader.Host },
+ {"IfMatch", HttpRequestHeader.IfMatch },
+ {"If-Modified-Since", HttpRequestHeader.IfModifiedSince },
+ {"If-None-Match", HttpRequestHeader.IfNoneMatch },
+ {"If-Range", HttpRequestHeader.IfRange },
+ {"If-Unmodified-Since", HttpRequestHeader.IfUnmodifiedSince },
+ {"MaxForwards", HttpRequestHeader.MaxForwards },
+ {"Proxy-Authorization", HttpRequestHeader.ProxyAuthorization },
+ {"Referer", HttpRequestHeader.Referer },
+ {"Range", HttpRequestHeader.Range },
+ {"Te", HttpRequestHeader.Te },
+ {"Translate", HttpRequestHeader.Translate },
+ {"User-Agent", HttpRequestHeader.UserAgent },
+ //Custom request headers
+ { "Content-Disposition", ContentDisposition },
+ { "origin", Origin }
+ };
+
+ /*
+ * Provides a lookup table for request header hashcodes (that are hashed in
+ * the static constructor) to ouput an http request header enum value from
+ * a header string's hashcode (allows for spans to produce an enum value
+ * during request parsing)
+ *
+ */
+ private static readonly IReadOnlyDictionary<int, HttpRequestHeader> RequestHeaderHashLookup;
+
+ /*
+ * Provides a constant lookup table for http version hashcodes to an http
+ * version enum value
+ */
+ private static readonly IReadOnlyDictionary<int, HttpVersion> VersionHashLookup = new Dictionary<int, HttpVersion>()
+ {
+ { string.GetHashCode("HTTP/0.9", StringComparison.OrdinalIgnoreCase), HttpVersion.Http09 },
+ { string.GetHashCode("HTTP/1.0", StringComparison.OrdinalIgnoreCase), HttpVersion.Http1 },
+ { string.GetHashCode("HTTP/1.1", StringComparison.OrdinalIgnoreCase), HttpVersion.Http11 },
+ { string.GetHashCode("HTTP/2.0", StringComparison.OrdinalIgnoreCase), HttpVersion.Http2 }
+ };
+
+
+ //Pre-compiled strings for all status codes for http 1, 1.1, and 2
+ private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_STAUTS_CODES;
+ private static readonly IReadOnlyDictionary<HttpStatusCode, string> V1_1_STATUS_CODES;
+ private static readonly IReadOnlyDictionary<HttpStatusCode, string> V2_STAUTS_CODES;
+
+ static HttpHelpers()
+ {
+ {
+ //Setup status code dict
+ Dictionary<HttpStatusCode, string> v1status = new();
+ Dictionary<HttpStatusCode, string> v11status = new();
+ Dictionary<HttpStatusCode, string> v2status = new();
+ //Get all status codes
+ foreach (HttpStatusCode code in Enum.GetValues<HttpStatusCode>())
+ {
+ //Use a regex to write the status code value as a string
+ v1status[code] = $"HTTP/1.0 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}";
+ v11status[code] = $"HTTP/1.1 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}";
+ v2status[code] = $"HTTP/2.0 {(int)code} {HttpRequestBuilderRegex.Replace(code.ToString(), " $1")}";
+ }
+ //Store as readonly
+ V1_STAUTS_CODES = v1status;
+ V1_1_STATUS_CODES = v11status;
+ V2_STAUTS_CODES = v2status;
+ }
+ {
+ /*
+ * Http methods are hashed at runtime using the HttpMethod enum
+ * values, purley for compatability and automation
+ */
+ Dictionary<int, HttpMethod> methods = new();
+ //Add all HTTP methods
+ foreach (HttpMethod method in Enum.GetValues<HttpMethod>())
+ {
+ //Exclude the not supported method
+ if (method == HttpMethod.None)
+ {
+ continue;
+ }
+ //Store method string's hashcode for faster lookups
+ methods[string.GetHashCode(method.ToString(), StringComparison.OrdinalIgnoreCase)] = method;
+ }
+ MethodHashLookup = methods;
+ }
+ {
+ /*
+ * Pre-compute common headers
+ */
+ Dictionary<int, HttpRequestHeader> requestHeaderHashes = new();
+ //Add all HTTP methods
+ foreach (string headerValue in RequestHeaderLookup.Keys)
+ {
+ //Compute the hashcode for the header value
+ int hashCode = string.GetHashCode(headerValue, StringComparison.OrdinalIgnoreCase);
+ //Store the http header enum value with the hash-code of the string of said header
+ requestHeaderHashes[hashCode] = RequestHeaderLookup[headerValue];
+ }
+ RequestHeaderHashLookup = requestHeaderHashes;
+ }
+ }
+
+
+ /// <summary>
+ /// Returns an http formatted content type string of a specified content type
+ /// </summary>
+ /// <param name="type">Contenty type</param>
+ /// <returns>Http acceptable string representing a content type</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
+ public static string GetContentTypeString(ContentType type) => CtToMime[type];
+ /// <summary>
+ /// Returns the <see cref="ContentType"/> enum value from the MIME string
+ /// </summary>
+ /// <param name="type">Content type from request</param>
+ /// <returns><see cref="ContentType"/> of request, <see cref="ContentType.NonSupported"/> if unknown</returns>
+ public static ContentType GetContentType(string type) => MimeToCt.GetValueOrDefault(type, ContentType.NonSupported);
+ //Cache control string using mdn reference
+ //https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
+ /// <summary>
+ /// Builds a Cache-Control MIME content header from the specified flags
+ /// </summary>
+ /// <param name="type">The cache type/mode</param>
+ /// <param name="maxAge">The max-age (time in seconds) argument</param>
+ /// <param name="immutable">Sets the immutable argument</param>
+ /// <returns>The string representation of the Cache-Control header</returns>
+ public static string GetCacheString(CacheType type, int maxAge = 0, bool immutable = false)
+ {
+ //Rent a buffer to write header to
+ Span<char> buffer = stackalloc char[128];
+ //Get buffer writer for cache header
+ ForwardOnlyWriter<char> sb = new(buffer);
+ if ((type & CacheType.NoCache) > 0)
+ {
+ sb.Append("no-cache, ");
+ }
+ if ((type & CacheType.NoStore) > 0)
+ {
+ sb.Append("no-store, ");
+ }
+ if ((type & CacheType.Public) > 0)
+ {
+ sb.Append("public, ");
+ }
+ if ((type & CacheType.Private) > 0)
+ {
+ sb.Append("private, ");
+ }
+ if ((type & CacheType.Revalidate) > 0)
+ {
+ sb.Append("must-revalidate, ");
+ }
+ if (immutable)
+ {
+ sb.Append("immutable, ");
+ }
+ sb.Append("max-age=");
+ sb.Append(maxAge);
+ return sb.ToString();
+ }
+ /// <summary>
+ /// Builds a Cache-Control MIME content header from the specified flags
+ /// </summary>
+ /// <param name="type">The cache type/mode</param>
+ /// <param name="maxAge">The max-age argument</param>
+ /// <param name="immutable">Sets the immutable argument</param>
+ /// <returns>The string representation of the Cache-Control header</returns>
+ public static string GetCacheString(CacheType type, TimeSpan maxAge, bool immutable = false) => GetCacheString(type, (int)maxAge.TotalSeconds, immutable);
+ /// <summary>
+ /// Returns an enum value of an httpmethod of an http request method string
+ /// </summary>
+ /// <param name="smethod">Http acceptable method type string</param>
+ /// <returns>Request method, <see cref="HttpMethod.None"/> if method is malformatted or unsupported</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static HttpMethod GetRequestMethod(ReadOnlySpan<char> smethod)
+ {
+ //Get the hashcode for the method "string"
+ int hashCode = string.GetHashCode(smethod, StringComparison.OrdinalIgnoreCase);
+ //run the lookup and return not supported if the method was not found
+ return MethodHashLookup.GetValueOrDefault(hashCode, HttpMethod.None);
+ }
+ /// <summary>
+ /// Compares the first 3 bytes of IPV4 ip address or the first 6 bytes of a IPV6. Can be used to determine if the address is local to another address
+ /// </summary>
+ /// <param name="first">Address to be compared</param>
+ /// <param name="other">Address to be comared to first address</param>
+ /// <returns>True if first 2 bytes of each address match (Big Endian)</returns>
+ public static bool IsLocalSubnet(this IPAddress first, IPAddress other)
+ {
+ if(first.AddressFamily != other.AddressFamily)
+ {
+ return false;
+ }
+ switch (first.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ {
+ //Alloc buffers 4 bytes for IPV4
+ Span<byte> firstBytes = stackalloc byte[4];
+ Span<byte> otherBytes = stackalloc byte[4];
+ //Write address's to the buffers
+ if (first.TryWriteBytes(firstBytes, out _) && other.TryWriteBytes(otherBytes, out _))
+ {
+ //Compare the first 3 bytes of the first address to the second address
+ return firstBytes.StartsWith(otherBytes[..3]);
+ }
+ }
+ break;
+ case AddressFamily.InterNetworkV6:
+ {
+ //Alloc buffers 8 bytes for IPV6
+ Span<byte> firstBytes = stackalloc byte[8];
+ Span<byte> otherBytes = stackalloc byte[8];
+ //Write address's to the buffers
+ if (first.TryWriteBytes(firstBytes, out _) && other.TryWriteBytes(otherBytes, out _))
+ {
+ //Compare the first 6 bytes of the first address to the second address
+ return firstBytes.StartsWith(otherBytes[..6]);
+ }
+ }
+ break;
+ }
+ return false;
+ }
+ /// <summary>
+ /// Selects a <see cref="ContentType"/> for a given file extension
+ /// </summary>
+ /// <param name="path">Path (including extension) of a file</param>
+ /// <returns><see cref="ContentType"/> of file. Returns <see cref="ContentType.Binary"/> if extension is unknown</returns>
+ public static ContentType GetContentTypeFromFile(ReadOnlySpan<char> path)
+ {
+ //Get the file's extension
+ ReadOnlySpan<char> extention = Path.GetExtension(path);
+ //Trim leading .
+ extention = extention.Trim('.');
+ //If the extension is defined, perform a lookup, otherwise return the default
+ return ExtensionToCt.GetValueOrDefault(extention.ToString(), ContentType.Binary);
+ }
+ /// <summary>
+ /// Selects a runtime compiled <see cref="string"/> matching the given <see cref="HttpStatusCode"/> and <see cref="HttpVersion"/>
+ /// </summary>
+ /// <param name="version">Version of the response string</param>
+ /// <param name="code">Status code of the response</param>
+ /// <returns>The HTTP response status line matching the code and version</returns>
+ public static string GetResponseString(HttpVersion version, HttpStatusCode code)
+ {
+ return version switch
+ {
+ HttpVersion.Http1 => V1_STAUTS_CODES[code],
+ HttpVersion.Http2 => V2_STAUTS_CODES[code],
+ _ => V1_1_STATUS_CODES[code],
+ };
+ }
+
+ /// <summary>
+ /// Parses the mime Content-Type header value into its sub-components
+ /// </summary>
+ /// <param name="header">The Content-Type header value field</param>
+ /// <param name="ContentType">The mime content type field</param>
+ /// <param name="Charset">The mime charset</param>
+ /// <param name="Boundry">The multi-part form boundry parameter</param>
+ /// <returns>True if parsing the content type succeded, false otherwise</returns>
+ public static bool TryParseContentType(string header, out string? ContentType, out string? Charset, out string? Boundry)
+ {
+ try
+ {
+ //Parse content type
+ System.Net.Mime.ContentType ctype = new(header);
+ Boundry = ctype.Boundary;
+ Charset = ctype.CharSet;
+ ContentType = ctype.MediaType;
+ return true;
+ }
+ catch
+//Disable warning for not using the exception, intended behavior
+#pragma warning disable ERP022 // Unobserved exception in a generic exception handler.
+ {
+ ContentType = Charset = Boundry = null;
+ //Invalid content type header value
+ }
+#pragma warning restore ERP022 // Unobserved exception in a generic exception handler.
+ return false;
+ }
+
+ /// <summary>
+ /// Parses a standard HTTP Content disposition header into its sub-components, type, name, filename (optional)
+ /// </summary>
+ /// <param name="header">The buffer containing the Content-Disposition header value only</param>
+ /// <param name="type">The mime form type</param>
+ /// <param name="name">The mime name argument</param>
+ /// <param name="fileName">The mime filename</param>
+ public static void ParseDisposition(ReadOnlySpan<char> header, out string? type, out string? name, out string? fileName)
+ {
+ //First parameter should be the type argument
+ type = header.SliceBeforeParam(';').Trim().ToString();
+ //Set defaults for name and filename
+ name = fileName = null;
+ //get the name parameter
+ ReadOnlySpan<char> nameSpan = header.SliceAfterParam("name=\"");
+ if (!nameSpan.IsEmpty)
+ {
+ //Capture the name parameter value and trim it up
+ name = nameSpan.SliceBeforeParam('"').Trim().ToString();
+ }
+ //Check for the filename parameter
+ ReadOnlySpan<char> fileNameSpan = header.SliceAfterParam("filename=\"");
+ if (!fileNameSpan.IsEmpty)
+ {
+ //Capture the name parameter value and trim it up
+ fileName = fileNameSpan.SliceBeforeParam('"').Trim().ToString();
+ }
+ }
+
+ /// <summary>
+ /// Performs a lookup of the specified header name to get the <see cref="HttpRequestHeader"/> enum value
+ /// </summary>
+ /// <param name="requestHeaderName">The value of the HTTP request header to compute</param>
+ /// <returns>The <see cref="HttpRequestHeader"/> enum value of the header, or 255 if not found</returns>
+ internal static HttpRequestHeader GetRequestHeaderEnumFromValue(ReadOnlySpan<char> requestHeaderName)
+ {
+ //Compute the hashcode from the header name
+ int hashcode = string.GetHashCode(requestHeaderName, StringComparison.OrdinalIgnoreCase);
+ //perform lookup
+ return RequestHeaderHashLookup.GetValueOrDefault(hashcode, (HttpRequestHeader)255);
+ }
+
+ /// <summary>
+ /// Gets the <see cref="HttpVersion"/> enum value from the version string
+ /// </summary>
+ /// <param name="httpVersion">The http header version string</param>
+ /// <returns>The <see cref="HttpVersion"/> enum value, or
+ /// <see cref="HttpVersion.None"/> if the version could not be
+ /// determined
+ /// </returns>
+ public static HttpVersion ParseHttpVersion(ReadOnlySpan<char> httpVersion)
+ {
+ //Get the hashcode for the http version "string"
+ int hashCode = string.GetHashCode(httpVersion.Trim(), StringComparison.OrdinalIgnoreCase);
+ //return the version that matches the hashcode, or return unsupported of not found
+ return VersionHashLookup.GetValueOrDefault(hashCode, HttpVersion.None);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/Helpers/MimeLookups.cs b/lib/Net.Http/src/Helpers/MimeLookups.cs
new file mode 100644
index 0000000..03bc59d
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/MimeLookups.cs
@@ -0,0 +1,3237 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: MimeLookups.cs
+*
+* MimeLookups.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ public static partial class HttpHelpers
+ {
+ //Content type lookup dict
+ private static readonly IReadOnlyDictionary<ContentType, string> CtToMime = new Dictionary<ContentType, string>()
+ {
+ { ContentType.NonSupported, "application/octet-stream" },
+ { ContentType.UrlEncoded, "application/x-www-form-urlencoded" },
+ { ContentType.MultiPart, "multipart/form-data" },
+ { ContentType.Aab, "application/x-authorware-bin" },
+ { ContentType.Aac, "audio/x-aac" },
+ { ContentType.Aam, "application/x-authorware-map" },
+ { ContentType.Aas, "application/x-authorware-seg" },
+ { ContentType.Abw, "application/x-abiword" },
+ { ContentType.Ac, "application/pkix-attr-cert" },
+ { ContentType.Acc, "application/vnd.americandynamics.acc" },
+ { ContentType.Ace, "application/x-ace-compressed" },
+ { ContentType.Acu, "application/vnd.acucobol" },
+ { ContentType.Acutc, "application/vnd.acucorp" },
+ { ContentType.Adp, "audio/adpcm" },
+ { ContentType.Aep, "application/vnd.audiograph" },
+ { ContentType.Afm, "application/x-font-type1" },
+ { ContentType.Afp, "application/vnd.ibm.modcap" },
+ { ContentType.Ahead, "application/vnd.ahead.space" },
+ { ContentType.Ai, "application/postscript" },
+ { ContentType.Aif, "audio/x-aiff" },
+ { ContentType.Aifc, "audio/x-aiff" },
+ { ContentType.Aiff, "audio/x-aiff" },
+ { ContentType.Air, "application/vnd.adobe.air-application-installer-package+zip" },
+ { ContentType.Ait, "application/vnd.dvb.ait" },
+ { ContentType.Ami, "application/vnd.amiga.ami" },
+ { ContentType.Amr, "audio/amr" },
+ { ContentType.Apk, "application/vnd.android.package-archive" },
+ { ContentType.Apng, "image/apng" },
+ { ContentType.Appcache, "text/cache-manifest" },
+ { ContentType.Apr, "application/vnd.lotus-approach" },
+ { ContentType.Arc, "application/x-freearc" },
+ { ContentType.Arj, "application/x-arj" },
+ { ContentType.Asc, "application/pgp-signature" },
+ { ContentType.Asf, "video/x-ms-asf" },
+ { ContentType.Asm, "text/x-asm" },
+ { ContentType.Aso, "application/vnd.accpac.simply.aso" },
+ { ContentType.Asx, "video/x-ms-asf" },
+ { ContentType.Atc, "application/vnd.acucorp" },
+ { ContentType.Atom, "application/atom+xml" },
+ { ContentType.Atomcat, "application/atomcat+xml" },
+ { ContentType.Atomsvc, "application/atomsvc+xml" },
+ { ContentType.Atx, "application/vnd.antix.game-component" },
+ { ContentType.Au, "audio/basic" },
+ { ContentType.Avi, "video/x-msvideo" },
+ { ContentType.Avif, "image/avif" },
+ { ContentType.Aw, "application/applixware" },
+ { ContentType.Azf, "application/vnd.airzip.filesecure.azf" },
+ { ContentType.Azs, "application/vnd.airzip.filesecure.azs" },
+ { ContentType.Azv, "image/vnd.airzip.accelerator.azv" },
+ { ContentType.Azw, "application/vnd.amazon.ebook" },
+ { ContentType.B16, "image/vnd.pco.b16" },
+ { ContentType.Bat, "application/x-msdownload" },
+ { ContentType.Bcpio, "application/x-bcpio" },
+ { ContentType.Bdf, "application/x-font-bdf" },
+ { ContentType.Bdm, "application/vnd.syncml.dm+wbxml" },
+ { ContentType.Bdoc, "application/bdoc" },
+ { ContentType.Bed, "application/vnd.realvnc.bed" },
+ { ContentType.Bh2, "application/vnd.fujitsu.oasysprs" },
+ { ContentType.Binary, "application/octet-stream" },
+ { ContentType.Blb, "application/x-blorb" },
+ { ContentType.Blorb, "application/x-blorb" },
+ { ContentType.Bmi, "application/vnd.bmi" },
+ { ContentType.Bmml, "application/vnd.balsamiq.bmml+xml" },
+ { ContentType.Bmp, "image/bmp" },
+ { ContentType.Book, "application/vnd.framemaker" },
+ { ContentType.Box, "application/vnd.previewsystems.box" },
+ { ContentType.Boz, "application/x-bzip2" },
+ { ContentType.Bpk, "application/octet-stream" },
+ { ContentType.Bsp, "model/vnd.valve.source.compiled-map" },
+ { ContentType.Btif, "image/prs.btif" },
+ { ContentType.Buffer, "application/octet-stream" },
+ { ContentType.Bz, "application/x-bzip" },
+ { ContentType.Bz2, "application/x-bzip2" },
+ { ContentType.C, "text/x-c" },
+ { ContentType.C11amc, "application/vnd.cluetrust.cartomobile-config" },
+ { ContentType.C11amz, "application/vnd.cluetrust.cartomobile-config-pkg" },
+ { ContentType.C4d, "application/vnd.clonk.c4group" },
+ { ContentType.C4f, "application/vnd.clonk.c4group" },
+ { ContentType.C4g, "application/vnd.clonk.c4group" },
+ { ContentType.C4p, "application/vnd.clonk.c4group" },
+ { ContentType.C4u, "application/vnd.clonk.c4group" },
+ { ContentType.Cab, "application/vnd.ms-cab-compressed" },
+ { ContentType.Caf, "audio/x-caf" },
+ { ContentType.Cap, "application/vnd.tcpdump.pcap" },
+ { ContentType.Car, "application/vnd.curl.car" },
+ { ContentType.Cat, "application/vnd.ms-pki.seccat" },
+ { ContentType.Cb7, "application/x-cbr" },
+ { ContentType.Cba, "application/x-cbr" },
+ { ContentType.Cbr, "application/x-cbr" },
+ { ContentType.Cbt, "application/x-cbr" },
+ { ContentType.Cbz, "application/x-cbr" },
+ { ContentType.Cc, "text/x-c" },
+ { ContentType.Cco, "application/x-cocoa" },
+ { ContentType.Cct, "application/x-director" },
+ { ContentType.Ccxml, "application/ccxml+xml" },
+ { ContentType.Cdbcmsg, "application/vnd.contact.cmsg" },
+ { ContentType.Cdf, "application/x-netcdf" },
+ { ContentType.Cdfx, "application/cdfx+xml" },
+ { ContentType.Cdkey, "application/vnd.mediastation.cdkey" },
+ { ContentType.Cdmia, "application/cdmi-capability" },
+ { ContentType.Cdmic, "application/cdmi-container" },
+ { ContentType.Cdmid, "application/cdmi-domain" },
+ { ContentType.Cdmio, "application/cdmi-object" },
+ { ContentType.Cdmiq, "application/cdmi-queue" },
+ { ContentType.Cdx, "chemical/x-cdx" },
+ { ContentType.Cdxml, "application/vnd.chemdraw+xml" },
+ { ContentType.Cdy, "application/vnd.cinderella" },
+ { ContentType.Cer, "application/pkix-cert" },
+ { ContentType.Cfs, "application/x-cfs-compressed" },
+ { ContentType.Cgm, "image/cgm" },
+ { ContentType.Chat, "application/x-chat" },
+ { ContentType.Chm, "application/vnd.ms-htmlhelp" },
+ { ContentType.Chrt, "application/vnd.kde.kchart" },
+ { ContentType.Cif, "chemical/x-cif" },
+ { ContentType.Cii, "application/vnd.anser-web-certificate-issue-initiation" },
+ { ContentType.Cil, "application/vnd.ms-artgalry" },
+ { ContentType.Cjs, "application/node" },
+ { ContentType.Cla, "application/vnd.claymore" },
+ { ContentType.Clkk, "application/vnd.crick.clicker.keyboard" },
+ { ContentType.Clkp, "application/vnd.crick.clicker.palette" },
+ { ContentType.Clkt, "application/vnd.crick.clicker.template" },
+ { ContentType.Clkw, "application/vnd.crick.clicker.wordbank" },
+ { ContentType.Clkx, "application/vnd.crick.clicker" },
+ { ContentType.Clp, "application/x-msclip" },
+ { ContentType.Cmc, "application/vnd.cosmocaller" },
+ { ContentType.Cmdf, "chemical/x-cmdf" },
+ { ContentType.Cml, "chemical/x-cml" },
+ { ContentType.Cmp, "application/vnd.yellowriver-custom-menu" },
+ { ContentType.Cmx, "image/x-cmx" },
+ { ContentType.Cod, "application/vnd.rim.cod" },
+ { ContentType.Coffee, "text/coffeescript" },
+ { ContentType.Com, "application/x-msdownload" },
+ { ContentType.Conf, "text/plain" },
+ { ContentType.Cpio, "application/x-cpio" },
+ { ContentType.Cpp, "text/x-c" },
+ { ContentType.Cpt, "application/mac-compactpro" },
+ { ContentType.Crd, "application/x-mscardfile" },
+ { ContentType.Crl, "application/pkix-crl" },
+ { ContentType.Crt, "application/x-x509-ca-cert" },
+ { ContentType.Crx, "application/x-chrome-extension" },
+ { ContentType.Csh, "application/x-csh" },
+ { ContentType.Csl, "application/vnd.citationstyles.style+xml" },
+ { ContentType.Csml, "chemical/x-csml" },
+ { ContentType.Csp, "application/vnd.commonspace" },
+ { ContentType.Css, "text/css" },
+ { ContentType.Cst, "application/x-director" },
+ { ContentType.Csv, "text/csv" },
+ { ContentType.Cu, "application/cu-seeme" },
+ { ContentType.Curl, "text/vnd.curl" },
+ { ContentType.Cww, "application/prs.cww" },
+ { ContentType.Cxt, "application/x-director" },
+ { ContentType.Cxx, "text/x-c" },
+ { ContentType.Dae, "model/vnd.collada+xml" },
+ { ContentType.Daf, "application/vnd.mobius.daf" },
+ { ContentType.Dart, "application/vnd.dart" },
+ { ContentType.Dataless, "application/vnd.fdsn.seed" },
+ { ContentType.Davmount, "application/davmount+xml" },
+ { ContentType.Dbf, "application/vnd.dbf" },
+ { ContentType.Dbk, "application/docbook+xml" },
+ { ContentType.Dcr, "application/x-director" },
+ { ContentType.Dcurl, "text/vnd.curl.dcurl" },
+ { ContentType.Dd2, "application/vnd.oma.dd2+xml" },
+ { ContentType.Ddd, "application/vnd.fujixerox.ddd" },
+ { ContentType.Ddf, "application/vnd.syncml.dmddf+xml" },
+ { ContentType.Dds, "image/vnd.ms-dds" },
+ { ContentType.Deb, "application/octet-stream" },
+ { ContentType.Def, "text/plain" },
+ { ContentType.Deploy, "application/octet-stream" },
+ { ContentType.Der, "application/x-x509-ca-cert" },
+ { ContentType.Dfac, "application/vnd.dreamfactory" },
+ { ContentType.Dgc, "application/x-dgc-compressed" },
+ { ContentType.Dic, "text/x-c" },
+ { ContentType.Dir, "application/x-director" },
+ { ContentType.Dis, "application/vnd.mobius.dis" },
+ { ContentType.Dist, "application/octet-stream" },
+ { ContentType.Distz, "application/octet-stream" },
+ { ContentType.Djv, "image/vnd.djvu" },
+ { ContentType.Djvu, "image/vnd.djvu" },
+ { ContentType.Dll, "application/octet-stream" },
+ { ContentType.Dmg, "application/octet-stream" },
+ { ContentType.Dmp, "application/vnd.tcpdump.pcap" },
+ { ContentType.Dms, "application/octet-stream" },
+ { ContentType.Dna, "application/vnd.dna" },
+ { ContentType.Doc, "application/msword" },
+ { ContentType.Docm, "application/vnd.ms-word.document.macroenabled.12" },
+ { ContentType.Docx, "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
+ { ContentType.Dot, "application/msword" },
+ { ContentType.Dotm, "application/vnd.ms-word.template.macroenabled.12" },
+ { ContentType.Dotx, "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
+ { ContentType.Dp, "application/vnd.osgi.dp" },
+ { ContentType.Dpg, "application/vnd.dpgraph" },
+ { ContentType.Dra, "audio/vnd.dra" },
+ { ContentType.Drle, "image/dicom-rle" },
+ { ContentType.Dsc, "text/prs.lines.tag" },
+ { ContentType.Dssc, "application/dssc+der" },
+ { ContentType.Dtb, "application/x-dtbook+xml" },
+ { ContentType.Dtd, "application/xml-dtd" },
+ { ContentType.Dts, "audio/vnd.dts" },
+ { ContentType.Dtshd, "audio/vnd.dts.hd" },
+ { ContentType.Dump, "application/octet-stream" },
+ { ContentType.Dvb, "video/vnd.dvb.file" },
+ { ContentType.Dvi, "application/x-dvi" },
+ { ContentType.Dwd, "application/atsc-dwd+xml" },
+ { ContentType.Dwf, "model/vnd.dwf" },
+ { ContentType.Dwg, "image/vnd.dwg" },
+ { ContentType.Dxf, "image/vnd.dxf" },
+ { ContentType.Dxp, "application/vnd.spotfire.dxp" },
+ { ContentType.Dxr, "application/x-director" },
+ { ContentType.Ear, "application/java-archive" },
+ { ContentType.Ecma, "application/ecmascript" },
+ { ContentType.Edm, "application/vnd.novadigm.edm" },
+ { ContentType.Edx, "application/vnd.novadigm.edx" },
+ { ContentType.Efif, "application/vnd.picsel" },
+ { ContentType.Ei6, "application/vnd.pg.osasli" },
+ { ContentType.Elc, "application/octet-stream" },
+ { ContentType.Emf, "application/x-msmetafile" },
+ { ContentType.Eml, "message/rfc822" },
+ { ContentType.Emma, "application/emma+xml" },
+ { ContentType.Emz, "application/x-msmetafile" },
+ { ContentType.Eol, "audio/vnd.digital-winds" },
+ { ContentType.Eot, "application/vnd.ms-fontobject" },
+ { ContentType.Eps, "application/postscript" },
+ { ContentType.Epub, "application/epub+zip" },
+ { ContentType.Es, "application/ecmascript" },
+ { ContentType.Es3, "application/vnd.eszigno3+xml" },
+ { ContentType.Esa, "application/vnd.osgi.subsystem" },
+ { ContentType.Esf, "application/vnd.epson.esf" },
+ { ContentType.Et3, "application/vnd.eszigno3+xml" },
+ { ContentType.Etx, "text/x-setext" },
+ { ContentType.Eva, "application/x-eva" },
+ { ContentType.Evy, "application/x-envoy" },
+ { ContentType.Exe, "application/octet-stream" },
+ { ContentType.Exi, "application/exi" },
+ { ContentType.Exp, "application/express" },
+ { ContentType.Exr, "image/aces" },
+ { ContentType.Ext, "application/vnd.novadigm.ext" },
+ { ContentType.Ez, "application/andrew-inset" },
+ { ContentType.Ez2, "application/vnd.ezpix-album" },
+ { ContentType.Ez3, "application/vnd.ezpix-package" },
+ { ContentType.F, "text/x-fortran" },
+ { ContentType.F4v, "video/x-f4v" },
+ { ContentType.Fortran, "text/x-fortran" },
+ { ContentType.F90, "text/x-fortran" },
+ { ContentType.Fbs, "image/vnd.fastbidsheet" },
+ { ContentType.Fcdt, "application/vnd.adobe.formscentral.fcdt" },
+ { ContentType.Fcs, "application/vnd.isac.fcs" },
+ { ContentType.Fdf, "application/vnd.fdf" },
+ { ContentType.Fdt, "application/fdt+xml" },
+ { ContentType.Fg5, "application/vnd.fujitsu.oasysgp" },
+ { ContentType.Fgd, "application/x-director" },
+ { ContentType.Fh, "image/x-freehand" },
+ { ContentType.Fh4, "image/x-freehand" },
+ { ContentType.Fh5, "image/x-freehand" },
+ { ContentType.Fh7, "image/x-freehand" },
+ { ContentType.Fhc, "image/x-freehand" },
+ { ContentType.Fig, "application/x-xfig" },
+ { ContentType.Fits, "image/fits" },
+ { ContentType.Flac, "audio/x-flac" },
+ { ContentType.Fli, "video/x-fli" },
+ { ContentType.Flo, "application/vnd.micrografx.flo" },
+ { ContentType.Flv, "video/x-flv" },
+ { ContentType.Flw, "application/vnd.kde.kivio" },
+ { ContentType.Flx, "text/vnd.fmi.flexstor" },
+ { ContentType.Fly, "text/vnd.fly" },
+ { ContentType.Fm, "application/vnd.framemaker" },
+ { ContentType.Fnc, "application/vnd.frogans.fnc" },
+ { ContentType.Fo, "application/vnd.software602.filler.form+xml" },
+ { ContentType.For, "text/x-fortran" },
+ { ContentType.Fpx, "image/vnd.fpx" },
+ { ContentType.Frame, "application/vnd.framemaker" },
+ { ContentType.Fsc, "application/vnd.fsc.weblaunch" },
+ { ContentType.Fst, "image/vnd.fst" },
+ { ContentType.Ftc, "application/vnd.fluxtime.clip" },
+ { ContentType.Fti, "application/vnd.anser-web-funds-transfer-initiation" },
+ { ContentType.Fvt, "video/vnd.fvt" },
+ { ContentType.Fxp, "application/vnd.adobe.fxp" },
+ { ContentType.Fxpl, "application/vnd.adobe.fxp" },
+ { ContentType.Fzs, "application/vnd.fuzzysheet" },
+ { ContentType.G2w, "application/vnd.geoplan" },
+ { ContentType.G3, "image/g3fax" },
+ { ContentType.G3w, "application/vnd.geospace" },
+ { ContentType.Gac, "application/vnd.groove-account" },
+ { ContentType.Gam, "application/x-tads" },
+ { ContentType.Gbr, "application/rpki-ghostbusters" },
+ { ContentType.Gca, "application/x-gca-compressed" },
+ { ContentType.Gdl, "model/vnd.gdl" },
+ { ContentType.Gdoc, "application/vnd.google-apps.document" },
+ { ContentType.Geo, "application/vnd.dynageo" },
+ { ContentType.Geojson, "application/geo+json" },
+ { ContentType.Gex, "application/vnd.geometry-explorer" },
+ { ContentType.Ggb, "application/vnd.geogebra.file" },
+ { ContentType.Ggt, "application/vnd.geogebra.tool" },
+ { ContentType.Ghf, "application/vnd.groove-help" },
+ { ContentType.Gif, "image/gif" },
+ { ContentType.Gim, "application/vnd.groove-identity-message" },
+ { ContentType.Glb, "model/gltf-binary" },
+ { ContentType.Gltf, "model/gltf+json" },
+ { ContentType.Gml, "application/gml+xml" },
+ { ContentType.Gmx, "application/vnd.gmx" },
+ { ContentType.Gnumeric, "application/x-gnumeric" },
+ { ContentType.Gph, "application/vnd.flographit" },
+ { ContentType.Gpx, "application/gpx+xml" },
+ { ContentType.Gqf, "application/vnd.grafeq" },
+ { ContentType.Gqs, "application/vnd.grafeq" },
+ { ContentType.Gram, "application/srgs" },
+ { ContentType.Gramps, "application/x-gramps-xml" },
+ { ContentType.Gre, "application/vnd.geometry-explorer" },
+ { ContentType.Grv, "application/vnd.groove-injector" },
+ { ContentType.Grxml, "application/srgs+xml" },
+ { ContentType.Gsf, "application/x-font-ghostscript" },
+ { ContentType.Gsheet, "application/vnd.google-apps.spreadsheet" },
+ { ContentType.Gslides, "application/vnd.google-apps.presentation" },
+ { ContentType.Gtar, "application/x-gtar" },
+ { ContentType.Gtm, "application/vnd.groove-tool-message" },
+ { ContentType.Gtw, "model/vnd.gtw" },
+ { ContentType.Gv, "text/vnd.graphviz" },
+ { ContentType.Gxf, "application/gxf" },
+ { ContentType.Gxt, "application/vnd.geonext" },
+ { ContentType.Gz, "application/gzip" },
+ { ContentType.H, "text/x-c" },
+ { ContentType.H261, "video/h261" },
+ { ContentType.H263, "video/h263" },
+ { ContentType.H264, "video/h264" },
+ { ContentType.Hal, "application/vnd.hal+xml" },
+ { ContentType.Hbci, "application/vnd.hbci" },
+ { ContentType.Hbs, "text/x-handlebars-template" },
+ { ContentType.Hdd, "application/x-virtualbox-hdd" },
+ { ContentType.Hdf, "application/x-hdf" },
+ { ContentType.Heic, "image/heic" },
+ { ContentType.Heics, "image/heic-sequence" },
+ { ContentType.Heif, "image/heif" },
+ { ContentType.Heifs, "image/heif-sequence" },
+ { ContentType.Hej2, "image/hej2k" },
+ { ContentType.Held, "application/atsc-held+xml" },
+ { ContentType.Hh, "text/x-c" },
+ { ContentType.Hjson, "application/hjson" },
+ { ContentType.Hlp, "application/winhlp" },
+ { ContentType.Hpgl, "application/vnd.hp-hpgl" },
+ { ContentType.Hpid, "application/vnd.hp-hpid" },
+ { ContentType.Hps, "application/vnd.hp-hps" },
+ { ContentType.Hqx, "application/mac-binhex40" },
+ { ContentType.Hsj2, "image/hsj2" },
+ { ContentType.Htc, "text/x-component" },
+ { ContentType.Htke, "application/vnd.kenameaapp" },
+ { ContentType.Htm, "text/html" },
+ { ContentType.Html, "text/html" },
+ { ContentType.Hvd, "application/vnd.yamaha.hv-dic" },
+ { ContentType.Hvp, "application/vnd.yamaha.hv-voice" },
+ { ContentType.Hvs, "application/vnd.yamaha.hv-script" },
+ { ContentType.I2g, "application/vnd.intergeo" },
+ { ContentType.Icc, "application/vnd.iccprofile" },
+ { ContentType.Ice, "x-conference/x-cooltalk" },
+ { ContentType.Icm, "application/vnd.iccprofile" },
+ { ContentType.Ico, "image/vnd.microsoft.icon" },
+ { ContentType.Ics, "text/calendar" },
+ { ContentType.Ief, "image/ief" },
+ { ContentType.Ifb, "text/calendar" },
+ { ContentType.Ifm, "application/vnd.shana.informed.formdata" },
+ { ContentType.Iges, "model/iges" },
+ { ContentType.Igl, "application/vnd.igloader" },
+ { ContentType.Igm, "application/vnd.insors.igm" },
+ { ContentType.Igs, "model/iges" },
+ { ContentType.Igx, "application/vnd.micrografx.igx" },
+ { ContentType.Iif, "application/vnd.shana.informed.interchange" },
+ { ContentType.Img, "application/octet-stream" },
+ { ContentType.Imp, "application/vnd.accpac.simply.imp" },
+ { ContentType.Ims, "application/vnd.ms-ims" },
+ { ContentType.Ini, "text/plain" },
+ { ContentType.Ink, "application/inkml+xml" },
+ { ContentType.Inkml, "application/inkml+xml" },
+ { ContentType.Install, "application/x-install-instructions" },
+ { ContentType.Iota, "application/vnd.astraea-software.iota" },
+ { ContentType.Ipfix, "application/ipfix" },
+ { ContentType.Ipk, "application/vnd.shana.informed.package" },
+ { ContentType.Irm, "application/vnd.ibm.rights-management" },
+ { ContentType.Irp, "application/vnd.irepository.package+xml" },
+ { ContentType.Iso, "application/octet-stream" },
+ { ContentType.Itp, "application/vnd.shana.informed.formtemplate" },
+ { ContentType.Its, "application/its+xml" },
+ { ContentType.Ivp, "application/vnd.immervision-ivp" },
+ { ContentType.Ivu, "application/vnd.immervision-ivu" },
+ { ContentType.Jad, "text/vnd.sun.j2me.app-descriptor" },
+ { ContentType.Jade, "text/jade" },
+ { ContentType.Jam, "application/vnd.jam" },
+ { ContentType.Jar, "application/java-archive" },
+ { ContentType.Jardiff, "application/x-java-archive-diff" },
+ { ContentType.Java, "text/x-java-source" },
+ { ContentType.Jhc, "image/jphc" },
+ { ContentType.Jisp, "application/vnd.jisp" },
+ { ContentType.Jls, "image/jls" },
+ { ContentType.Jlt, "application/vnd.hp-jlyt" },
+ { ContentType.Jng, "image/x-jng" },
+ { ContentType.Jnlp, "application/x-java-jnlp-file" },
+ { ContentType.Joda, "application/vnd.joost.joda-archive" },
+ { ContentType.Jp2, "image/jp2" },
+ { ContentType.Jpe, "image/jpeg" },
+ { ContentType.Jpeg, "image/jpeg" },
+ { ContentType.Jpf, "image/jpx" },
+ { ContentType.Jpg, "image/jpeg" },
+ { ContentType.Jpg2, "image/jp2" },
+ { ContentType.Jpgm, "video/jpm" },
+ { ContentType.Jpgv, "video/jpeg" },
+ { ContentType.Jph, "image/jph" },
+ { ContentType.Jpm, "image/jpm" },
+ { ContentType.Jpx, "image/jpx" },
+ { ContentType.Javascript, "application/javascript" },
+ { ContentType.Json, "application/json" },
+ { ContentType.Json5, "application/json5" },
+ { ContentType.Jsonld, "application/ld+json" },
+ { ContentType.Jsonml, "application/jsonml+json" },
+ { ContentType.Jsx, "text/jsx" },
+ { ContentType.Jxr, "image/jxr" },
+ { ContentType.Jxra, "image/jxra" },
+ { ContentType.Jxrs, "image/jxrs" },
+ { ContentType.Jxs, "image/jxs" },
+ { ContentType.Jxsc, "image/jxsc" },
+ { ContentType.Jxsi, "image/jxsi" },
+ { ContentType.Jxss, "image/jxss" },
+ { ContentType.Kar, "audio/midi" },
+ { ContentType.Karbon, "application/vnd.kde.karbon" },
+ { ContentType.Kdbx, "application/x-keepass2" },
+ { ContentType.Key, "application/vnd.apple.keynote" },
+ { ContentType.Kfo, "application/vnd.kde.kformula" },
+ { ContentType.Kia, "application/vnd.kidspiration" },
+ { ContentType.Kml, "application/vnd.google-earth.kml+xml" },
+ { ContentType.Kmz, "application/vnd.google-earth.kmz" },
+ { ContentType.Kne, "application/vnd.kinar" },
+ { ContentType.Knp, "application/vnd.kinar" },
+ { ContentType.Kon, "application/vnd.kde.kontour" },
+ { ContentType.Kpr, "application/vnd.kde.kpresenter" },
+ { ContentType.Kpt, "application/vnd.kde.kpresenter" },
+ { ContentType.Kpxx, "application/vnd.ds-keypoint" },
+ { ContentType.Ksp, "application/vnd.kde.kspread" },
+ { ContentType.Ktr, "application/vnd.kahootz" },
+ { ContentType.Ktx, "image/ktx" },
+ { ContentType.Ktx2, "image/ktx2" },
+ { ContentType.Ktz, "application/vnd.kahootz" },
+ { ContentType.Kwd, "application/vnd.kde.kword" },
+ { ContentType.Kwt, "application/vnd.kde.kword" },
+ { ContentType.Lasxml, "application/vnd.las.las+xml" },
+ { ContentType.Latex, "application/x-latex" },
+ { ContentType.Lbd, "application/vnd.llamagraphics.life-balance.desktop" },
+ { ContentType.Lbe, "application/vnd.llamagraphics.life-balance.exchange+xml" },
+ { ContentType.Les, "application/vnd.hhe.lesson-player" },
+ { ContentType.Less, "text/less" },
+ { ContentType.Lgr, "application/lgr+xml" },
+ { ContentType.Lha, "application/x-lzh-compressed" },
+ { ContentType.Link66, "application/vnd.route66.link66+xml" },
+ { ContentType.List, "text/plain" },
+ { ContentType.List3820, "application/vnd.ibm.modcap" },
+ { ContentType.Listafp, "application/vnd.ibm.modcap" },
+ { ContentType.Lnk, "application/x-ms-shortcut" },
+ { ContentType.Log, "text/plain" },
+ { ContentType.Lostxml, "application/lost+xml" },
+ { ContentType.Lrf, "application/octet-stream" },
+ { ContentType.Lrm, "application/vnd.ms-lrm" },
+ { ContentType.Ltf, "application/vnd.frogans.ltf" },
+ { ContentType.Lua, "text/x-lua" },
+ { ContentType.Luac, "application/x-lua-bytecode" },
+ { ContentType.Lvp, "audio/vnd.lucent.voice" },
+ { ContentType.Lwp, "application/vnd.lotus-wordpro" },
+ { ContentType.Lzh, "application/x-lzh-compressed" },
+ { ContentType.M13, "application/x-msmediaview" },
+ { ContentType.M14, "application/x-msmediaview" },
+ { ContentType.M1v, "video/mpeg" },
+ { ContentType.M21, "application/mp21" },
+ { ContentType.M2a, "audio/mpeg" },
+ { ContentType.M2v, "video/mpeg" },
+ { ContentType.M3a, "audio/mpeg" },
+ { ContentType.M3u, "audio/x-mpegurl" },
+ { ContentType.M3u8, "application/vnd.apple.mpegurl" },
+ { ContentType.M4a, "audio/mp4" },
+ { ContentType.M4p, "application/mp4" },
+ { ContentType.M4s, "video/iso.segment" },
+ { ContentType.M4u, "video/vnd.mpegurl" },
+ { ContentType.M4v, "video/x-m4v" },
+ { ContentType.Ma, "application/mathematica" },
+ { ContentType.Mads, "application/mads+xml" },
+ { ContentType.Maei, "application/mmt-aei+xml" },
+ { ContentType.Mag, "application/vnd.ecowin.chart" },
+ { ContentType.Maker, "application/vnd.framemaker" },
+ { ContentType.Man, "text/troff" },
+ { ContentType.Manifest, "text/cache-manifest" },
+ { ContentType.Map, "application/json" },
+ { ContentType.Mar, "application/octet-stream" },
+ { ContentType.Markdown, "text/markdown" },
+ { ContentType.Mathml, "application/mathml+xml" },
+ { ContentType.Mb, "application/mathematica" },
+ { ContentType.Mbk, "application/vnd.mobius.mbk" },
+ { ContentType.Mbox, "application/mbox" },
+ { ContentType.Mc1, "application/vnd.medcalcdata" },
+ { ContentType.Mcd, "application/vnd.mcd" },
+ { ContentType.Mcurl, "text/vnd.curl.mcurl" },
+ { ContentType.Md, "text/markdown" },
+ { ContentType.Mdb, "application/x-msaccess" },
+ { ContentType.Mdi, "image/vnd.ms-modi" },
+ { ContentType.Mdx, "text/mdx" },
+ { ContentType.Me, "text/troff" },
+ { ContentType.Mesh, "model/mesh" },
+ { ContentType.Meta4, "application/metalink4+xml" },
+ { ContentType.Metalink, "application/metalink+xml" },
+ { ContentType.Mets, "application/mets+xml" },
+ { ContentType.Mfm, "application/vnd.mfmp" },
+ { ContentType.Mft, "application/rpki-manifest" },
+ { ContentType.Mgp, "application/vnd.osgeo.mapguide.package" },
+ { ContentType.Mgz, "application/vnd.proteus.magazine" },
+ { ContentType.Mid, "audio/midi" },
+ { ContentType.Midi, "audio/midi" },
+ { ContentType.Mie, "application/x-mie" },
+ { ContentType.Mif, "application/vnd.mif" },
+ { ContentType.Mime, "message/rfc822" },
+ { ContentType.Mj2, "video/mj2" },
+ { ContentType.Mjp2, "video/mj2" },
+ { ContentType.Mjs, "application/javascript" },
+ { ContentType.Mk3d, "video/x-matroska" },
+ { ContentType.Mka, "audio/x-matroska" },
+ { ContentType.Mkd, "text/x-markdown" },
+ { ContentType.Mks, "video/x-matroska" },
+ { ContentType.Mkv, "video/x-matroska" },
+ { ContentType.Mlp, "application/vnd.dolby.mlp" },
+ { ContentType.Mmd, "application/vnd.chipnuts.karaoke-mmd" },
+ { ContentType.Mmf, "application/vnd.smaf" },
+ { ContentType.Mml, "text/mathml" },
+ { ContentType.Mmr, "image/vnd.fujixerox.edmics-mmr" },
+ { ContentType.Mng, "video/x-mng" },
+ { ContentType.Mny, "application/x-msmoney" },
+ { ContentType.Mobi, "application/x-mobipocket-ebook" },
+ { ContentType.Mods, "application/mods+xml" },
+ { ContentType.Mov, "video/quicktime" },
+ { ContentType.Movie, "video/x-sgi-movie" },
+ { ContentType.Mp2, "audio/mpeg" },
+ { ContentType.Mp21, "application/mp21" },
+ { ContentType.Mp2a, "audio/mpeg" },
+ { ContentType.Mp3, "audio/mp3" },
+ { ContentType.Mp4, "video/mp4" },
+ { ContentType.Mp4a, "audio/mp4" },
+ { ContentType.Mp4s, "application/mp4" },
+ { ContentType.Mp4v, "video/mp4" },
+ { ContentType.Mpc, "application/vnd.mophun.certificate" },
+ { ContentType.Mpd, "application/dash+xml" },
+ { ContentType.Mpe, "video/mpeg" },
+ { ContentType.Mpeg, "video/mpeg" },
+ { ContentType.Mpg, "video/mpeg" },
+ { ContentType.Mpg4, "video/mp4" },
+ { ContentType.Mpga, "audio/mpeg" },
+ { ContentType.Mpkg, "application/vnd.apple.installer+xml" },
+ { ContentType.Mpm, "application/vnd.blueice.multipass" },
+ { ContentType.Mpn, "application/vnd.mophun.application" },
+ { ContentType.Mpp, "application/vnd.ms-project" },
+ { ContentType.Mpt, "application/vnd.ms-project" },
+ { ContentType.Mpy, "application/vnd.ibm.minipay" },
+ { ContentType.Mqy, "application/vnd.mobius.mqy" },
+ { ContentType.Mrc, "application/marc" },
+ { ContentType.Mrcx, "application/marcxml+xml" },
+ { ContentType.Ms, "text/troff" },
+ { ContentType.Mscml, "application/mediaservercontrol+xml" },
+ { ContentType.Mseed, "application/vnd.fdsn.mseed" },
+ { ContentType.Mseq, "application/vnd.mseq" },
+ { ContentType.Msf, "application/vnd.epson.msf" },
+ { ContentType.Msg, "application/vnd.ms-outlook" },
+ { ContentType.Msh, "model/mesh" },
+ { ContentType.Msi, "application/octet-stream" },
+ { ContentType.Msl, "application/vnd.mobius.msl" },
+ { ContentType.Msm, "application/octet-stream" },
+ { ContentType.Msp, "application/octet-stream" },
+ { ContentType.Msty, "application/vnd.muvee.style" },
+ { ContentType.Mtl, "model/mtl" },
+ { ContentType.Mts, "model/vnd.mts" },
+ { ContentType.Mus, "application/vnd.musician" },
+ { ContentType.Musd, "application/mmt-usd+xml" },
+ { ContentType.Musicxml, "application/vnd.recordare.musicxml+xml" },
+ { ContentType.Mvb, "application/x-msmediaview" },
+ { ContentType.Mvt, "application/vnd.mapbox-vector-tile" },
+ { ContentType.Mwf, "application/vnd.mfer" },
+ { ContentType.Mxf, "application/mxf" },
+ { ContentType.Mxl, "application/vnd.recordare.musicxml" },
+ { ContentType.Mxmf, "audio/mobile-xmf" },
+ { ContentType.Mxml, "application/xv+xml" },
+ { ContentType.Mxs, "application/vnd.triscape.mxs" },
+ { ContentType.Mxu, "video/vnd.mpegurl" },
+ { ContentType.N3, "text/n3" },
+ { ContentType.Nb, "application/mathematica" },
+ { ContentType.Nbp, "application/vnd.wolfram.player" },
+ { ContentType.Nc, "application/x-netcdf" },
+ { ContentType.Ncx, "application/x-dtbncx+xml" },
+ { ContentType.Nfo, "text/x-nfo" },
+ { ContentType.Ngdat, "application/vnd.nokia.n-gage.data" },
+ { ContentType.Nitf, "application/vnd.nitf" },
+ { ContentType.Nlu, "application/vnd.neurolanguage.nlu" },
+ { ContentType.Nml, "application/vnd.enliven" },
+ { ContentType.Nnd, "application/vnd.noblenet-directory" },
+ { ContentType.Nns, "application/vnd.noblenet-sealer" },
+ { ContentType.Nnw, "application/vnd.noblenet-web" },
+ { ContentType.Npx, "image/vnd.net-fpx" },
+ { ContentType.Nq, "application/n-quads" },
+ { ContentType.Nsc, "application/x-conference" },
+ { ContentType.Nsf, "application/vnd.lotus-notes" },
+ { ContentType.Nt, "application/n-triples" },
+ { ContentType.Ntf, "application/vnd.nitf" },
+ { ContentType.Numbers, "application/vnd.apple.numbers" },
+ { ContentType.Nzb, "application/x-nzb" },
+ { ContentType.Oa2, "application/vnd.fujitsu.oasys2" },
+ { ContentType.Oa3, "application/vnd.fujitsu.oasys3" },
+ { ContentType.Oas, "application/vnd.fujitsu.oasys" },
+ { ContentType.Obd, "application/x-msbinder" },
+ { ContentType.Obgx, "application/vnd.openblox.game+xml" },
+ { ContentType.Obj, "application/x-tgif" },
+ { ContentType.Oda, "application/oda" },
+ { ContentType.Odb, "application/vnd.oasis.opendocument.database" },
+ { ContentType.Odc, "application/vnd.oasis.opendocument.chart" },
+ { ContentType.Odf, "application/vnd.oasis.opendocument.formula" },
+ { ContentType.Odft, "application/vnd.oasis.opendocument.formula-template" },
+ { ContentType.Odg, "application/vnd.oasis.opendocument.graphics" },
+ { ContentType.Odi, "application/vnd.oasis.opendocument.image" },
+ { ContentType.Odm, "application/vnd.oasis.opendocument.text-master" },
+ { ContentType.Odp, "application/vnd.oasis.opendocument.presentation" },
+ { ContentType.Ods, "application/vnd.oasis.opendocument.spreadsheet" },
+ { ContentType.Odt, "application/vnd.oasis.opendocument.text" },
+ { ContentType.Oga, "audio/ogg" },
+ { ContentType.Ogex, "model/vnd.opengex" },
+ { ContentType.Ogg, "audio/ogg" },
+ { ContentType.Ogv, "video/ogg" },
+ { ContentType.Ogx, "application/ogg" },
+ { ContentType.Omdoc, "application/omdoc+xml" },
+ { ContentType.Onepkg, "application/onenote" },
+ { ContentType.Onetmp, "application/onenote" },
+ { ContentType.Onetoc, "application/onenote" },
+ { ContentType.Onetoc2, "application/onenote" },
+ { ContentType.Opf, "application/oebps-package+xml" },
+ { ContentType.Opml, "text/x-opml" },
+ { ContentType.Oprc, "application/vnd.palm" },
+ { ContentType.Opus, "audio/ogg" },
+ { ContentType.Org, "application/vnd.lotus-organizer" },
+ { ContentType.Osf, "application/vnd.yamaha.openscoreformat" },
+ { ContentType.Osfpvg, "application/vnd.yamaha.openscoreformat.osfpvg+xml" },
+ { ContentType.Osm, "application/vnd.openstreetmap.data+xml" },
+ { ContentType.Otc, "application/vnd.oasis.opendocument.chart-template" },
+ { ContentType.Otf, "font/otf" },
+ { ContentType.Otg, "application/vnd.oasis.opendocument.graphics-template" },
+ { ContentType.Oth, "application/vnd.oasis.opendocument.text-web" },
+ { ContentType.Oti, "application/vnd.oasis.opendocument.image-template" },
+ { ContentType.Otp, "application/vnd.oasis.opendocument.presentation-template" },
+ { ContentType.Ots, "application/vnd.oasis.opendocument.spreadsheet-template" },
+ { ContentType.Ott, "application/vnd.oasis.opendocument.text-template" },
+ { ContentType.Ova, "application/x-virtualbox-ova" },
+ { ContentType.Ovf, "application/x-virtualbox-ovf" },
+ { ContentType.Owl, "application/rdf+xml" },
+ { ContentType.Oxps, "application/oxps" },
+ { ContentType.Oxt, "application/vnd.openofficeorg.extension" },
+ { ContentType.P, "text/x-pascal" },
+ { ContentType.P10, "application/pkcs10" },
+ { ContentType.P12, "application/x-pkcs12" },
+ { ContentType.P7b, "application/x-pkcs7-certificates" },
+ { ContentType.P7c, "application/pkcs7-mime" },
+ { ContentType.P7m, "application/pkcs7-mime" },
+ { ContentType.P7r, "application/x-pkcs7-certreqresp" },
+ { ContentType.P7s, "application/pkcs7-signature" },
+ { ContentType.P8, "application/pkcs8" },
+ { ContentType.Pac, "application/x-ns-proxy-autoconfig" },
+ { ContentType.Pages, "application/vnd.apple.pages" },
+ { ContentType.Pas, "text/x-pascal" },
+ { ContentType.Paw, "application/vnd.pawaafile" },
+ { ContentType.Pbd, "application/vnd.powerbuilder6" },
+ { ContentType.Pbm, "image/x-portable-bitmap" },
+ { ContentType.Pcap, "application/vnd.tcpdump.pcap" },
+ { ContentType.Pcf, "application/x-font-pcf" },
+ { ContentType.Pcl, "application/vnd.hp-pcl" },
+ { ContentType.Pclxl, "application/vnd.hp-pclxl" },
+ { ContentType.Pct, "image/x-pict" },
+ { ContentType.Pcurl, "application/vnd.curl.pcurl" },
+ { ContentType.Pcx, "image/vnd.zbrush.pcx" },
+ { ContentType.Pdb, "application/vnd.palm" },
+ { ContentType.Pde, "text/x-processing" },
+ { ContentType.Pdf, "application/pdf" },
+ { ContentType.Pem, "application/x-x509-ca-cert" },
+ { ContentType.Pfa, "application/x-font-type1" },
+ { ContentType.Pfb, "application/x-font-type1" },
+ { ContentType.Pfm, "application/x-font-type1" },
+ { ContentType.Pfr, "application/font-tdpfr" },
+ { ContentType.Pfx, "application/x-pkcs12" },
+ { ContentType.Pgm, "image/x-portable-graymap" },
+ { ContentType.Pgn, "application/x-chess-pgn" },
+ { ContentType.Pgp, "application/pgp-encrypted" },
+ { ContentType.Php, "application/x-httpd-php" },
+ { ContentType.Pic, "image/x-pict" },
+ { ContentType.Pkg, "application/octet-stream" },
+ { ContentType.Pki, "application/pkixcmp" },
+ { ContentType.Pkipath, "application/pkix-pkipath" },
+ { ContentType.Pkpass, "application/vnd.apple.pkpass" },
+ { ContentType.Pl, "application/x-perl" },
+ { ContentType.Plb, "application/vnd.3gpp.pic-bw-large" },
+ { ContentType.Plc, "application/vnd.mobius.plc" },
+ { ContentType.Plf, "application/vnd.pocketlearn" },
+ { ContentType.Pls, "application/pls+xml" },
+ { ContentType.Pm, "application/x-perl" },
+ { ContentType.Pml, "application/vnd.ctc-posml" },
+ { ContentType.Png, "image/png" },
+ { ContentType.Pnm, "image/x-portable-anymap" },
+ { ContentType.Portpkg, "application/vnd.macports.portpkg" },
+ { ContentType.Pot, "application/vnd.ms-powerpoint" },
+ { ContentType.Potm, "application/vnd.ms-powerpoint.template.macroenabled.12" },
+ { ContentType.Potx, "application/vnd.openxmlformats-officedocument.presentationml.template" },
+ { ContentType.Ppam, "application/vnd.ms-powerpoint.addin.macroenabled.12" },
+ { ContentType.Ppd, "application/vnd.cups-ppd" },
+ { ContentType.Ppm, "image/x-portable-pixmap" },
+ { ContentType.Pps, "application/vnd.ms-powerpoint" },
+ { ContentType.Ppsm, "application/vnd.ms-powerpoint.slideshow.macroenabled.12" },
+ { ContentType.Ppsx, "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
+ { ContentType.Ppt, "application/vnd.ms-powerpoint" },
+ { ContentType.Pptm, "application/vnd.ms-powerpoint.presentation.macroenabled.12" },
+ { ContentType.Pptx, "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
+ { ContentType.Pqa, "application/vnd.palm" },
+ { ContentType.Prc, "application/x-mobipocket-ebook" },
+ { ContentType.Pre, "application/vnd.lotus-freelance" },
+ { ContentType.Prf, "application/pics-rules" },
+ { ContentType.Provx, "application/provenance+xml" },
+ { ContentType.Ps, "application/postscript" },
+ { ContentType.Psb, "application/vnd.3gpp.pic-bw-small" },
+ { ContentType.Psd, "image/vnd.adobe.photoshop" },
+ { ContentType.Psf, "application/x-font-linux-psf" },
+ { ContentType.Pskcxml, "application/pskc+xml" },
+ { ContentType.Pti, "image/prs.pti" },
+ { ContentType.Ptid, "application/vnd.pvi.ptid1" },
+ { ContentType.Pub, "application/x-mspublisher" },
+ { ContentType.Pvb, "application/vnd.3gpp.pic-bw-var" },
+ { ContentType.Pwn, "application/vnd.3m.post-it-notes" },
+ { ContentType.Pya, "audio/vnd.ms-playready.media.pya" },
+ { ContentType.Pyv, "video/vnd.ms-playready.media.pyv" },
+ { ContentType.Qam, "application/vnd.epson.quickanime" },
+ { ContentType.Qbo, "application/vnd.intu.qbo" },
+ { ContentType.Qfx, "application/vnd.intu.qfx" },
+ { ContentType.Qps, "application/vnd.publishare-delta-tree" },
+ { ContentType.Qt, "video/quicktime" },
+ { ContentType.Qwd, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qwt, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxb, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxd, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxl, "application/vnd.quark.quarkxpress" },
+ { ContentType.Qxt, "application/vnd.quark.quarkxpress" },
+ { ContentType.Ra, "audio/x-pn-realaudio" },
+ { ContentType.Ram, "audio/x-pn-realaudio" },
+ { ContentType.Raml, "application/raml+yaml" },
+ { ContentType.Rapd, "application/route-apd+xml" },
+ { ContentType.Rar, "application/vnd.rar" },
+ { ContentType.Ras, "image/x-cmu-raster" },
+ { ContentType.Rdf, "application/rdf+xml" },
+ { ContentType.Rdz, "application/vnd.data-vision.rdz" },
+ { ContentType.Relo, "application/p2p-overlay+xml" },
+ { ContentType.Rep, "application/vnd.businessobjects" },
+ { ContentType.Res, "application/x-dtbresource+xml" },
+ { ContentType.Rgb, "image/x-rgb" },
+ { ContentType.Rif, "application/reginfo+xml" },
+ { ContentType.Rip, "audio/vnd.rip" },
+ { ContentType.Ris, "application/x-research-info-systems" },
+ { ContentType.Rl, "application/resource-lists+xml" },
+ { ContentType.Rlc, "image/vnd.fujixerox.edmics-rlc" },
+ { ContentType.Rld, "application/resource-lists-diff+xml" },
+ { ContentType.Rm, "application/vnd.rn-realmedia" },
+ { ContentType.Rmi, "audio/midi" },
+ { ContentType.Rmp, "audio/x-pn-realaudio-plugin" },
+ { ContentType.Rms, "application/vnd.jcp.javame.midlet-rms" },
+ { ContentType.Rmvb, "application/vnd.rn-realmedia-vbr" },
+ { ContentType.Rnc, "application/relax-ng-compact-syntax" },
+ { ContentType.Rng, "application/xml" },
+ { ContentType.Roa, "application/rpki-roa" },
+ { ContentType.Roff, "text/troff" },
+ { ContentType.Rp9, "application/vnd.cloanto.rp9" },
+ { ContentType.Rpm, "application/x-redhat-package-manager" },
+ { ContentType.Rpss, "application/vnd.nokia.radio-presets" },
+ { ContentType.Rpst, "application/vnd.nokia.radio-preset" },
+ { ContentType.Rq, "application/sparql-query" },
+ { ContentType.Rs, "application/rls-services+xml" },
+ { ContentType.Rsat, "application/atsc-rsat+xml" },
+ { ContentType.Rsd, "application/rsd+xml" },
+ { ContentType.Rsheet, "application/urc-ressheet+xml" },
+ { ContentType.Rss, "application/rss+xml" },
+ { ContentType.Rtf, "application/rtf" },
+ { ContentType.Rtx, "text/richtext" },
+ { ContentType.Run, "application/x-makeself" },
+ { ContentType.Rusd, "application/route-usd+xml" },
+ { ContentType.S, "text/x-asm" },
+ { ContentType.S3m, "audio/s3m" },
+ { ContentType.Saf, "application/vnd.yamaha.smaf-audio" },
+ { ContentType.Sass, "text/x-sass" },
+ { ContentType.Sbml, "application/sbml+xml" },
+ { ContentType.Sc, "application/vnd.ibm.secure-container" },
+ { ContentType.Scd, "application/x-msschedule" },
+ { ContentType.Scm, "application/vnd.lotus-screencam" },
+ { ContentType.Scq, "application/scvp-cv-request" },
+ { ContentType.Scs, "application/scvp-cv-response" },
+ { ContentType.Scss, "text/x-scss" },
+ { ContentType.Scurl, "text/vnd.curl.scurl" },
+ { ContentType.Sda, "application/vnd.stardivision.draw" },
+ { ContentType.Sdc, "application/vnd.stardivision.calc" },
+ { ContentType.Sdd, "application/vnd.stardivision.impress" },
+ { ContentType.Sdkd, "application/vnd.solent.sdkm+xml" },
+ { ContentType.Sdkm, "application/vnd.solent.sdkm+xml" },
+ { ContentType.Sdp, "application/sdp" },
+ { ContentType.Sdw, "application/vnd.stardivision.writer" },
+ { ContentType.Sea, "application/x-sea" },
+ { ContentType.See, "application/vnd.seemail" },
+ { ContentType.Seed, "application/vnd.fdsn.seed" },
+ { ContentType.Sema, "application/vnd.sema" },
+ { ContentType.Semd, "application/vnd.semd" },
+ { ContentType.Semf, "application/vnd.semf" },
+ { ContentType.Senmlx, "application/senml+xml" },
+ { ContentType.Sensmlx, "application/sensml+xml" },
+ { ContentType.Ser, "application/java-serialized-object" },
+ { ContentType.Setpay, "application/set-payment-initiation" },
+ { ContentType.Setreg, "application/set-registration-initiation" },
+ { ContentType.Sfs, "application/vnd.spotfire.sfs" },
+ { ContentType.Sfv, "text/x-sfv" },
+ { ContentType.Sgi, "image/sgi" },
+ { ContentType.Sgl, "application/vnd.stardivision.writer-global" },
+ { ContentType.Sgm, "text/sgml" },
+ { ContentType.Sgml, "text/sgml" },
+ { ContentType.Sh, "application/x-sh" },
+ { ContentType.Shar, "application/x-shar" },
+ { ContentType.Shex, "text/shex" },
+ { ContentType.Shf, "application/shf+xml" },
+ { ContentType.Shtml, "text/html" },
+ { ContentType.Sid, "image/x-mrsid-image" },
+ { ContentType.Sieve, "application/sieve" },
+ { ContentType.Sig, "application/pgp-signature" },
+ { ContentType.Sil, "audio/silk" },
+ { ContentType.Silo, "model/mesh" },
+ { ContentType.Sis, "application/vnd.symbian.install" },
+ { ContentType.Sisx, "application/vnd.symbian.install" },
+ { ContentType.Sit, "application/x-stuffit" },
+ { ContentType.Sitx, "application/x-stuffitx" },
+ { ContentType.Siv, "application/sieve" },
+ { ContentType.Skd, "application/vnd.koan" },
+ { ContentType.Skm, "application/vnd.koan" },
+ { ContentType.Skp, "application/vnd.koan" },
+ { ContentType.Skt, "application/vnd.koan" },
+ { ContentType.Sldm, "application/vnd.ms-powerpoint.slide.macroenabled.12" },
+ { ContentType.Sldx, "application/vnd.openxmlformats-officedocument.presentationml.slide" },
+ { ContentType.Slim, "text/slim" },
+ { ContentType.Slm, "text/slim" },
+ { ContentType.Sls, "application/route-s-tsid+xml" },
+ { ContentType.Slt, "application/vnd.epson.salt" },
+ { ContentType.Sm, "application/vnd.stepmania.stepchart" },
+ { ContentType.Smf, "application/vnd.stardivision.math" },
+ { ContentType.Smi, "application/smil+xml" },
+ { ContentType.Smil, "application/smil+xml" },
+ { ContentType.Smv, "video/x-smv" },
+ { ContentType.Smzip, "application/vnd.stepmania.package" },
+ { ContentType.Snd, "audio/basic" },
+ { ContentType.Snf, "application/x-font-snf" },
+ { ContentType.So, "application/octet-stream" },
+ { ContentType.Spc, "application/x-pkcs7-certificates" },
+ { ContentType.Spdx, "text/spdx" },
+ { ContentType.Spf, "application/vnd.yamaha.smaf-phrase" },
+ { ContentType.Spl, "application/x-futuresplash" },
+ { ContentType.Spot, "text/vnd.in3d.spot" },
+ { ContentType.Spp, "application/scvp-vp-response" },
+ { ContentType.Spq, "application/scvp-vp-request" },
+ { ContentType.Spx, "audio/ogg" },
+ { ContentType.Sql, "application/x-sql" },
+ { ContentType.Src, "application/x-wais-source" },
+ { ContentType.Srt, "application/x-subrip" },
+ { ContentType.Sru, "application/sru+xml" },
+ { ContentType.Srx, "application/sparql-results+xml" },
+ { ContentType.Ssdl, "application/ssdl+xml" },
+ { ContentType.Sse, "application/vnd.kodak-descriptor" },
+ { ContentType.Ssf, "application/vnd.epson.ssf" },
+ { ContentType.Ssml, "application/ssml+xml" },
+ { ContentType.St, "application/vnd.sailingtracker.track" },
+ { ContentType.Stc, "application/vnd.sun.xml.calc.template" },
+ { ContentType.Std, "application/vnd.sun.xml.draw.template" },
+ { ContentType.Stf, "application/vnd.wt.stf" },
+ { ContentType.Sti, "application/vnd.sun.xml.impress.template" },
+ { ContentType.Stk, "application/hyperstudio" },
+ { ContentType.Stl, "application/vnd.ms-pki.stl" },
+ { ContentType.Stpx, "model/step+xml" },
+ { ContentType.Stpxz, "model/step-xml+zip" },
+ { ContentType.Stpz, "model/step+zip" },
+ { ContentType.Str, "application/vnd.pg.format" },
+ { ContentType.Stw, "application/vnd.sun.xml.writer.template" },
+ { ContentType.Styl, "text/stylus" },
+ { ContentType.Stylus, "text/stylus" },
+ { ContentType.Sub, "image/vnd.dvb.subtitle" },
+ { ContentType.Sus, "application/vnd.sus-calendar" },
+ { ContentType.Susp, "application/vnd.sus-calendar" },
+ { ContentType.Sv4cpio, "application/x-sv4cpio" },
+ { ContentType.Sv4crc, "application/x-sv4crc" },
+ { ContentType.Svc, "application/vnd.dvb.service" },
+ { ContentType.Svd, "application/vnd.svd" },
+ { ContentType.Svg, "image/svg+xml" },
+ { ContentType.Svgz, "image/svg+xml" },
+ { ContentType.Swa, "application/x-director" },
+ { ContentType.Swf, "application/x-shockwave-flash" },
+ { ContentType.Swi, "application/vnd.aristanetworks.swi" },
+ { ContentType.Swidtag, "application/swid+xml" },
+ { ContentType.Sxc, "application/vnd.sun.xml.calc" },
+ { ContentType.Sxd, "application/vnd.sun.xml.draw" },
+ { ContentType.Sxg, "application/vnd.sun.xml.writer.global" },
+ { ContentType.Sxi, "application/vnd.sun.xml.impress" },
+ { ContentType.Sxm, "application/vnd.sun.xml.math" },
+ { ContentType.Sxw, "application/vnd.sun.xml.writer" },
+ { ContentType.T, "text/troff" },
+ { ContentType.T3, "application/x-t3vm-image" },
+ { ContentType.T38, "image/t38" },
+ { ContentType.Taglet, "application/vnd.mynfc" },
+ { ContentType.Tao, "application/vnd.tao.intent-module-archive" },
+ { ContentType.Tap, "image/vnd.tencent.tap" },
+ { ContentType.Tar, "application/x-tar" },
+ { ContentType.Tcap, "application/vnd.3gpp2.tcap" },
+ { ContentType.Tcl, "application/x-tcl" },
+ { ContentType.Td, "application/urc-targetdesc+xml" },
+ { ContentType.Teacher, "application/vnd.smart.teacher" },
+ { ContentType.Tei, "application/tei+xml" },
+ { ContentType.Tex, "application/x-tex" },
+ { ContentType.Texi, "application/x-texinfo" },
+ { ContentType.Texinfo, "application/x-texinfo" },
+ { ContentType.Text, "text/plain" },
+ { ContentType.Tfi, "application/thraud+xml" },
+ { ContentType.Tfm, "application/x-tex-tfm" },
+ { ContentType.Tfx, "image/tiff-fx" },
+ { ContentType.Tga, "image/x-tga" },
+ { ContentType.Thmx, "application/vnd.ms-officetheme" },
+ { ContentType.Tif, "image/tiff" },
+ { ContentType.Tiff, "image/tiff" },
+ { ContentType.Tk, "application/x-tcl" },
+ { ContentType.Tmo, "application/vnd.tmobile-livetv" },
+ { ContentType.Toml, "application/toml" },
+ { ContentType.Torrent, "application/x-bittorrent" },
+ { ContentType.Tpl, "application/vnd.groove-tool-template" },
+ { ContentType.Tpt, "application/vnd.trid.tpt" },
+ { ContentType.Tr, "text/troff" },
+ { ContentType.Tra, "application/vnd.trueapp" },
+ { ContentType.Trig, "application/trig" },
+ { ContentType.Trm, "application/x-msterminal" },
+ { ContentType.Ts, "video/mp2t" },
+ { ContentType.Tsd, "application/timestamped-data" },
+ { ContentType.Tsv, "text/tab-separated-values" },
+ { ContentType.Ttc, "font/collection" },
+ { ContentType.Ttf, "font/ttf" },
+ { ContentType.Ttl, "text/turtle" },
+ { ContentType.Ttml, "application/ttml+xml" },
+ { ContentType.Twd, "application/vnd.simtech-mindmapper" },
+ { ContentType.Twds, "application/vnd.simtech-mindmapper" },
+ { ContentType.Txd, "application/vnd.genomatix.tuxedo" },
+ { ContentType.Txf, "application/vnd.mobius.txf" },
+ { ContentType.Txt, "text/plain" },
+ { ContentType.U32, "application/x-authorware-bin" },
+ { ContentType.U8dsn, "message/global-delivery-status" },
+ { ContentType.U8hdr, "message/global-headers" },
+ { ContentType.U8mdn, "message/global-disposition-notification" },
+ { ContentType.U8msg, "message/global" },
+ { ContentType.Ubj, "application/ubjson" },
+ { ContentType.Udeb, "application/x-debian-package" },
+ { ContentType.Ufd, "application/vnd.ufdl" },
+ { ContentType.Ufdl, "application/vnd.ufdl" },
+ { ContentType.Ulx, "application/x-glulx" },
+ { ContentType.Umj, "application/vnd.umajin" },
+ { ContentType.Unityweb, "application/vnd.unity" },
+ { ContentType.Uoml, "application/vnd.uoml+xml" },
+ { ContentType.Uri, "text/uri-list" },
+ { ContentType.Uris, "text/uri-list" },
+ { ContentType.Urls, "text/uri-list" },
+ { ContentType.Usdz, "model/vnd.usdz+zip" },
+ { ContentType.Ustar, "application/x-ustar" },
+ { ContentType.Utz, "application/vnd.uiq.theme" },
+ { ContentType.Uu, "text/x-uuencode" },
+ { ContentType.Uva, "audio/vnd.dece.audio" },
+ { ContentType.Uvd, "application/vnd.dece.data" },
+ { ContentType.Uvf, "application/vnd.dece.data" },
+ { ContentType.Uvg, "image/vnd.dece.graphic" },
+ { ContentType.Uvh, "video/vnd.dece.hd" },
+ { ContentType.Uvi, "image/vnd.dece.graphic" },
+ { ContentType.Uvm, "video/vnd.dece.mobile" },
+ { ContentType.Uvp, "video/vnd.dece.pd" },
+ { ContentType.Uvs, "video/vnd.dece.sd" },
+ { ContentType.Uvt, "application/vnd.dece.ttml+xml" },
+ { ContentType.Uvu, "video/vnd.uvvu.mp4" },
+ { ContentType.Uvv, "video/vnd.dece.video" },
+ { ContentType.Uvva, "audio/vnd.dece.audio" },
+ { ContentType.Uvvd, "application/vnd.dece.data" },
+ { ContentType.Uvvf, "application/vnd.dece.data" },
+ { ContentType.Uvvg, "image/vnd.dece.graphic" },
+ { ContentType.Uvvh, "video/vnd.dece.hd" },
+ { ContentType.Uvvi, "image/vnd.dece.graphic" },
+ { ContentType.Uvvm, "video/vnd.dece.mobile" },
+ { ContentType.Uvvp, "video/vnd.dece.pd" },
+ { ContentType.Uvvs, "video/vnd.dece.sd" },
+ { ContentType.Uvvt, "application/vnd.dece.ttml+xml" },
+ { ContentType.Uvvu, "video/vnd.uvvu.mp4" },
+ { ContentType.Uvvv, "video/vnd.dece.video" },
+ { ContentType.Uvvx, "application/vnd.dece.unspecified" },
+ { ContentType.Uvvz, "application/vnd.dece.zip" },
+ { ContentType.Uvx, "application/vnd.dece.unspecified" },
+ { ContentType.Uvz, "application/vnd.dece.zip" },
+ { ContentType.Vbox, "application/x-virtualbox-vbox" },
+ { ContentType.Vcard, "text/vcard" },
+ { ContentType.Vcd, "application/x-cdlink" },
+ { ContentType.Vcf, "text/x-vcard" },
+ { ContentType.Vcg, "application/vnd.groove-vcard" },
+ { ContentType.Vcs, "text/x-vcalendar" },
+ { ContentType.Vcx, "application/vnd.vcx" },
+ { ContentType.Vdi, "application/x-virtualbox-vdi" },
+ { ContentType.Vds, "model/vnd.sap.vds" },
+ { ContentType.Vhd, "application/x-virtualbox-vhd" },
+ { ContentType.Vis, "application/vnd.visionary" },
+ { ContentType.Viv, "video/vnd.vivo" },
+ { ContentType.Vmdk, "application/x-virtualbox-vmdk" },
+ { ContentType.Vob, "video/x-ms-vob" },
+ { ContentType.Vor, "application/vnd.stardivision.writer" },
+ { ContentType.Vox, "application/x-authorware-bin" },
+ { ContentType.Vrml, "model/vrml" },
+ { ContentType.Vsd, "application/vnd.visio" },
+ { ContentType.Vsf, "application/vnd.vsf" },
+ { ContentType.Vss, "application/vnd.visio" },
+ { ContentType.Vst, "application/vnd.visio" },
+ { ContentType.Vsw, "application/vnd.visio" },
+ { ContentType.Vtf, "image/vnd.valve.source.texture" },
+ { ContentType.Vtt, "text/vtt" },
+ { ContentType.Vtu, "model/vnd.vtu" },
+ { ContentType.Vxml, "application/voicexml+xml" },
+ { ContentType.W3d, "application/x-director" },
+ { ContentType.Wad, "application/x-doom" },
+ { ContentType.Wadl, "application/vnd.sun.wadl+xml" },
+ { ContentType.War, "application/java-archive" },
+ { ContentType.Wasm, "application/wasm" },
+ { ContentType.Wav, "audio/wav" },
+ { ContentType.Wax, "audio/x-ms-wax" },
+ { ContentType.Wbmp, "image/vnd.wap.wbmp" },
+ { ContentType.Wbs, "application/vnd.criticaltools.wbs+xml" },
+ { ContentType.Wbxml, "application/vnd.wap.wbxml" },
+ { ContentType.Wcm, "application/vnd.ms-works" },
+ { ContentType.Wdb, "application/vnd.ms-works" },
+ { ContentType.Wdp, "image/vnd.ms-photo" },
+ { ContentType.Weba, "audio/webm" },
+ { ContentType.Webapp, "application/x-web-app-manifest+json" },
+ { ContentType.Webm, "video/webm" },
+ { ContentType.Webp, "image/webp" },
+ { ContentType.Wg, "application/vnd.pmi.widget" },
+ { ContentType.Wgt, "application/widget" },
+ { ContentType.Wks, "application/vnd.ms-works" },
+ { ContentType.Wm, "video/x-ms-wm" },
+ { ContentType.Wma, "audio/x-ms-wma" },
+ { ContentType.Wmd, "application/x-ms-wmd" },
+ { ContentType.Wmf, "application/x-msmetafile" },
+ { ContentType.Wml, "text/vnd.wap.wml" },
+ { ContentType.Wmlc, "application/vnd.wap.wmlc" },
+ { ContentType.Wmls, "text/vnd.wap.wmlscript" },
+ { ContentType.Wmlsc, "application/vnd.wap.wmlscriptc" },
+ { ContentType.Wmv, "video/x-ms-wmv" },
+ { ContentType.Wmx, "video/x-ms-wmx" },
+ { ContentType.Wmz, "application/x-ms-wmz" },
+ { ContentType.Woff, "font/woff" },
+ { ContentType.Woff2, "font/woff2" },
+ { ContentType.Wpd, "application/vnd.wordperfect" },
+ { ContentType.Wpl, "application/vnd.ms-wpl" },
+ { ContentType.Wps, "application/vnd.ms-works" },
+ { ContentType.Wqd, "application/vnd.wqd" },
+ { ContentType.Wri, "application/x-mswrite" },
+ { ContentType.Wrl, "model/vrml" },
+ { ContentType.Wsc, "message/vnd.wfa.wsc" },
+ { ContentType.Wsdl, "application/wsdl+xml" },
+ { ContentType.Wspolicy, "application/wspolicy+xml" },
+ { ContentType.Wtb, "application/vnd.webturbo" },
+ { ContentType.Wvx, "video/x-ms-wvx" },
+ { ContentType.X32, "application/x-authorware-bin" },
+ { ContentType.X3d, "model/x3d+xml" },
+ { ContentType.X3db, "model/x3d+binary" },
+ { ContentType.X3dbz, "model/x3d+binary" },
+ { ContentType.X3dv, "model/x3d+vrml" },
+ { ContentType.X3dvz, "model/x3d+vrml" },
+ { ContentType.X3dz, "model/x3d+xml" },
+ { ContentType.Xaml, "application/xaml+xml" },
+ { ContentType.Xap, "application/x-silverlight-app" },
+ { ContentType.Xar, "application/vnd.xara" },
+ { ContentType.Xav, "application/xcap-att+xml" },
+ { ContentType.Xbap, "application/x-ms-xbap" },
+ { ContentType.Xbd, "application/vnd.fujixerox.docuworks.binder" },
+ { ContentType.Xbm, "image/x-xbitmap" },
+ { ContentType.Xca, "application/xcap-caps+xml" },
+ { ContentType.Xcs, "application/calendar+xml" },
+ { ContentType.Xdf, "application/xcap-diff+xml" },
+ { ContentType.Xdm, "application/vnd.syncml.dm+xml" },
+ { ContentType.Xdp, "application/vnd.adobe.xdp+xml" },
+ { ContentType.Xdssc, "application/dssc+xml" },
+ { ContentType.Xdw, "application/vnd.fujixerox.docuworks" },
+ { ContentType.Xel, "application/xcap-el+xml" },
+ { ContentType.Xenc, "application/xenc+xml" },
+ { ContentType.Xer, "application/patch-ops-error+xml" },
+ { ContentType.Xfdf, "application/vnd.adobe.xfdf" },
+ { ContentType.Xfdl, "application/vnd.xfdl" },
+ { ContentType.Xht, "application/xhtml+xml" },
+ { ContentType.Xhtml, "application/xhtml+xml" },
+ { ContentType.Xhvml, "application/xv+xml" },
+ { ContentType.Xif, "image/vnd.xiff" },
+ { ContentType.Xla, "application/vnd.ms-excel" },
+ { ContentType.Xlam, "application/vnd.ms-excel.addin.macroenabled.12" },
+ { ContentType.Xlc, "application/vnd.ms-excel" },
+ { ContentType.Xlf, "application/x-xliff+xml" },
+ { ContentType.Xlm, "application/vnd.ms-excel" },
+ { ContentType.Xls, "application/vnd.ms-excel" },
+ { ContentType.Xlsb, "application/vnd.ms-excel.sheet.binary.macroenabled.12" },
+ { ContentType.Xlsm, "application/vnd.ms-excel.sheet.macroenabled.12" },
+ { ContentType.Xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
+ { ContentType.Xlt, "application/vnd.ms-excel" },
+ { ContentType.Xltm, "application/vnd.ms-excel.template.macroenabled.12" },
+ { ContentType.Xltx, "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
+ { ContentType.Xlw, "application/vnd.ms-excel" },
+ { ContentType.Xm, "audio/xm" },
+ { ContentType.Xml, "application/xml" },
+ { ContentType.Xns, "application/xcap-ns+xml" },
+ { ContentType.Xo, "application/vnd.olpc-sugar" },
+ { ContentType.Xop, "application/xop+xml" },
+ { ContentType.Xpi, "application/x-xpinstall" },
+ { ContentType.Xpl, "application/xproc+xml" },
+ { ContentType.Xpm, "image/x-xpixmap" },
+ { ContentType.Xpr, "application/vnd.is-xpr" },
+ { ContentType.Xps, "application/vnd.ms-xpsdocument" },
+ { ContentType.Xpw, "application/vnd.intercon.formnet" },
+ { ContentType.Xpx, "application/vnd.intercon.formnet" },
+ { ContentType.Xsd, "application/xml" },
+ { ContentType.Xsl, "application/xml" },
+ { ContentType.Xslt, "application/xslt+xml" },
+ { ContentType.Xsm, "application/vnd.syncml+xml" },
+ { ContentType.Xspf, "application/xspf+xml" },
+ { ContentType.Xul, "application/vnd.mozilla.xul+xml" },
+ { ContentType.Xvm, "application/xv+xml" },
+ { ContentType.Xvml, "application/xv+xml" },
+ { ContentType.Xwd, "image/x-xwindowdump" },
+ { ContentType.Xyz, "chemical/x-xyz" },
+ { ContentType.Xz, "application/x-xz" },
+ { ContentType.Yaml, "text/yaml" },
+ { ContentType.Yang, "application/yang" },
+ { ContentType.Yin, "application/yin+xml" },
+ { ContentType.Yml, "text/yaml" },
+ { ContentType.Ymp, "text/x-suse-ymp" },
+ { ContentType.Z1, "application/x-zmachine" },
+ { ContentType.Z2, "application/x-zmachine" },
+ { ContentType.Z3, "application/x-zmachine" },
+ { ContentType.Z4, "application/x-zmachine" },
+ { ContentType.Z5, "application/x-zmachine" },
+ { ContentType.Z6, "application/x-zmachine" },
+ { ContentType.Z7, "application/x-zmachine" },
+ { ContentType.Z8, "application/x-zmachine" },
+ { ContentType.Zaz, "application/vnd.zzazz.deck+xml" },
+ { ContentType.Zip, "application/zip" },
+ { ContentType.Zir, "application/vnd.zul" },
+ { ContentType.Zirz, "application/vnd.zul" },
+ { ContentType.Zmm, "application/vnd.handheld-entertainment+xml" },
+ };
+ private static readonly IReadOnlyDictionary<string, ContentType> ExtensionToCt = new Dictionary<string, ContentType>()
+ {
+ { "aab", ContentType.Aab },
+ { "aac", ContentType.Aac },
+ { "aam", ContentType.Aam },
+ { "aas", ContentType.Aas },
+ { "abw", ContentType.Abw },
+ { "ac", ContentType.Ac },
+ { "acc", ContentType.Acc },
+ { "ace", ContentType.Ace },
+ { "acu", ContentType.Acu },
+ { "acutc", ContentType.Acutc },
+ { "adp", ContentType.Adp },
+ { "aep", ContentType.Aep },
+ { "afm", ContentType.Afm },
+ { "afp", ContentType.Afp },
+ { "ahead", ContentType.Ahead },
+ { "ai", ContentType.Ai },
+ { "aif", ContentType.Aif },
+ { "aifc", ContentType.Aifc },
+ { "aiff", ContentType.Aiff },
+ { "air", ContentType.Air },
+ { "ait", ContentType.Ait },
+ { "ami", ContentType.Ami },
+ { "amr", ContentType.Amr },
+ { "apk", ContentType.Apk },
+ { "apng", ContentType.Apng },
+ { "appcache", ContentType.Appcache },
+ { "apr", ContentType.Apr },
+ { "arc", ContentType.Arc },
+ { "arj", ContentType.Arj },
+ { "asc", ContentType.Asc },
+ { "asf", ContentType.Asf },
+ { "asm", ContentType.Asm },
+ { "aso", ContentType.Aso },
+ { "asx", ContentType.Asx },
+ { "atc", ContentType.Atc },
+ { "atom", ContentType.Atom },
+ { "atomcat", ContentType.Atomcat },
+ { "atomsvc", ContentType.Atomsvc },
+ { "atx", ContentType.Atx },
+ { "au", ContentType.Au },
+ { "avi", ContentType.Avi },
+ { "avif", ContentType.Avif },
+ { "aw", ContentType.Aw },
+ { "azf", ContentType.Azf },
+ { "azs", ContentType.Azs },
+ { "azv", ContentType.Azv },
+ { "azw", ContentType.Azw },
+ { "b16", ContentType.B16 },
+ { "bat", ContentType.Bat },
+ { "bcpio", ContentType.Bcpio },
+ { "bdf", ContentType.Bdf },
+ { "bdm", ContentType.Bdm },
+ { "bdoc", ContentType.Bdoc },
+ { "bed", ContentType.Bed },
+ { "bh2", ContentType.Bh2 },
+ { "bin", ContentType.Binary },
+ { "blb", ContentType.Blb },
+ { "blorb", ContentType.Blorb },
+ { "bmi", ContentType.Bmi },
+ { "bmml", ContentType.Bmml },
+ { "bmp", ContentType.Bmp },
+ { "book", ContentType.Book },
+ { "box", ContentType.Box },
+ { "boz", ContentType.Boz },
+ { "bpk", ContentType.Bpk },
+ { "bsp", ContentType.Bsp },
+ { "btif", ContentType.Btif },
+ { "buffer", ContentType.Buffer },
+ { "bz", ContentType.Bz },
+ { "bz2", ContentType.Bz2 },
+ { "c", ContentType.C },
+ { "c11amc", ContentType.C11amc },
+ { "c11amz", ContentType.C11amz },
+ { "c4d", ContentType.C4d },
+ { "c4f", ContentType.C4f },
+ { "c4g", ContentType.C4g },
+ { "c4p", ContentType.C4p },
+ { "c4u", ContentType.C4u },
+ { "cab", ContentType.Cab },
+ { "caf", ContentType.Caf },
+ { "cap", ContentType.Cap },
+ { "car", ContentType.Car },
+ { "cat", ContentType.Cat },
+ { "cb7", ContentType.Cb7 },
+ { "cba", ContentType.Cba },
+ { "cbr", ContentType.Cbr },
+ { "cbt", ContentType.Cbt },
+ { "cbz", ContentType.Cbz },
+ { "cc", ContentType.Cc },
+ { "cco", ContentType.Cco },
+ { "cct", ContentType.Cct },
+ { "ccxml", ContentType.Ccxml },
+ { "cdbcmsg", ContentType.Cdbcmsg },
+ { "cdf", ContentType.Cdf },
+ { "cdfx", ContentType.Cdfx },
+ { "cdkey", ContentType.Cdkey },
+ { "cdmia", ContentType.Cdmia },
+ { "cdmic", ContentType.Cdmic },
+ { "cdmid", ContentType.Cdmid },
+ { "cdmio", ContentType.Cdmio },
+ { "cdmiq", ContentType.Cdmiq },
+ { "cdx", ContentType.Cdx },
+ { "cdxml", ContentType.Cdxml },
+ { "cdy", ContentType.Cdy },
+ { "cer", ContentType.Cer },
+ { "cfs", ContentType.Cfs },
+ { "cgm", ContentType.Cgm },
+ { "chat", ContentType.Chat },
+ { "chm", ContentType.Chm },
+ { "chrt", ContentType.Chrt },
+ { "cif", ContentType.Cif },
+ { "cii", ContentType.Cii },
+ { "cil", ContentType.Cil },
+ { "cjs", ContentType.Cjs },
+ { "cla", ContentType.Cla },
+ { "clkk", ContentType.Clkk },
+ { "clkp", ContentType.Clkp },
+ { "clkt", ContentType.Clkt },
+ { "clkw", ContentType.Clkw },
+ { "clkx", ContentType.Clkx },
+ { "clp", ContentType.Clp },
+ { "cmc", ContentType.Cmc },
+ { "cmdf", ContentType.Cmdf },
+ { "cml", ContentType.Cml },
+ { "cmp", ContentType.Cmp },
+ { "cmx", ContentType.Cmx },
+ { "cod", ContentType.Cod },
+ { "coffee", ContentType.Coffee },
+ { "com", ContentType.Com },
+ { "conf", ContentType.Conf },
+ { "cpio", ContentType.Cpio },
+ { "cpp", ContentType.Cpp },
+ { "cpt", ContentType.Cpt },
+ { "crd", ContentType.Crd },
+ { "crl", ContentType.Crl },
+ { "crt", ContentType.Crt },
+ { "crx", ContentType.Crx },
+ { "csh", ContentType.Csh },
+ { "csl", ContentType.Csl },
+ { "csml", ContentType.Csml },
+ { "csp", ContentType.Csp },
+ { "css", ContentType.Css },
+ { "cst", ContentType.Cst },
+ { "csv", ContentType.Csv },
+ { "cu", ContentType.Cu },
+ { "curl", ContentType.Curl },
+ { "cww", ContentType.Cww },
+ { "cxt", ContentType.Cxt },
+ { "cxx", ContentType.Cxx },
+ { "dae", ContentType.Dae },
+ { "daf", ContentType.Daf },
+ { "dart", ContentType.Dart },
+ { "dataless", ContentType.Dataless },
+ { "davmount", ContentType.Davmount },
+ { "dbf", ContentType.Dbf },
+ { "dbk", ContentType.Dbk },
+ { "dcr", ContentType.Dcr },
+ { "dcurl", ContentType.Dcurl },
+ { "dd2", ContentType.Dd2 },
+ { "ddd", ContentType.Ddd },
+ { "ddf", ContentType.Ddf },
+ { "dds", ContentType.Dds },
+ { "deb", ContentType.Deb },
+ { "def", ContentType.Def },
+ { "deploy", ContentType.Deploy },
+ { "der", ContentType.Der },
+ { "dfac", ContentType.Dfac },
+ { "dgc", ContentType.Dgc },
+ { "dic", ContentType.Dic },
+ { "dir", ContentType.Dir },
+ { "dis", ContentType.Dis },
+ { "dist", ContentType.Dist },
+ { "distz", ContentType.Distz },
+ { "djv", ContentType.Djv },
+ { "djvu", ContentType.Djvu },
+ { "dll", ContentType.Dll },
+ { "dmg", ContentType.Dmg },
+ { "dmp", ContentType.Dmp },
+ { "dms", ContentType.Dms },
+ { "dna", ContentType.Dna },
+ { "doc", ContentType.Doc },
+ { "docm", ContentType.Docm },
+ { "docx", ContentType.Docx },
+ { "dot", ContentType.Dot },
+ { "dotm", ContentType.Dotm },
+ { "dotx", ContentType.Dotx },
+ { "dp", ContentType.Dp },
+ { "dpg", ContentType.Dpg },
+ { "dra", ContentType.Dra },
+ { "drle", ContentType.Drle },
+ { "dsc", ContentType.Dsc },
+ { "dssc", ContentType.Dssc },
+ { "dtb", ContentType.Dtb },
+ { "dtd", ContentType.Dtd },
+ { "dts", ContentType.Dts },
+ { "dtshd", ContentType.Dtshd },
+ { "dump", ContentType.Dump },
+ { "dvb", ContentType.Dvb },
+ { "dvi", ContentType.Dvi },
+ { "dwd", ContentType.Dwd },
+ { "dwf", ContentType.Dwf },
+ { "dwg", ContentType.Dwg },
+ { "dxf", ContentType.Dxf },
+ { "dxp", ContentType.Dxp },
+ { "dxr", ContentType.Dxr },
+ { "ear", ContentType.Ear },
+ { "ecma", ContentType.Ecma },
+ { "edm", ContentType.Edm },
+ { "edx", ContentType.Edx },
+ { "efif", ContentType.Efif },
+ { "ei6", ContentType.Ei6 },
+ { "elc", ContentType.Elc },
+ { "emf", ContentType.Emf },
+ { "eml", ContentType.Eml },
+ { "emma", ContentType.Emma },
+ { "emz", ContentType.Emz },
+ { "eol", ContentType.Eol },
+ { "eot", ContentType.Eot },
+ { "eps", ContentType.Eps },
+ { "epub", ContentType.Epub },
+ { "es", ContentType.Es },
+ { "es3", ContentType.Es3 },
+ { "esa", ContentType.Esa },
+ { "esf", ContentType.Esf },
+ { "et3", ContentType.Et3 },
+ { "etx", ContentType.Etx },
+ { "eva", ContentType.Eva },
+ { "evy", ContentType.Evy },
+ { "exe", ContentType.Exe },
+ { "exi", ContentType.Exi },
+ { "exp", ContentType.Exp },
+ { "exr", ContentType.Exr },
+ { "ext", ContentType.Ext },
+ { "ez", ContentType.Ez },
+ { "ez2", ContentType.Ez2 },
+ { "ez3", ContentType.Ez3 },
+ { "f", ContentType.F },
+ { "f4v", ContentType.F4v },
+ { "f77", ContentType.Fortran },
+ { "f90", ContentType.F90 },
+ { "fbs", ContentType.Fbs },
+ { "fcdt", ContentType.Fcdt },
+ { "fcs", ContentType.Fcs },
+ { "fdf", ContentType.Fdf },
+ { "fdt", ContentType.Fdt },
+ { "fg5", ContentType.Fg5 },
+ { "fgd", ContentType.Fgd },
+ { "fh", ContentType.Fh },
+ { "fh4", ContentType.Fh4 },
+ { "fh5", ContentType.Fh5 },
+ { "fh7", ContentType.Fh7 },
+ { "fhc", ContentType.Fhc },
+ { "fig", ContentType.Fig },
+ { "fits", ContentType.Fits },
+ { "flac", ContentType.Flac },
+ { "fli", ContentType.Fli },
+ { "flo", ContentType.Flo },
+ { "flv", ContentType.Flv },
+ { "flw", ContentType.Flw },
+ { "flx", ContentType.Flx },
+ { "fly", ContentType.Fly },
+ { "fm", ContentType.Fm },
+ { "fnc", ContentType.Fnc },
+ { "fo", ContentType.Fo },
+ { "for", ContentType.For },
+ { "fpx", ContentType.Fpx },
+ { "frame", ContentType.Frame },
+ { "fsc", ContentType.Fsc },
+ { "fst", ContentType.Fst },
+ { "ftc", ContentType.Ftc },
+ { "fti", ContentType.Fti },
+ { "fvt", ContentType.Fvt },
+ { "fxp", ContentType.Fxp },
+ { "fxpl", ContentType.Fxpl },
+ { "fzs", ContentType.Fzs },
+ { "g2w", ContentType.G2w },
+ { "g3", ContentType.G3 },
+ { "g3w", ContentType.G3w },
+ { "gac", ContentType.Gac },
+ { "gam", ContentType.Gam },
+ { "gbr", ContentType.Gbr },
+ { "gca", ContentType.Gca },
+ { "gdl", ContentType.Gdl },
+ { "gdoc", ContentType.Gdoc },
+ { "geo", ContentType.Geo },
+ { "geojson", ContentType.Geojson },
+ { "gex", ContentType.Gex },
+ { "ggb", ContentType.Ggb },
+ { "ggt", ContentType.Ggt },
+ { "ghf", ContentType.Ghf },
+ { "gif", ContentType.Gif },
+ { "gim", ContentType.Gim },
+ { "glb", ContentType.Glb },
+ { "gltf", ContentType.Gltf },
+ { "gml", ContentType.Gml },
+ { "gmx", ContentType.Gmx },
+ { "gnumeric", ContentType.Gnumeric },
+ { "gph", ContentType.Gph },
+ { "gpx", ContentType.Gpx },
+ { "gqf", ContentType.Gqf },
+ { "gqs", ContentType.Gqs },
+ { "gram", ContentType.Gram },
+ { "gramps", ContentType.Gramps },
+ { "gre", ContentType.Gre },
+ { "grv", ContentType.Grv },
+ { "grxml", ContentType.Grxml },
+ { "gsf", ContentType.Gsf },
+ { "gsheet", ContentType.Gsheet },
+ { "gslides", ContentType.Gslides },
+ { "gtar", ContentType.Gtar },
+ { "gtm", ContentType.Gtm },
+ { "gtw", ContentType.Gtw },
+ { "gv", ContentType.Gv },
+ { "gxf", ContentType.Gxf },
+ { "gxt", ContentType.Gxt },
+ { "gz", ContentType.Gz },
+ { "h", ContentType.H },
+ { "h261", ContentType.H261 },
+ { "h263", ContentType.H263 },
+ { "h264", ContentType.H264 },
+ { "hal", ContentType.Hal },
+ { "hbci", ContentType.Hbci },
+ { "hbs", ContentType.Hbs },
+ { "hdd", ContentType.Hdd },
+ { "hdf", ContentType.Hdf },
+ { "heic", ContentType.Heic },
+ { "heics", ContentType.Heics },
+ { "heif", ContentType.Heif },
+ { "heifs", ContentType.Heifs },
+ { "hej2", ContentType.Hej2 },
+ { "held", ContentType.Held },
+ { "hh", ContentType.Hh },
+ { "hjson", ContentType.Hjson },
+ { "hlp", ContentType.Hlp },
+ { "hpgl", ContentType.Hpgl },
+ { "hpid", ContentType.Hpid },
+ { "hps", ContentType.Hps },
+ { "hqx", ContentType.Hqx },
+ { "hsj2", ContentType.Hsj2 },
+ { "htc", ContentType.Htc },
+ { "htke", ContentType.Htke },
+ { "htm", ContentType.Htm },
+ { "html", ContentType.Html },
+ { "hvd", ContentType.Hvd },
+ { "hvp", ContentType.Hvp },
+ { "hex", ContentType.Binary },
+ { "hvs", ContentType.Hvs },
+ { "i2g", ContentType.I2g },
+ { "icc", ContentType.Icc },
+ { "ice", ContentType.Ice },
+ { "icm", ContentType.Icm },
+ { "ico", ContentType.Ico },
+ { "ics", ContentType.Ics },
+ { "ief", ContentType.Ief },
+ { "ifb", ContentType.Ifb },
+ { "ifm", ContentType.Ifm },
+ { "iges", ContentType.Iges },
+ { "igl", ContentType.Igl },
+ { "igm", ContentType.Igm },
+ { "igs", ContentType.Igs },
+ { "igx", ContentType.Igx },
+ { "iif", ContentType.Iif },
+ { "img", ContentType.Img },
+ { "imp", ContentType.Imp },
+ { "ims", ContentType.Ims },
+ { "ini", ContentType.Ini },
+ { "ink", ContentType.Ink },
+ { "inkml", ContentType.Inkml },
+ { "install", ContentType.Install },
+ { "iota", ContentType.Iota },
+ { "ipfix", ContentType.Ipfix },
+ { "ipk", ContentType.Ipk },
+ { "irm", ContentType.Irm },
+ { "irp", ContentType.Irp },
+ { "iso", ContentType.Iso },
+ { "itp", ContentType.Itp },
+ { "its", ContentType.Its },
+ { "ivp", ContentType.Ivp },
+ { "ivu", ContentType.Ivu },
+ { "jad", ContentType.Jad },
+ { "jade", ContentType.Jade },
+ { "jam", ContentType.Jam },
+ { "jar", ContentType.Jar },
+ { "jardiff", ContentType.Jardiff },
+ { "java", ContentType.Java },
+ { "jhc", ContentType.Jhc },
+ { "jisp", ContentType.Jisp },
+ { "jls", ContentType.Jls },
+ { "jlt", ContentType.Jlt },
+ { "jng", ContentType.Jng },
+ { "jnlp", ContentType.Jnlp },
+ { "joda", ContentType.Joda },
+ { "jp2", ContentType.Jp2 },
+ { "jpe", ContentType.Jpe },
+ { "jpeg", ContentType.Jpeg },
+ { "jpf", ContentType.Jpf },
+ { "jpg", ContentType.Jpg },
+ { "jpg2", ContentType.Jpg2 },
+ { "jpgm", ContentType.Jpgm },
+ { "jpgv", ContentType.Jpgv },
+ { "jph", ContentType.Jph },
+ { "jpm", ContentType.Jpm },
+ { "jpx", ContentType.Jpx },
+ { "js", ContentType.Javascript },
+ { "json", ContentType.Json },
+ { "json5", ContentType.Json5 },
+ { "jsonld", ContentType.Jsonld },
+ { "jsonml", ContentType.Jsonml },
+ { "jsx", ContentType.Jsx },
+ { "jxr", ContentType.Jxr },
+ { "jxra", ContentType.Jxra },
+ { "jxrs", ContentType.Jxrs },
+ { "jxs", ContentType.Jxs },
+ { "jxsc", ContentType.Jxsc },
+ { "jxsi", ContentType.Jxsi },
+ { "jxss", ContentType.Jxss },
+ { "kar", ContentType.Kar },
+ { "karbon", ContentType.Karbon },
+ { "kdbx", ContentType.Kdbx },
+ { "key", ContentType.Key },
+ { "kfo", ContentType.Kfo },
+ { "kia", ContentType.Kia },
+ { "kml", ContentType.Kml },
+ { "kmz", ContentType.Kmz },
+ { "kne", ContentType.Kne },
+ { "knp", ContentType.Knp },
+ { "kon", ContentType.Kon },
+ { "kpr", ContentType.Kpr },
+ { "kpt", ContentType.Kpt },
+ { "kpxx", ContentType.Kpxx },
+ { "ksp", ContentType.Ksp },
+ { "ktr", ContentType.Ktr },
+ { "ktx", ContentType.Ktx },
+ { "ktx2", ContentType.Ktx2 },
+ { "ktz", ContentType.Ktz },
+ { "kwd", ContentType.Kwd },
+ { "kwt", ContentType.Kwt },
+ { "lasxml", ContentType.Lasxml },
+ { "latex", ContentType.Latex },
+ { "lbd", ContentType.Lbd },
+ { "lbe", ContentType.Lbe },
+ { "les", ContentType.Les },
+ { "less", ContentType.Less },
+ { "lgr", ContentType.Lgr },
+ { "lha", ContentType.Lha },
+ { "link66", ContentType.Link66 },
+ { "list", ContentType.List },
+ { "list3820", ContentType.List3820 },
+ { "listafp", ContentType.Listafp },
+ { "lnk", ContentType.Lnk },
+ { "log", ContentType.Log },
+ { "lostxml", ContentType.Lostxml },
+ { "lrf", ContentType.Lrf },
+ { "lrm", ContentType.Lrm },
+ { "ltf", ContentType.Ltf },
+ { "lua", ContentType.Lua },
+ { "luac", ContentType.Luac },
+ { "lvp", ContentType.Lvp },
+ { "lwp", ContentType.Lwp },
+ { "lzh", ContentType.Lzh },
+ { "m13", ContentType.M13 },
+ { "m14", ContentType.M14 },
+ { "m1v", ContentType.M1v },
+ { "m21", ContentType.M21 },
+ { "m2a", ContentType.M2a },
+ { "m2v", ContentType.M2v },
+ { "m3a", ContentType.M3a },
+ { "m3u", ContentType.M3u },
+ { "m3u8", ContentType.M3u8 },
+ { "m4a", ContentType.M4a },
+ { "m4p", ContentType.M4p },
+ { "m4s", ContentType.M4s },
+ { "m4u", ContentType.M4u },
+ { "m4v", ContentType.M4v },
+ { "ma", ContentType.Ma },
+ { "mads", ContentType.Mads },
+ { "maei", ContentType.Maei },
+ { "mag", ContentType.Mag },
+ { "maker", ContentType.Maker },
+ { "man", ContentType.Man },
+ { "manifest", ContentType.Manifest },
+ { "map", ContentType.Map },
+ { "mar", ContentType.Mar },
+ { "markdown", ContentType.Markdown },
+ { "mathml", ContentType.Mathml },
+ { "mb", ContentType.Mb },
+ { "mbk", ContentType.Mbk },
+ { "mbox", ContentType.Mbox },
+ { "mc1", ContentType.Mc1 },
+ { "mcd", ContentType.Mcd },
+ { "mcurl", ContentType.Mcurl },
+ { "md", ContentType.Md },
+ { "mdb", ContentType.Mdb },
+ { "mdi", ContentType.Mdi },
+ { "mdx", ContentType.Mdx },
+ { "me", ContentType.Me },
+ { "mesh", ContentType.Mesh },
+ { "meta4", ContentType.Meta4 },
+ { "metalink", ContentType.Metalink },
+ { "mets", ContentType.Mets },
+ { "mfm", ContentType.Mfm },
+ { "mft", ContentType.Mft },
+ { "mgp", ContentType.Mgp },
+ { "mgz", ContentType.Mgz },
+ { "mid", ContentType.Mid },
+ { "midi", ContentType.Midi },
+ { "mie", ContentType.Mie },
+ { "mif", ContentType.Mif },
+ { "mime", ContentType.Mime },
+ { "mj2", ContentType.Mj2 },
+ { "mjp2", ContentType.Mjp2 },
+ { "mjs", ContentType.Mjs },
+ { "mk3d", ContentType.Mk3d },
+ { "mka", ContentType.Mka },
+ { "mkd", ContentType.Mkd },
+ { "mks", ContentType.Mks },
+ { "mkv", ContentType.Mkv },
+ { "mlp", ContentType.Mlp },
+ { "mmd", ContentType.Mmd },
+ { "mmf", ContentType.Mmf },
+ { "mml", ContentType.Mml },
+ { "mmr", ContentType.Mmr },
+ { "mng", ContentType.Mng },
+ { "mny", ContentType.Mny },
+ { "mobi", ContentType.Mobi },
+ { "mods", ContentType.Mods },
+ { "mov", ContentType.Mov },
+ { "movie", ContentType.Movie },
+ { "mp2", ContentType.Mp2 },
+ { "mp21", ContentType.Mp21 },
+ { "mp2a", ContentType.Mp2a },
+ { "mp3", ContentType.Mp3 },
+ { "mp4", ContentType.Mp4 },
+ { "mp4a", ContentType.Mp4a },
+ { "mp4s", ContentType.Mp4s },
+ { "mp4v", ContentType.Mp4v },
+ { "mpc", ContentType.Mpc },
+ { "mpd", ContentType.Mpd },
+ { "mpe", ContentType.Mpe },
+ { "mpeg", ContentType.Mpeg },
+ { "mpg", ContentType.Mpg },
+ { "mpg4", ContentType.Mpg4 },
+ { "mpga", ContentType.Mpga },
+ { "mpkg", ContentType.Mpkg },
+ { "mpm", ContentType.Mpm },
+ { "mpn", ContentType.Mpn },
+ { "mpp", ContentType.Mpp },
+ { "mpt", ContentType.Mpt },
+ { "mpy", ContentType.Mpy },
+ { "mqy", ContentType.Mqy },
+ { "mrc", ContentType.Mrc },
+ { "mrcx", ContentType.Mrcx },
+ { "ms", ContentType.Ms },
+ { "mscml", ContentType.Mscml },
+ { "mseed", ContentType.Mseed },
+ { "mseq", ContentType.Mseq },
+ { "msf", ContentType.Msf },
+ { "msg", ContentType.Msg },
+ { "msh", ContentType.Msh },
+ { "msi", ContentType.Msi },
+ { "msl", ContentType.Msl },
+ { "msm", ContentType.Msm },
+ { "msp", ContentType.Msp },
+ { "msty", ContentType.Msty },
+ { "mtl", ContentType.Mtl },
+ { "mts", ContentType.Mts },
+ { "mus", ContentType.Mus },
+ { "musd", ContentType.Musd },
+ { "musicxml", ContentType.Musicxml },
+ { "mvb", ContentType.Mvb },
+ { "mvt", ContentType.Mvt },
+ { "mwf", ContentType.Mwf },
+ { "mxf", ContentType.Mxf },
+ { "mxl", ContentType.Mxl },
+ { "mxmf", ContentType.Mxmf },
+ { "mxml", ContentType.Mxml },
+ { "mxs", ContentType.Mxs },
+ { "mxu", ContentType.Mxu },
+ { "n3", ContentType.N3 },
+ { "nb", ContentType.Nb },
+ { "nbp", ContentType.Nbp },
+ { "nc", ContentType.Nc },
+ { "ncx", ContentType.Ncx },
+ { "nfo", ContentType.Nfo },
+ { "ngdat", ContentType.Ngdat },
+ { "nitf", ContentType.Nitf },
+ { "nlu", ContentType.Nlu },
+ { "nml", ContentType.Nml },
+ { "nnd", ContentType.Nnd },
+ { "nns", ContentType.Nns },
+ { "nnw", ContentType.Nnw },
+ { "npx", ContentType.Npx },
+ { "nq", ContentType.Nq },
+ { "nsc", ContentType.Nsc },
+ { "nsf", ContentType.Nsf },
+ { "nt", ContentType.Nt },
+ { "ntf", ContentType.Ntf },
+ { "numbers", ContentType.Numbers },
+ { "nzb", ContentType.Nzb },
+ { "oa2", ContentType.Oa2 },
+ { "oa3", ContentType.Oa3 },
+ { "oas", ContentType.Oas },
+ { "obd", ContentType.Obd },
+ { "obgx", ContentType.Obgx },
+ { "obj", ContentType.Obj },
+ { "oda", ContentType.Oda },
+ { "odb", ContentType.Odb },
+ { "odc", ContentType.Odc },
+ { "odf", ContentType.Odf },
+ { "odft", ContentType.Odft },
+ { "odg", ContentType.Odg },
+ { "odi", ContentType.Odi },
+ { "odm", ContentType.Odm },
+ { "odp", ContentType.Odp },
+ { "ods", ContentType.Ods },
+ { "odt", ContentType.Odt },
+ { "oga", ContentType.Oga },
+ { "ogex", ContentType.Ogex },
+ { "ogg", ContentType.Ogg },
+ { "ogv", ContentType.Ogv },
+ { "ogx", ContentType.Ogx },
+ { "omdoc", ContentType.Omdoc },
+ { "onepkg", ContentType.Onepkg },
+ { "onetmp", ContentType.Onetmp },
+ { "onetoc", ContentType.Onetoc },
+ { "onetoc2", ContentType.Onetoc2 },
+ { "opf", ContentType.Opf },
+ { "opml", ContentType.Opml },
+ { "oprc", ContentType.Oprc },
+ { "opus", ContentType.Opus },
+ { "org", ContentType.Org },
+ { "osf", ContentType.Osf },
+ { "osfpvg", ContentType.Osfpvg },
+ { "osm", ContentType.Osm },
+ { "otc", ContentType.Otc },
+ { "otf", ContentType.Otf },
+ { "otg", ContentType.Otg },
+ { "oth", ContentType.Oth },
+ { "oti", ContentType.Oti },
+ { "otp", ContentType.Otp },
+ { "ots", ContentType.Ots },
+ { "ott", ContentType.Ott },
+ { "ova", ContentType.Ova },
+ { "ovf", ContentType.Ovf },
+ { "owl", ContentType.Owl },
+ { "oxps", ContentType.Oxps },
+ { "oxt", ContentType.Oxt },
+ { "p", ContentType.P },
+ { "p10", ContentType.P10 },
+ { "p12", ContentType.P12 },
+ { "p7b", ContentType.P7b },
+ { "p7c", ContentType.P7c },
+ { "p7m", ContentType.P7m },
+ { "p7r", ContentType.P7r },
+ { "p7s", ContentType.P7s },
+ { "p8", ContentType.P8 },
+ { "pac", ContentType.Pac },
+ { "pages", ContentType.Pages },
+ { "pas", ContentType.Pas },
+ { "paw", ContentType.Paw },
+ { "pbd", ContentType.Pbd },
+ { "pbm", ContentType.Pbm },
+ { "pcap", ContentType.Pcap },
+ { "pcf", ContentType.Pcf },
+ { "pcl", ContentType.Pcl },
+ { "pclxl", ContentType.Pclxl },
+ { "pct", ContentType.Pct },
+ { "pcurl", ContentType.Pcurl },
+ { "pcx", ContentType.Pcx },
+ { "pdb", ContentType.Pdb },
+ { "pde", ContentType.Pde },
+ { "pdf", ContentType.Pdf },
+ { "pem", ContentType.Pem },
+ { "pfa", ContentType.Pfa },
+ { "pfb", ContentType.Pfb },
+ { "pfm", ContentType.Pfm },
+ { "pfr", ContentType.Pfr },
+ { "pfx", ContentType.Pfx },
+ { "pgm", ContentType.Pgm },
+ { "pgn", ContentType.Pgn },
+ { "pgp", ContentType.Pgp },
+ { "php", ContentType.Php },
+ { "pic", ContentType.Pic },
+ { "pkg", ContentType.Pkg },
+ { "pki", ContentType.Pki },
+ { "pkipath", ContentType.Pkipath },
+ { "pkpass", ContentType.Pkpass },
+ { "pl", ContentType.Pl },
+ { "plb", ContentType.Plb },
+ { "plc", ContentType.Plc },
+ { "plf", ContentType.Plf },
+ { "pls", ContentType.Pls },
+ { "pm", ContentType.Pm },
+ { "pml", ContentType.Pml },
+ { "png", ContentType.Png },
+ { "pnm", ContentType.Pnm },
+ { "portpkg", ContentType.Portpkg },
+ { "pot", ContentType.Pot },
+ { "potm", ContentType.Potm },
+ { "potx", ContentType.Potx },
+ { "ppam", ContentType.Ppam },
+ { "ppd", ContentType.Ppd },
+ { "ppm", ContentType.Ppm },
+ { "pps", ContentType.Pps },
+ { "ppsm", ContentType.Ppsm },
+ { "ppsx", ContentType.Ppsx },
+ { "ppt", ContentType.Ppt },
+ { "pptm", ContentType.Pptm },
+ { "pptx", ContentType.Pptx },
+ { "pqa", ContentType.Pqa },
+ { "prc", ContentType.Prc },
+ { "pre", ContentType.Pre },
+ { "prf", ContentType.Prf },
+ { "provx", ContentType.Provx },
+ { "ps", ContentType.Ps },
+ { "psb", ContentType.Psb },
+ { "psd", ContentType.Psd },
+ { "psf", ContentType.Psf },
+ { "pskcxml", ContentType.Pskcxml },
+ { "pti", ContentType.Pti },
+ { "ptid", ContentType.Ptid },
+ { "pub", ContentType.Pub },
+ { "pvb", ContentType.Pvb },
+ { "pwn", ContentType.Pwn },
+ { "pya", ContentType.Pya },
+ { "pyv", ContentType.Pyv },
+ { "qam", ContentType.Qam },
+ { "qbo", ContentType.Qbo },
+ { "qfx", ContentType.Qfx },
+ { "qps", ContentType.Qps },
+ { "qt", ContentType.Qt },
+ { "qwd", ContentType.Qwd },
+ { "qwt", ContentType.Qwt },
+ { "qxb", ContentType.Qxb },
+ { "qxd", ContentType.Qxd },
+ { "qxl", ContentType.Qxl },
+ { "qxt", ContentType.Qxt },
+ { "ra", ContentType.Ra },
+ { "ram", ContentType.Ram },
+ { "raml", ContentType.Raml },
+ { "rapd", ContentType.Rapd },
+ { "rar", ContentType.Rar },
+ { "ras", ContentType.Ras },
+ { "rdf", ContentType.Rdf },
+ { "rdz", ContentType.Rdz },
+ { "relo", ContentType.Relo },
+ { "rep", ContentType.Rep },
+ { "res", ContentType.Res },
+ { "rgb", ContentType.Rgb },
+ { "rif", ContentType.Rif },
+ { "rip", ContentType.Rip },
+ { "ris", ContentType.Ris },
+ { "rl", ContentType.Rl },
+ { "rlc", ContentType.Rlc },
+ { "rld", ContentType.Rld },
+ { "rm", ContentType.Rm },
+ { "rmi", ContentType.Rmi },
+ { "rmp", ContentType.Rmp },
+ { "rms", ContentType.Rms },
+ { "rmvb", ContentType.Rmvb },
+ { "rnc", ContentType.Rnc },
+ { "rng", ContentType.Rng },
+ { "roa", ContentType.Roa },
+ { "roff", ContentType.Roff },
+ { "rp9", ContentType.Rp9 },
+ { "rpm", ContentType.Rpm },
+ { "rpss", ContentType.Rpss },
+ { "rpst", ContentType.Rpst },
+ { "rq", ContentType.Rq },
+ { "rs", ContentType.Rs },
+ { "rsat", ContentType.Rsat },
+ { "rsd", ContentType.Rsd },
+ { "rsheet", ContentType.Rsheet },
+ { "rss", ContentType.Rss },
+ { "rtf", ContentType.Rtf },
+ { "rtx", ContentType.Rtx },
+ { "run", ContentType.Run },
+ { "rusd", ContentType.Rusd },
+ { "s", ContentType.S },
+ { "s3m", ContentType.S3m },
+ { "saf", ContentType.Saf },
+ { "sass", ContentType.Sass },
+ { "sbml", ContentType.Sbml },
+ { "sc", ContentType.Sc },
+ { "scd", ContentType.Scd },
+ { "scm", ContentType.Scm },
+ { "scq", ContentType.Scq },
+ { "scs", ContentType.Scs },
+ { "scss", ContentType.Scss },
+ { "scurl", ContentType.Scurl },
+ { "sda", ContentType.Sda },
+ { "sdc", ContentType.Sdc },
+ { "sdd", ContentType.Sdd },
+ { "sdkd", ContentType.Sdkd },
+ { "sdkm", ContentType.Sdkm },
+ { "sdp", ContentType.Sdp },
+ { "sdw", ContentType.Sdw },
+ { "sea", ContentType.Sea },
+ { "see", ContentType.See },
+ { "seed", ContentType.Seed },
+ { "sema", ContentType.Sema },
+ { "semd", ContentType.Semd },
+ { "semf", ContentType.Semf },
+ { "senmlx", ContentType.Senmlx },
+ { "sensmlx", ContentType.Sensmlx },
+ { "ser", ContentType.Ser },
+ { "setpay", ContentType.Setpay },
+ { "setreg", ContentType.Setreg },
+ { "sfs", ContentType.Sfs },
+ { "sfv", ContentType.Sfv },
+ { "sgi", ContentType.Sgi },
+ { "sgl", ContentType.Sgl },
+ { "sgm", ContentType.Sgm },
+ { "sgml", ContentType.Sgml },
+ { "sh", ContentType.Sh },
+ { "shar", ContentType.Shar },
+ { "shex", ContentType.Shex },
+ { "shf", ContentType.Shf },
+ { "shtml", ContentType.Shtml },
+ { "sid", ContentType.Sid },
+ { "sieve", ContentType.Sieve },
+ { "sig", ContentType.Sig },
+ { "sil", ContentType.Sil },
+ { "silo", ContentType.Silo },
+ { "sis", ContentType.Sis },
+ { "sisx", ContentType.Sisx },
+ { "sit", ContentType.Sit },
+ { "sitx", ContentType.Sitx },
+ { "siv", ContentType.Siv },
+ { "skd", ContentType.Skd },
+ { "skm", ContentType.Skm },
+ { "skp", ContentType.Skp },
+ { "skt", ContentType.Skt },
+ { "sldm", ContentType.Sldm },
+ { "sldx", ContentType.Sldx },
+ { "slim", ContentType.Slim },
+ { "slm", ContentType.Slm },
+ { "sls", ContentType.Sls },
+ { "slt", ContentType.Slt },
+ { "sm", ContentType.Sm },
+ { "smf", ContentType.Smf },
+ { "smi", ContentType.Smi },
+ { "smil", ContentType.Smil },
+ { "smv", ContentType.Smv },
+ { "smzip", ContentType.Smzip },
+ { "snd", ContentType.Snd },
+ { "snf", ContentType.Snf },
+ { "so", ContentType.So },
+ { "spc", ContentType.Spc },
+ { "spdx", ContentType.Spdx },
+ { "spf", ContentType.Spf },
+ { "spl", ContentType.Spl },
+ { "spot", ContentType.Spot },
+ { "spp", ContentType.Spp },
+ { "spq", ContentType.Spq },
+ { "spx", ContentType.Spx },
+ { "sql", ContentType.Sql },
+ { "src", ContentType.Src },
+ { "srt", ContentType.Srt },
+ { "sru", ContentType.Sru },
+ { "srx", ContentType.Srx },
+ { "ssdl", ContentType.Ssdl },
+ { "sse", ContentType.Sse },
+ { "ssf", ContentType.Ssf },
+ { "ssml", ContentType.Ssml },
+ { "st", ContentType.St },
+ { "stc", ContentType.Stc },
+ { "std", ContentType.Std },
+ { "stf", ContentType.Stf },
+ { "sti", ContentType.Sti },
+ { "stk", ContentType.Stk },
+ { "stl", ContentType.Stl },
+ { "stpx", ContentType.Stpx },
+ { "stpxz", ContentType.Stpxz },
+ { "stpz", ContentType.Stpz },
+ { "str", ContentType.Str },
+ { "stw", ContentType.Stw },
+ { "styl", ContentType.Styl },
+ { "stylus", ContentType.Stylus },
+ { "sub", ContentType.Sub },
+ { "sus", ContentType.Sus },
+ { "susp", ContentType.Susp },
+ { "sv4cpio", ContentType.Sv4cpio },
+ { "sv4crc", ContentType.Sv4crc },
+ { "svc", ContentType.Svc },
+ { "svd", ContentType.Svd },
+ { "svg", ContentType.Svg },
+ { "svgz", ContentType.Svgz },
+ { "swa", ContentType.Swa },
+ { "swf", ContentType.Swf },
+ { "swi", ContentType.Swi },
+ { "swidtag", ContentType.Swidtag },
+ { "sxc", ContentType.Sxc },
+ { "sxd", ContentType.Sxd },
+ { "sxg", ContentType.Sxg },
+ { "sxi", ContentType.Sxi },
+ { "sxm", ContentType.Sxm },
+ { "sxw", ContentType.Sxw },
+ { "t", ContentType.T },
+ { "t3", ContentType.T3 },
+ { "t38", ContentType.T38 },
+ { "taglet", ContentType.Taglet },
+ { "tao", ContentType.Tao },
+ { "tap", ContentType.Tap },
+ { "tar", ContentType.Tar },
+ { "tcap", ContentType.Tcap },
+ { "tcl", ContentType.Tcl },
+ { "td", ContentType.Td },
+ { "teacher", ContentType.Teacher },
+ { "tei", ContentType.Tei },
+ { "tex", ContentType.Tex },
+ { "texi", ContentType.Texi },
+ { "texinfo", ContentType.Texinfo },
+ { "text", ContentType.Text },
+ { "tfi", ContentType.Tfi },
+ { "tfm", ContentType.Tfm },
+ { "tfx", ContentType.Tfx },
+ { "tga", ContentType.Tga },
+ { "thmx", ContentType.Thmx },
+ { "tif", ContentType.Tif },
+ { "tiff", ContentType.Tiff },
+ { "tk", ContentType.Tk },
+ { "tmo", ContentType.Tmo },
+ { "toml", ContentType.Toml },
+ { "torrent", ContentType.Torrent },
+ { "tpl", ContentType.Tpl },
+ { "tpt", ContentType.Tpt },
+ { "tr", ContentType.Tr },
+ { "tra", ContentType.Tra },
+ { "trig", ContentType.Trig },
+ { "trm", ContentType.Trm },
+ { "ts", ContentType.Ts },
+ { "tsd", ContentType.Tsd },
+ { "tsv", ContentType.Tsv },
+ { "ttc", ContentType.Ttc },
+ { "ttf", ContentType.Ttf },
+ { "ttl", ContentType.Ttl },
+ { "ttml", ContentType.Ttml },
+ { "twd", ContentType.Twd },
+ { "twds", ContentType.Twds },
+ { "txd", ContentType.Txd },
+ { "txf", ContentType.Txf },
+ { "txt", ContentType.Txt },
+ { "u32", ContentType.U32 },
+ { "u8dsn", ContentType.U8dsn },
+ { "u8hdr", ContentType.U8hdr },
+ { "u8mdn", ContentType.U8mdn },
+ { "u8msg", ContentType.U8msg },
+ { "ubj", ContentType.Ubj },
+ { "udeb", ContentType.Udeb },
+ { "ufd", ContentType.Ufd },
+ { "ufdl", ContentType.Ufdl },
+ { "ulx", ContentType.Ulx },
+ { "umj", ContentType.Umj },
+ { "unityweb", ContentType.Unityweb },
+ { "uoml", ContentType.Uoml },
+ { "uri", ContentType.Uri },
+ { "uris", ContentType.Uris },
+ { "urls", ContentType.Urls },
+ { "usdz", ContentType.Usdz },
+ { "ustar", ContentType.Ustar },
+ { "utz", ContentType.Utz },
+ { "uu", ContentType.Uu },
+ { "uva", ContentType.Uva },
+ { "uvd", ContentType.Uvd },
+ { "uvf", ContentType.Uvf },
+ { "uvg", ContentType.Uvg },
+ { "uvh", ContentType.Uvh },
+ { "uvi", ContentType.Uvi },
+ { "uvm", ContentType.Uvm },
+ { "uvp", ContentType.Uvp },
+ { "uvs", ContentType.Uvs },
+ { "uvt", ContentType.Uvt },
+ { "uvu", ContentType.Uvu },
+ { "uvv", ContentType.Uvv },
+ { "uvva", ContentType.Uvva },
+ { "uvvd", ContentType.Uvvd },
+ { "uvvf", ContentType.Uvvf },
+ { "uvvg", ContentType.Uvvg },
+ { "uvvh", ContentType.Uvvh },
+ { "uvvi", ContentType.Uvvi },
+ { "uvvm", ContentType.Uvvm },
+ { "uvvp", ContentType.Uvvp },
+ { "uvvs", ContentType.Uvvs },
+ { "uvvt", ContentType.Uvvt },
+ { "uvvu", ContentType.Uvvu },
+ { "uvvv", ContentType.Uvvv },
+ { "uvvx", ContentType.Uvvx },
+ { "uvvz", ContentType.Uvvz },
+ { "uvx", ContentType.Uvx },
+ { "uvz", ContentType.Uvz },
+ { "vbox", ContentType.Vbox },
+ { "vcard", ContentType.Vcard },
+ { "vcd", ContentType.Vcd },
+ { "vcf", ContentType.Vcf },
+ { "vcg", ContentType.Vcg },
+ { "vcs", ContentType.Vcs },
+ { "vcx", ContentType.Vcx },
+ { "vdi", ContentType.Vdi },
+ { "vds", ContentType.Vds },
+ { "vhd", ContentType.Vhd },
+ { "vis", ContentType.Vis },
+ { "viv", ContentType.Viv },
+ { "vmdk", ContentType.Vmdk },
+ { "vob", ContentType.Vob },
+ { "vor", ContentType.Vor },
+ { "vox", ContentType.Vox },
+ { "vrml", ContentType.Vrml },
+ { "vsd", ContentType.Vsd },
+ { "vsf", ContentType.Vsf },
+ { "vss", ContentType.Vss },
+ { "vst", ContentType.Vst },
+ { "vsw", ContentType.Vsw },
+ { "vtf", ContentType.Vtf },
+ { "vtt", ContentType.Vtt },
+ { "vtu", ContentType.Vtu },
+ { "vxml", ContentType.Vxml },
+ { "w3d", ContentType.W3d },
+ { "wad", ContentType.Wad },
+ { "wadl", ContentType.Wadl },
+ { "war", ContentType.War },
+ { "wasm", ContentType.Wasm },
+ { "wav", ContentType.Wav },
+ { "wax", ContentType.Wax },
+ { "wbmp", ContentType.Wbmp },
+ { "wbs", ContentType.Wbs },
+ { "wbxml", ContentType.Wbxml },
+ { "wcm", ContentType.Wcm },
+ { "wdb", ContentType.Wdb },
+ { "wdp", ContentType.Wdp },
+ { "weba", ContentType.Weba },
+ { "webapp", ContentType.Webapp },
+ { "webm", ContentType.Webm },
+ { "webp", ContentType.Webp },
+ { "wg", ContentType.Wg },
+ { "wgt", ContentType.Wgt },
+ { "wks", ContentType.Wks },
+ { "wm", ContentType.Wm },
+ { "wma", ContentType.Wma },
+ { "wmd", ContentType.Wmd },
+ { "wmf", ContentType.Wmf },
+ { "wml", ContentType.Wml },
+ { "wmlc", ContentType.Wmlc },
+ { "wmls", ContentType.Wmls },
+ { "wmlsc", ContentType.Wmlsc },
+ { "wmv", ContentType.Wmv },
+ { "wmx", ContentType.Wmx },
+ { "wmz", ContentType.Wmz },
+ { "woff", ContentType.Woff },
+ { "woff2", ContentType.Woff2 },
+ { "wpd", ContentType.Wpd },
+ { "wpl", ContentType.Wpl },
+ { "wps", ContentType.Wps },
+ { "wqd", ContentType.Wqd },
+ { "wri", ContentType.Wri },
+ { "wrl", ContentType.Wrl },
+ { "wsc", ContentType.Wsc },
+ { "wsdl", ContentType.Wsdl },
+ { "wspolicy", ContentType.Wspolicy },
+ { "wtb", ContentType.Wtb },
+ { "wvx", ContentType.Wvx },
+ { "x32", ContentType.X32 },
+ { "x3d", ContentType.X3d },
+ { "x3db", ContentType.X3db },
+ { "x3dbz", ContentType.X3dbz },
+ { "x3dv", ContentType.X3dv },
+ { "x3dvz", ContentType.X3dvz },
+ { "x3dz", ContentType.X3dz },
+ { "xaml", ContentType.Xaml },
+ { "xap", ContentType.Xap },
+ { "xar", ContentType.Xar },
+ { "xav", ContentType.Xav },
+ { "xbap", ContentType.Xbap },
+ { "xbd", ContentType.Xbd },
+ { "xbm", ContentType.Xbm },
+ { "xca", ContentType.Xca },
+ { "xcs", ContentType.Xcs },
+ { "xdf", ContentType.Xdf },
+ { "xdm", ContentType.Xdm },
+ { "xdp", ContentType.Xdp },
+ { "xdssc", ContentType.Xdssc },
+ { "xdw", ContentType.Xdw },
+ { "xel", ContentType.Xel },
+ { "xenc", ContentType.Xenc },
+ { "xer", ContentType.Xer },
+ { "xfdf", ContentType.Xfdf },
+ { "xfdl", ContentType.Xfdl },
+ { "xht", ContentType.Xht },
+ { "xhtml", ContentType.Xhtml },
+ { "xhvml", ContentType.Xhvml },
+ { "xif", ContentType.Xif },
+ { "xla", ContentType.Xla },
+ { "xlam", ContentType.Xlam },
+ { "xlc", ContentType.Xlc },
+ { "xlf", ContentType.Xlf },
+ { "xlm", ContentType.Xlm },
+ { "xls", ContentType.Xls },
+ { "xlsb", ContentType.Xlsb },
+ { "xlsm", ContentType.Xlsm },
+ { "xlsx", ContentType.Xlsx },
+ { "xlt", ContentType.Xlt },
+ { "xltm", ContentType.Xltm },
+ { "xltx", ContentType.Xltx },
+ { "xlw", ContentType.Xlw },
+ { "xm", ContentType.Xm },
+ { "xml", ContentType.Xml },
+ { "xns", ContentType.Xns },
+ { "xo", ContentType.Xo },
+ { "xop", ContentType.Xop },
+ { "xpi", ContentType.Xpi },
+ { "xpl", ContentType.Xpl },
+ { "xpm", ContentType.Xpm },
+ { "xpr", ContentType.Xpr },
+ { "xps", ContentType.Xps },
+ { "xpw", ContentType.Xpw },
+ { "xpx", ContentType.Xpx },
+ { "xsd", ContentType.Xsd },
+ { "xsl", ContentType.Xsl },
+ { "xslt", ContentType.Xslt },
+ { "xsm", ContentType.Xsm },
+ { "xspf", ContentType.Xspf },
+ { "xul", ContentType.Xul },
+ { "xvm", ContentType.Xvm },
+ { "xvml", ContentType.Xvml },
+ { "xwd", ContentType.Xwd },
+ { "xyz", ContentType.Xyz },
+ { "xz", ContentType.Xz },
+ { "yaml", ContentType.Yaml },
+ { "yang", ContentType.Yang },
+ { "yin", ContentType.Yin },
+ { "yml", ContentType.Yml },
+ { "ymp", ContentType.Ymp },
+ { "z1", ContentType.Z1 },
+ { "z2", ContentType.Z2 },
+ { "z3", ContentType.Z3 },
+ { "z4", ContentType.Z4 },
+ { "z5", ContentType.Z5 },
+ { "z6", ContentType.Z6 },
+ { "z7", ContentType.Z7 },
+ { "z8", ContentType.Z8 },
+ { "zaz", ContentType.Zaz },
+ { "zip", ContentType.Zip },
+ { "zir", ContentType.Zir },
+ { "zirz", ContentType.Zirz },
+ { "zmm", ContentType.Zmm },
+ };
+ private static readonly IReadOnlyDictionary<string, ContentType> MimeToCt = new Dictionary<string, ContentType>()
+ {
+ { "application/x-www-form-urlencoded", ContentType.UrlEncoded },
+ { "multipart/form-data", ContentType.MultiPart },
+ { "audio/x-aac", ContentType.Aac },
+ { "application/x-authorware-map", ContentType.Aam },
+ { "application/x-authorware-seg", ContentType.Aas },
+ { "application/x-abiword", ContentType.Abw },
+ { "application/pkix-attr-cert", ContentType.Ac },
+ { "application/vnd.americandynamics.acc", ContentType.Acc },
+ { "application/x-ace-compressed", ContentType.Ace },
+ { "application/vnd.acucobol", ContentType.Acu },
+ { "application/vnd.acucorp", ContentType.Acutc },
+ { "audio/adpcm", ContentType.Adp },
+ { "application/vnd.audiograph", ContentType.Aep },
+ { "application/vnd.ibm.modcap", ContentType.Afp },
+ { "application/vnd.ahead.space", ContentType.Ahead },
+ { "audio/x-aiff", ContentType.Aiff },
+ { "application/vnd.adobe.air-application-installer-package+zip", ContentType.Air },
+ { "application/vnd.dvb.ait", ContentType.Ait },
+ { "application/vnd.amiga.ami", ContentType.Ami },
+ { "audio/amr", ContentType.Amr },
+ { "application/vnd.android.package-archive", ContentType.Apk },
+ { "image/apng", ContentType.Apng },
+ { "application/vnd.lotus-approach", ContentType.Apr },
+ { "application/x-freearc", ContentType.Arc },
+ { "application/x-arj", ContentType.Arj },
+ { "video/x-ms-asf", ContentType.Asf },
+ { "text/x-asm", ContentType.Asm },
+ { "application/vnd.accpac.simply.aso", ContentType.Aso },
+ { "application/atom+xml", ContentType.Atom },
+ { "application/atomcat+xml", ContentType.Atomcat },
+ { "application/atomsvc+xml", ContentType.Atomsvc },
+ { "application/vnd.antix.game-component", ContentType.Atx },
+ { "audio/basic", ContentType.Au },
+ { "video/x-msvideo", ContentType.Avi },
+ { "image/avif", ContentType.Avif },
+ { "application/applixware", ContentType.Aw },
+ { "application/vnd.airzip.filesecure.azf", ContentType.Azf },
+ { "application/vnd.airzip.filesecure.azs", ContentType.Azs },
+ { "image/vnd.airzip.accelerator.azv", ContentType.Azv },
+ { "application/vnd.amazon.ebook", ContentType.Azw },
+ { "image/vnd.pco.b16", ContentType.B16 },
+ { "application/x-msdownload", ContentType.Bat },
+ { "application/x-bcpio", ContentType.Bcpio },
+ { "application/x-font-bdf", ContentType.Bdf },
+ { "application/vnd.syncml.dm+wbxml", ContentType.Bdm },
+ { "application/bdoc", ContentType.Bdoc },
+ { "application/vnd.realvnc.bed", ContentType.Bed },
+ { "application/vnd.fujitsu.oasysprs", ContentType.Bh2 },
+ { "application/octet-stream", ContentType.Binary },
+ { "application/x-blorb", ContentType.Blorb },
+ { "application/vnd.bmi", ContentType.Bmi },
+ { "application/vnd.balsamiq.bmml+xml", ContentType.Bmml },
+ { "image/bmp", ContentType.Bmp },
+ { "application/vnd.previewsystems.box", ContentType.Box },
+ { "application/x-bzip2", ContentType.Boz },
+ { "model/vnd.valve.source.compiled-map", ContentType.Bsp },
+ { "image/prs.btif", ContentType.Btif },
+ { "application/x-bzip", ContentType.Bz },
+ { "text/x-c", ContentType.C },
+ { "application/vnd.cluetrust.cartomobile-config", ContentType.C11amc },
+ { "application/vnd.cluetrust.cartomobile-config-pkg", ContentType.C11amz },
+ { "application/vnd.clonk.c4group", ContentType.C4d },
+ { "application/vnd.ms-cab-compressed", ContentType.Cab },
+ { "audio/x-caf", ContentType.Caf },
+ { "application/vnd.curl.car", ContentType.Car },
+ { "application/vnd.ms-pki.seccat", ContentType.Cat },
+ { "application/x-cbr", ContentType.Cb7 },
+ { "application/x-cocoa", ContentType.Cco },
+ { "application/ccxml+xml", ContentType.Ccxml },
+ { "application/vnd.contact.cmsg", ContentType.Cdbcmsg },
+ { "application/x-netcdf", ContentType.Cdf },
+ { "application/cdfx+xml", ContentType.Cdfx },
+ { "application/vnd.mediastation.cdkey", ContentType.Cdkey },
+ { "application/cdmi-capability", ContentType.Cdmia },
+ { "application/cdmi-container", ContentType.Cdmic },
+ { "application/cdmi-domain", ContentType.Cdmid },
+ { "application/cdmi-object", ContentType.Cdmio },
+ { "application/cdmi-queue", ContentType.Cdmiq },
+ { "chemical/x-cdx", ContentType.Cdx },
+ { "application/vnd.chemdraw+xml", ContentType.Cdxml },
+ { "application/vnd.cinderella", ContentType.Cdy },
+ { "application/pkix-cert", ContentType.Cer },
+ { "application/x-cfs-compressed", ContentType.Cfs },
+ { "image/cgm", ContentType.Cgm },
+ { "application/x-chat", ContentType.Chat },
+ { "application/vnd.ms-htmlhelp", ContentType.Chm },
+ { "application/vnd.kde.kchart", ContentType.Chrt },
+ { "chemical/x-cif", ContentType.Cif },
+ { "application/vnd.anser-web-certificate-issue-initiation", ContentType.Cii },
+ { "application/vnd.ms-artgalry", ContentType.Cil },
+ { "application/node", ContentType.Cjs },
+ { "application/vnd.claymore", ContentType.Cla },
+ { "application/vnd.crick.clicker.keyboard", ContentType.Clkk },
+ { "application/vnd.crick.clicker.palette", ContentType.Clkp },
+ { "application/vnd.crick.clicker.template", ContentType.Clkt },
+ { "application/vnd.crick.clicker.wordbank", ContentType.Clkw },
+ { "application/vnd.crick.clicker", ContentType.Clkx },
+ { "application/x-msclip", ContentType.Clp },
+ { "application/vnd.cosmocaller", ContentType.Cmc },
+ { "chemical/x-cmdf", ContentType.Cmdf },
+ { "chemical/x-cml", ContentType.Cml },
+ { "application/vnd.yellowriver-custom-menu", ContentType.Cmp },
+ { "image/x-cmx", ContentType.Cmx },
+ { "application/vnd.rim.cod", ContentType.Cod },
+ { "text/coffeescript", ContentType.Coffee },
+ { "application/x-cpio", ContentType.Cpio },
+ { "application/mac-compactpro", ContentType.Cpt },
+ { "application/x-mscardfile", ContentType.Crd },
+ { "application/pkix-crl", ContentType.Crl },
+ { "application/x-x509-ca-cert", ContentType.Crt },
+ { "application/x-chrome-extension", ContentType.Crx },
+ { "application/x-csh", ContentType.Csh },
+ { "application/vnd.citationstyles.style+xml", ContentType.Csl },
+ { "chemical/x-csml", ContentType.Csml },
+ { "application/vnd.commonspace", ContentType.Csp },
+ { "text/css", ContentType.Css },
+ { "text/csv", ContentType.Csv },
+ { "application/cu-seeme", ContentType.Cu },
+ { "text/vnd.curl", ContentType.Curl },
+ { "application/prs.cww", ContentType.Cww },
+ { "model/vnd.collada+xml", ContentType.Dae },
+ { "application/vnd.mobius.daf", ContentType.Daf },
+ { "application/vnd.dart", ContentType.Dart },
+ { "application/davmount+xml", ContentType.Davmount },
+ { "application/vnd.dbf", ContentType.Dbf },
+ { "application/docbook+xml", ContentType.Dbk },
+ { "application/x-director", ContentType.Dcr },
+ { "text/vnd.curl.dcurl", ContentType.Dcurl },
+ { "application/vnd.oma.dd2+xml", ContentType.Dd2 },
+ { "application/vnd.fujixerox.ddd", ContentType.Ddd },
+ { "application/vnd.syncml.dmddf+xml", ContentType.Ddf },
+ { "image/vnd.ms-dds", ContentType.Dds },
+ { "application/vnd.dreamfactory", ContentType.Dfac },
+ { "application/x-dgc-compressed", ContentType.Dgc },
+ { "application/vnd.mobius.dis", ContentType.Dis },
+ { "image/vnd.djvu", ContentType.Djvu },
+ { "application/vnd.dna", ContentType.Dna },
+ { "application/msword", ContentType.Doc },
+ { "application/vnd.ms-word.document.macroenabled.12", ContentType.Docm },
+ { "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ContentType.Docx },
+ { "application/vnd.openxmlformats-officedocument.wordprocessingml.template", ContentType.Dotx },
+ { "application/vnd.osgi.dp", ContentType.Dp },
+ { "application/vnd.dpgraph", ContentType.Dpg },
+ { "audio/vnd.dra", ContentType.Dra },
+ { "image/dicom-rle", ContentType.Drle },
+ { "text/prs.lines.tag", ContentType.Dsc },
+ { "application/dssc+der", ContentType.Dssc },
+ { "application/x-dtbook+xml", ContentType.Dtb },
+ { "application/xml-dtd", ContentType.Dtd },
+ { "audio/vnd.dts", ContentType.Dts },
+ { "audio/vnd.dts.hd", ContentType.Dtshd },
+ { "video/vnd.dvb.file", ContentType.Dvb },
+ { "application/x-dvi", ContentType.Dvi },
+ { "application/atsc-dwd+xml", ContentType.Dwd },
+ { "model/vnd.dwf", ContentType.Dwf },
+ { "image/vnd.dwg", ContentType.Dwg },
+ { "image/vnd.dxf", ContentType.Dxf },
+ { "application/vnd.spotfire.dxp", ContentType.Dxp },
+ { "application/ecmascript", ContentType.Ecma },
+ { "application/vnd.novadigm.edm", ContentType.Edm },
+ { "application/vnd.novadigm.edx", ContentType.Edx },
+ { "application/vnd.picsel", ContentType.Efif },
+ { "application/vnd.pg.osasli", ContentType.Ei6 },
+ { "application/emma+xml", ContentType.Emma },
+ { "audio/vnd.digital-winds", ContentType.Eol },
+ { "application/vnd.ms-fontobject", ContentType.Eot },
+ { "application/epub+zip", ContentType.Epub },
+ { "application/vnd.osgi.subsystem", ContentType.Esa },
+ { "application/vnd.epson.esf", ContentType.Esf },
+ { "application/vnd.eszigno3+xml", ContentType.Et3 },
+ { "text/x-setext", ContentType.Etx },
+ { "application/x-eva", ContentType.Eva },
+ { "application/x-envoy", ContentType.Evy },
+ { "application/exi", ContentType.Exi },
+ { "application/express", ContentType.Exp },
+ { "image/aces", ContentType.Exr },
+ { "application/vnd.novadigm.ext", ContentType.Ext },
+ { "application/andrew-inset", ContentType.Ez },
+ { "application/vnd.ezpix-album", ContentType.Ez2 },
+ { "application/vnd.ezpix-package", ContentType.Ez3 },
+ { "video/x-f4v", ContentType.F4v },
+ { "text/x-fortran", ContentType.Fortran },
+ { "image/vnd.fastbidsheet", ContentType.Fbs },
+ { "application/vnd.adobe.formscentral.fcdt", ContentType.Fcdt },
+ { "application/vnd.isac.fcs", ContentType.Fcs },
+ { "application/vnd.fdf", ContentType.Fdf },
+ { "application/fdt+xml", ContentType.Fdt },
+ { "application/vnd.fujitsu.oasysgp", ContentType.Fg5 },
+ { "image/x-freehand", ContentType.Fh },
+ { "application/x-xfig", ContentType.Fig },
+ { "image/fits", ContentType.Fits },
+ { "audio/x-flac", ContentType.Flac },
+ { "video/x-fli", ContentType.Fli },
+ { "application/vnd.micrografx.flo", ContentType.Flo },
+ { "video/x-flv", ContentType.Flv },
+ { "application/vnd.kde.kivio", ContentType.Flw },
+ { "text/vnd.fmi.flexstor", ContentType.Flx },
+ { "text/vnd.fly", ContentType.Fly },
+ { "application/vnd.frogans.fnc", ContentType.Fnc },
+ { "application/vnd.software602.filler.form+xml", ContentType.Fo },
+ { "image/vnd.fpx", ContentType.Fpx },
+ { "application/vnd.framemaker", ContentType.Frame },
+ { "application/vnd.fsc.weblaunch", ContentType.Fsc },
+ { "image/vnd.fst", ContentType.Fst },
+ { "application/vnd.fluxtime.clip", ContentType.Ftc },
+ { "application/vnd.anser-web-funds-transfer-initiation", ContentType.Fti },
+ { "video/vnd.fvt", ContentType.Fvt },
+ { "application/vnd.adobe.fxp", ContentType.Fxp },
+ { "application/vnd.fuzzysheet", ContentType.Fzs },
+ { "application/vnd.geoplan", ContentType.G2w },
+ { "image/g3fax", ContentType.G3 },
+ { "application/vnd.geospace", ContentType.G3w },
+ { "application/vnd.groove-account", ContentType.Gac },
+ { "application/x-tads", ContentType.Gam },
+ { "application/rpki-ghostbusters", ContentType.Gbr },
+ { "application/x-gca-compressed", ContentType.Gca },
+ { "model/vnd.gdl", ContentType.Gdl },
+ { "application/vnd.google-apps.document", ContentType.Gdoc },
+ { "application/vnd.dynageo", ContentType.Geo },
+ { "application/geo+json", ContentType.Geojson },
+ { "application/vnd.geometry-explorer", ContentType.Gex },
+ { "application/vnd.geogebra.file", ContentType.Ggb },
+ { "application/vnd.geogebra.tool", ContentType.Ggt },
+ { "application/vnd.groove-help", ContentType.Ghf },
+ { "image/gif", ContentType.Gif },
+ { "application/vnd.groove-identity-message", ContentType.Gim },
+ { "model/gltf-binary", ContentType.Glb },
+ { "model/gltf+json", ContentType.Gltf },
+ { "application/gml+xml", ContentType.Gml },
+ { "application/vnd.gmx", ContentType.Gmx },
+ { "application/x-gnumeric", ContentType.Gnumeric },
+ { "application/vnd.flographit", ContentType.Gph },
+ { "application/gpx+xml", ContentType.Gpx },
+ { "application/vnd.grafeq", ContentType.Gqf },
+ { "application/srgs", ContentType.Gram },
+ { "application/x-gramps-xml", ContentType.Gramps },
+ { "application/vnd.groove-injector", ContentType.Grv },
+ { "application/srgs+xml", ContentType.Grxml },
+ { "application/x-font-ghostscript", ContentType.Gsf },
+ { "application/vnd.google-apps.spreadsheet", ContentType.Gsheet },
+ { "application/vnd.google-apps.presentation", ContentType.Gslides },
+ { "application/x-gtar", ContentType.Gtar },
+ { "application/vnd.groove-tool-message", ContentType.Gtm },
+ { "model/vnd.gtw", ContentType.Gtw },
+ { "text/vnd.graphviz", ContentType.Gv },
+ { "application/gxf", ContentType.Gxf },
+ { "application/vnd.geonext", ContentType.Gxt },
+ { "application/gzip", ContentType.Gz },
+ { "video/h261", ContentType.H261 },
+ { "video/h263", ContentType.H263 },
+ { "video/h264", ContentType.H264 },
+ { "application/vnd.hal+xml", ContentType.Hal },
+ { "application/vnd.hbci", ContentType.Hbci },
+ { "text/x-handlebars-template", ContentType.Hbs },
+ { "application/x-virtualbox-hdd", ContentType.Hdd },
+ { "application/x-hdf", ContentType.Hdf },
+ { "image/heic", ContentType.Heic },
+ { "image/heic-sequence", ContentType.Heics },
+ { "image/heif", ContentType.Heif },
+ { "image/heif-sequence", ContentType.Heifs },
+ { "image/hej2k", ContentType.Hej2 },
+ { "application/atsc-held+xml", ContentType.Held },
+ { "application/hjson", ContentType.Hjson },
+ { "application/winhlp", ContentType.Hlp },
+ { "application/vnd.hp-hpgl", ContentType.Hpgl },
+ { "application/vnd.hp-hpid", ContentType.Hpid },
+ { "application/vnd.hp-hps", ContentType.Hps },
+ { "application/mac-binhex40", ContentType.Hqx },
+ { "image/hsj2", ContentType.Hsj2 },
+ { "application/vnd.kenameaapp", ContentType.Htke },
+ { "text/html", ContentType.Html },
+ { "application/vnd.yamaha.hv-dic", ContentType.Hvd },
+ { "application/vnd.yamaha.hv-voice", ContentType.Hvp },
+ { "application/vnd.yamaha.hv-script", ContentType.Hvs },
+ { "application/vnd.intergeo", ContentType.I2g },
+ { "application/vnd.iccprofile", ContentType.Icc },
+ { "x-conference/x-cooltalk", ContentType.Ice },
+ { "image/vnd.microsoft.icon", ContentType.Ico },
+ { "image/ief", ContentType.Ief },
+ { "application/vnd.shana.informed.formdata", ContentType.Ifm },
+ { "model/iges", ContentType.Iges },
+ { "application/vnd.igloader", ContentType.Igl },
+ { "application/vnd.insors.igm", ContentType.Igm },
+ { "application/vnd.micrografx.igx", ContentType.Igx },
+ { "application/vnd.shana.informed.interchange", ContentType.Iif },
+ { "application/vnd.accpac.simply.imp", ContentType.Imp },
+ { "application/vnd.ms-ims", ContentType.Ims },
+ { "application/inkml+xml", ContentType.Inkml },
+ { "application/x-install-instructions", ContentType.Install },
+ { "application/vnd.astraea-software.iota", ContentType.Iota },
+ { "application/ipfix", ContentType.Ipfix },
+ { "application/vnd.shana.informed.package", ContentType.Ipk },
+ { "application/vnd.ibm.rights-management", ContentType.Irm },
+ { "application/vnd.irepository.package+xml", ContentType.Irp },
+ { "application/vnd.shana.informed.formtemplate", ContentType.Itp },
+ { "application/its+xml", ContentType.Its },
+ { "application/vnd.immervision-ivp", ContentType.Ivp },
+ { "application/vnd.immervision-ivu", ContentType.Ivu },
+ { "text/vnd.sun.j2me.app-descriptor", ContentType.Jad },
+ { "text/jade", ContentType.Jade },
+ { "application/vnd.jam", ContentType.Jam },
+ { "application/java-archive", ContentType.Jar },
+ { "application/x-java-archive-diff", ContentType.Jardiff },
+ { "text/x-java-source", ContentType.Java },
+ { "image/jphc", ContentType.Jhc },
+ { "application/vnd.jisp", ContentType.Jisp },
+ { "image/jls", ContentType.Jls },
+ { "application/vnd.hp-jlyt", ContentType.Jlt },
+ { "image/x-jng", ContentType.Jng },
+ { "application/x-java-jnlp-file", ContentType.Jnlp },
+ { "application/vnd.joost.joda-archive", ContentType.Joda },
+ { "image/jp2", ContentType.Jp2 },
+ { "image/jpeg", ContentType.Jpeg },
+ { "video/jpm", ContentType.Jpgm },
+ { "video/jpeg", ContentType.Jpgv },
+ { "image/jph", ContentType.Jph },
+ { "image/jpm", ContentType.Jpm },
+ { "image/jpx", ContentType.Jpx },
+ { "application/javascript", ContentType.Javascript },
+ { "application/json", ContentType.Json },
+ { "application/json5", ContentType.Json5 },
+ { "application/ld+json", ContentType.Jsonld },
+ { "application/jsonml+json", ContentType.Jsonml },
+ { "text/jsx", ContentType.Jsx },
+ { "image/jxr", ContentType.Jxr },
+ { "image/jxra", ContentType.Jxra },
+ { "image/jxrs", ContentType.Jxrs },
+ { "image/jxs", ContentType.Jxs },
+ { "image/jxsc", ContentType.Jxsc },
+ { "image/jxsi", ContentType.Jxsi },
+ { "image/jxss", ContentType.Jxss },
+ { "application/vnd.kde.karbon", ContentType.Karbon },
+ { "application/x-keepass2", ContentType.Kdbx },
+ { "application/vnd.apple.keynote", ContentType.Key },
+ { "application/vnd.kde.kformula", ContentType.Kfo },
+ { "application/vnd.kidspiration", ContentType.Kia },
+ { "application/vnd.google-earth.kml+xml", ContentType.Kml },
+ { "application/vnd.google-earth.kmz", ContentType.Kmz },
+ { "application/vnd.kinar", ContentType.Kne },
+ { "application/vnd.kde.kontour", ContentType.Kon },
+ { "application/vnd.kde.kpresenter", ContentType.Kpr },
+ { "application/vnd.ds-keypoint", ContentType.Kpxx },
+ { "application/vnd.kde.kspread", ContentType.Ksp },
+ { "image/ktx", ContentType.Ktx },
+ { "image/ktx2", ContentType.Ktx2 },
+ { "application/vnd.kahootz", ContentType.Ktz },
+ { "application/vnd.kde.kword", ContentType.Kwd },
+ { "application/vnd.las.las+xml", ContentType.Lasxml },
+ { "application/x-latex", ContentType.Latex },
+ { "application/vnd.llamagraphics.life-balance.desktop", ContentType.Lbd },
+ { "application/vnd.llamagraphics.life-balance.exchange+xml", ContentType.Lbe },
+ { "application/vnd.hhe.lesson-player", ContentType.Les },
+ { "text/less", ContentType.Less },
+ { "application/lgr+xml", ContentType.Lgr },
+ { "application/vnd.route66.link66+xml", ContentType.Link66 },
+ { "application/x-ms-shortcut", ContentType.Lnk },
+ { "application/lost+xml", ContentType.Lostxml },
+ { "application/vnd.ms-lrm", ContentType.Lrm },
+ { "application/vnd.frogans.ltf", ContentType.Ltf },
+ { "text/x-lua", ContentType.Lua },
+ { "application/x-lua-bytecode", ContentType.Luac },
+ { "audio/vnd.lucent.voice", ContentType.Lvp },
+ { "application/vnd.lotus-wordpro", ContentType.Lwp },
+ { "application/x-lzh-compressed", ContentType.Lzh },
+ { "audio/mpeg", ContentType.M2a },
+ { "audio/x-mpegurl", ContentType.M3u },
+ { "application/vnd.apple.mpegurl", ContentType.M3u8 },
+ { "audio/mp4", ContentType.M4a },
+ { "video/iso.segment", ContentType.M4s },
+ { "video/vnd.mpegurl", ContentType.M4u },
+ { "video/x-m4v", ContentType.M4v },
+ { "application/mathematica", ContentType.Ma },
+ { "application/mads+xml", ContentType.Mads },
+ { "application/mmt-aei+xml", ContentType.Maei },
+ { "application/vnd.ecowin.chart", ContentType.Mag },
+ { "text/cache-manifest", ContentType.Manifest },
+ { "text/markdown", ContentType.Markdown },
+ { "application/mathml+xml", ContentType.Mathml },
+ { "application/vnd.mobius.mbk", ContentType.Mbk },
+ { "application/mbox", ContentType.Mbox },
+ { "application/vnd.medcalcdata", ContentType.Mc1 },
+ { "application/vnd.mcd", ContentType.Mcd },
+ { "text/vnd.curl.mcurl", ContentType.Mcurl },
+ { "application/x-msaccess", ContentType.Mdb },
+ { "image/vnd.ms-modi", ContentType.Mdi },
+ { "text/mdx", ContentType.Mdx },
+ { "model/mesh", ContentType.Mesh },
+ { "application/metalink4+xml", ContentType.Meta4 },
+ { "application/metalink+xml", ContentType.Metalink },
+ { "application/mets+xml", ContentType.Mets },
+ { "application/vnd.mfmp", ContentType.Mfm },
+ { "application/rpki-manifest", ContentType.Mft },
+ { "application/vnd.osgeo.mapguide.package", ContentType.Mgp },
+ { "application/vnd.proteus.magazine", ContentType.Mgz },
+ { "audio/midi", ContentType.Midi },
+ { "application/x-mie", ContentType.Mie },
+ { "application/vnd.mif", ContentType.Mif },
+ { "message/rfc822", ContentType.Mime },
+ { "video/mj2", ContentType.Mj2 },
+ { "audio/x-matroska", ContentType.Mka },
+ { "text/x-markdown", ContentType.Mkd },
+ { "video/x-matroska", ContentType.Mkv },
+ { "application/vnd.dolby.mlp", ContentType.Mlp },
+ { "application/vnd.chipnuts.karaoke-mmd", ContentType.Mmd },
+ { "application/vnd.smaf", ContentType.Mmf },
+ { "text/mathml", ContentType.Mml },
+ { "image/vnd.fujixerox.edmics-mmr", ContentType.Mmr },
+ { "video/x-mng", ContentType.Mng },
+ { "application/x-msmoney", ContentType.Mny },
+ { "application/mods+xml", ContentType.Mods },
+ { "video/x-sgi-movie", ContentType.Movie },
+ { "application/mp21", ContentType.Mp21 },
+ { "audio/mp3", ContentType.Mp3 },
+ { "video/mp4", ContentType.Mp4 },
+ { "application/mp4", ContentType.Mp4s },
+ { "application/vnd.mophun.certificate", ContentType.Mpc },
+ { "application/dash+xml", ContentType.Mpd },
+ { "video/mpeg", ContentType.Mpeg },
+ { "application/vnd.apple.installer+xml", ContentType.Mpkg },
+ { "application/vnd.blueice.multipass", ContentType.Mpm },
+ { "application/vnd.mophun.application", ContentType.Mpn },
+ { "application/vnd.ms-project", ContentType.Mpt },
+ { "application/vnd.ibm.minipay", ContentType.Mpy },
+ { "application/vnd.mobius.mqy", ContentType.Mqy },
+ { "application/marc", ContentType.Mrc },
+ { "application/marcxml+xml", ContentType.Mrcx },
+ { "application/mediaservercontrol+xml", ContentType.Mscml },
+ { "application/vnd.fdsn.mseed", ContentType.Mseed },
+ { "application/vnd.mseq", ContentType.Mseq },
+ { "application/vnd.epson.msf", ContentType.Msf },
+ { "application/vnd.ms-outlook", ContentType.Msg },
+ { "application/vnd.mobius.msl", ContentType.Msl },
+ { "application/vnd.muvee.style", ContentType.Msty },
+ { "model/mtl", ContentType.Mtl },
+ { "model/vnd.mts", ContentType.Mts },
+ { "application/vnd.musician", ContentType.Mus },
+ { "application/mmt-usd+xml", ContentType.Musd },
+ { "application/vnd.recordare.musicxml+xml", ContentType.Musicxml },
+ { "application/x-msmediaview", ContentType.Mvb },
+ { "application/vnd.mapbox-vector-tile", ContentType.Mvt },
+ { "application/vnd.mfer", ContentType.Mwf },
+ { "application/mxf", ContentType.Mxf },
+ { "application/vnd.recordare.musicxml", ContentType.Mxl },
+ { "audio/mobile-xmf", ContentType.Mxmf },
+ { "application/vnd.triscape.mxs", ContentType.Mxs },
+ { "text/n3", ContentType.N3 },
+ { "application/vnd.wolfram.player", ContentType.Nbp },
+ { "application/x-dtbncx+xml", ContentType.Ncx },
+ { "text/x-nfo", ContentType.Nfo },
+ { "application/vnd.nokia.n-gage.data", ContentType.Ngdat },
+ { "application/vnd.nitf", ContentType.Nitf },
+ { "application/vnd.neurolanguage.nlu", ContentType.Nlu },
+ { "application/vnd.enliven", ContentType.Nml },
+ { "application/vnd.noblenet-directory", ContentType.Nnd },
+ { "application/vnd.noblenet-sealer", ContentType.Nns },
+ { "application/vnd.noblenet-web", ContentType.Nnw },
+ { "image/vnd.net-fpx", ContentType.Npx },
+ { "application/n-quads", ContentType.Nq },
+ { "application/x-conference", ContentType.Nsc },
+ { "application/vnd.lotus-notes", ContentType.Nsf },
+ { "application/n-triples", ContentType.Nt },
+ { "application/vnd.apple.numbers", ContentType.Numbers },
+ { "application/x-nzb", ContentType.Nzb },
+ { "application/vnd.fujitsu.oasys2", ContentType.Oa2 },
+ { "application/vnd.fujitsu.oasys3", ContentType.Oa3 },
+ { "application/vnd.fujitsu.oasys", ContentType.Oas },
+ { "application/x-msbinder", ContentType.Obd },
+ { "application/vnd.openblox.game+xml", ContentType.Obgx },
+ { "application/x-tgif", ContentType.Obj },
+ { "application/oda", ContentType.Oda },
+ { "application/vnd.oasis.opendocument.database", ContentType.Odb },
+ { "application/vnd.oasis.opendocument.chart", ContentType.Odc },
+ { "application/vnd.oasis.opendocument.formula", ContentType.Odf },
+ { "application/vnd.oasis.opendocument.formula-template", ContentType.Odft },
+ { "application/vnd.oasis.opendocument.graphics", ContentType.Odg },
+ { "application/vnd.oasis.opendocument.image", ContentType.Odi },
+ { "application/vnd.oasis.opendocument.text-master", ContentType.Odm },
+ { "application/vnd.oasis.opendocument.presentation", ContentType.Odp },
+ { "application/vnd.oasis.opendocument.spreadsheet", ContentType.Ods },
+ { "application/vnd.oasis.opendocument.text", ContentType.Odt },
+ { "model/vnd.opengex", ContentType.Ogex },
+ { "audio/ogg", ContentType.Ogg },
+ { "video/ogg", ContentType.Ogv },
+ { "application/ogg", ContentType.Ogx },
+ { "application/omdoc+xml", ContentType.Omdoc },
+ { "application/onenote", ContentType.Onetoc },
+ { "application/oebps-package+xml", ContentType.Opf },
+ { "text/x-opml", ContentType.Opml },
+ { "application/vnd.lotus-organizer", ContentType.Org },
+ { "application/vnd.yamaha.openscoreformat", ContentType.Osf },
+ { "application/vnd.yamaha.openscoreformat.osfpvg+xml", ContentType.Osfpvg },
+ { "application/vnd.openstreetmap.data+xml", ContentType.Osm },
+ { "application/vnd.oasis.opendocument.chart-template", ContentType.Otc },
+ { "font/otf", ContentType.Otf },
+ { "application/vnd.oasis.opendocument.graphics-template", ContentType.Otg },
+ { "application/vnd.oasis.opendocument.text-web", ContentType.Oth },
+ { "application/vnd.oasis.opendocument.image-template", ContentType.Oti },
+ { "application/vnd.oasis.opendocument.presentation-template", ContentType.Otp },
+ { "application/vnd.oasis.opendocument.spreadsheet-template", ContentType.Ots },
+ { "application/vnd.oasis.opendocument.text-template", ContentType.Ott },
+ { "application/x-virtualbox-ova", ContentType.Ova },
+ { "application/x-virtualbox-ovf", ContentType.Ovf },
+ { "application/oxps", ContentType.Oxps },
+ { "application/vnd.openofficeorg.extension", ContentType.Oxt },
+ { "text/x-pascal", ContentType.P },
+ { "application/pkcs10", ContentType.P10 },
+ { "application/x-pkcs7-certificates", ContentType.P7b },
+ { "application/pkcs7-mime", ContentType.P7m },
+ { "application/x-pkcs7-certreqresp", ContentType.P7r },
+ { "application/pkcs7-signature", ContentType.P7s },
+ { "application/pkcs8", ContentType.P8 },
+ { "application/x-ns-proxy-autoconfig", ContentType.Pac },
+ { "application/vnd.apple.pages", ContentType.Pages },
+ { "application/vnd.pawaafile", ContentType.Paw },
+ { "application/vnd.powerbuilder6", ContentType.Pbd },
+ { "image/x-portable-bitmap", ContentType.Pbm },
+ { "application/vnd.tcpdump.pcap", ContentType.Pcap },
+ { "application/x-font-pcf", ContentType.Pcf },
+ { "application/vnd.hp-pcl", ContentType.Pcl },
+ { "application/vnd.hp-pclxl", ContentType.Pclxl },
+ { "image/x-pict", ContentType.Pct },
+ { "application/vnd.curl.pcurl", ContentType.Pcurl },
+ { "image/vnd.zbrush.pcx", ContentType.Pcx },
+ { "application/vnd.palm", ContentType.Pdb },
+ { "text/x-processing", ContentType.Pde },
+ { "application/pdf", ContentType.Pdf },
+ { "application/x-font-type1", ContentType.Pfa },
+ { "application/font-tdpfr", ContentType.Pfr },
+ { "application/x-pkcs12", ContentType.Pfx },
+ { "image/x-portable-graymap", ContentType.Pgm },
+ { "application/x-chess-pgn", ContentType.Pgn },
+ { "application/pgp-encrypted", ContentType.Pgp },
+ { "application/x-httpd-php", ContentType.Php },
+ { "application/pkixcmp", ContentType.Pki },
+ { "application/pkix-pkipath", ContentType.Pkipath },
+ { "application/vnd.apple.pkpass", ContentType.Pkpass },
+ { "application/x-perl", ContentType.Pl },
+ { "application/vnd.3gpp.pic-bw-large", ContentType.Plb },
+ { "application/vnd.mobius.plc", ContentType.Plc },
+ { "application/vnd.pocketlearn", ContentType.Plf },
+ { "application/pls+xml", ContentType.Pls },
+ { "application/vnd.ctc-posml", ContentType.Pml },
+ { "image/png", ContentType.Png },
+ { "image/x-portable-anymap", ContentType.Pnm },
+ { "application/vnd.macports.portpkg", ContentType.Portpkg },
+ { "application/vnd.ms-powerpoint.template.macroenabled.12", ContentType.Potm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.template", ContentType.Potx },
+ { "application/vnd.ms-powerpoint.addin.macroenabled.12", ContentType.Ppam },
+ { "application/vnd.cups-ppd", ContentType.Ppd },
+ { "image/x-portable-pixmap", ContentType.Ppm },
+ { "application/vnd.ms-powerpoint.slideshow.macroenabled.12", ContentType.Ppsm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.slideshow", ContentType.Ppsx },
+ { "application/vnd.ms-powerpoint", ContentType.Ppt },
+ { "application/vnd.ms-powerpoint.presentation.macroenabled.12", ContentType.Pptm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.presentation", ContentType.Pptx },
+ { "application/x-mobipocket-ebook", ContentType.Prc },
+ { "application/vnd.lotus-freelance", ContentType.Pre },
+ { "application/pics-rules", ContentType.Prf },
+ { "application/provenance+xml", ContentType.Provx },
+ { "application/postscript", ContentType.Ps },
+ { "application/vnd.3gpp.pic-bw-small", ContentType.Psb },
+ { "image/vnd.adobe.photoshop", ContentType.Psd },
+ { "application/x-font-linux-psf", ContentType.Psf },
+ { "application/pskc+xml", ContentType.Pskcxml },
+ { "image/prs.pti", ContentType.Pti },
+ { "application/vnd.pvi.ptid1", ContentType.Ptid },
+ { "application/x-mspublisher", ContentType.Pub },
+ { "application/vnd.3gpp.pic-bw-var", ContentType.Pvb },
+ { "application/vnd.3m.post-it-notes", ContentType.Pwn },
+ { "audio/vnd.ms-playready.media.pya", ContentType.Pya },
+ { "video/vnd.ms-playready.media.pyv", ContentType.Pyv },
+ { "application/vnd.epson.quickanime", ContentType.Qam },
+ { "application/vnd.intu.qbo", ContentType.Qbo },
+ { "application/vnd.intu.qfx", ContentType.Qfx },
+ { "application/vnd.publishare-delta-tree", ContentType.Qps },
+ { "video/quicktime", ContentType.Qt },
+ { "application/vnd.quark.quarkxpress", ContentType.Qwd },
+ { "audio/x-pn-realaudio", ContentType.Ra },
+ { "application/raml+yaml", ContentType.Raml },
+ { "application/route-apd+xml", ContentType.Rapd },
+ { "application/vnd.rar", ContentType.Rar },
+ { "image/x-cmu-raster", ContentType.Ras },
+ { "application/rdf+xml", ContentType.Rdf },
+ { "application/vnd.data-vision.rdz", ContentType.Rdz },
+ { "application/p2p-overlay+xml", ContentType.Relo },
+ { "application/vnd.businessobjects", ContentType.Rep },
+ { "application/x-dtbresource+xml", ContentType.Res },
+ { "image/x-rgb", ContentType.Rgb },
+ { "application/reginfo+xml", ContentType.Rif },
+ { "audio/vnd.rip", ContentType.Rip },
+ { "application/x-research-info-systems", ContentType.Ris },
+ { "application/resource-lists+xml", ContentType.Rl },
+ { "image/vnd.fujixerox.edmics-rlc", ContentType.Rlc },
+ { "application/resource-lists-diff+xml", ContentType.Rld },
+ { "application/vnd.rn-realmedia", ContentType.Rm },
+ { "audio/x-pn-realaudio-plugin", ContentType.Rmp },
+ { "application/vnd.jcp.javame.midlet-rms", ContentType.Rms },
+ { "application/vnd.rn-realmedia-vbr", ContentType.Rmvb },
+ { "application/relax-ng-compact-syntax", ContentType.Rnc },
+ { "application/rpki-roa", ContentType.Roa },
+ { "text/troff", ContentType.Roff },
+ { "application/vnd.cloanto.rp9", ContentType.Rp9 },
+ { "application/x-redhat-package-manager", ContentType.Rpm },
+ { "application/vnd.nokia.radio-presets", ContentType.Rpss },
+ { "application/vnd.nokia.radio-preset", ContentType.Rpst },
+ { "application/sparql-query", ContentType.Rq },
+ { "application/rls-services+xml", ContentType.Rs },
+ { "application/atsc-rsat+xml", ContentType.Rsat },
+ { "application/rsd+xml", ContentType.Rsd },
+ { "application/urc-ressheet+xml", ContentType.Rsheet },
+ { "application/rss+xml", ContentType.Rss },
+ { "application/rtf", ContentType.Rtf },
+ { "text/richtext", ContentType.Rtx },
+ { "application/x-makeself", ContentType.Run },
+ { "application/route-usd+xml", ContentType.Rusd },
+ { "audio/s3m", ContentType.S3m },
+ { "application/vnd.yamaha.smaf-audio", ContentType.Saf },
+ { "text/x-sass", ContentType.Sass },
+ { "application/sbml+xml", ContentType.Sbml },
+ { "application/vnd.ibm.secure-container", ContentType.Sc },
+ { "application/x-msschedule", ContentType.Scd },
+ { "application/vnd.lotus-screencam", ContentType.Scm },
+ { "application/scvp-cv-request", ContentType.Scq },
+ { "application/scvp-cv-response", ContentType.Scs },
+ { "text/x-scss", ContentType.Scss },
+ { "text/vnd.curl.scurl", ContentType.Scurl },
+ { "application/vnd.stardivision.draw", ContentType.Sda },
+ { "application/vnd.stardivision.calc", ContentType.Sdc },
+ { "application/vnd.stardivision.impress", ContentType.Sdd },
+ { "application/vnd.solent.sdkm+xml", ContentType.Sdkm },
+ { "application/sdp", ContentType.Sdp },
+ { "application/vnd.stardivision.writer", ContentType.Sdw },
+ { "application/x-sea", ContentType.Sea },
+ { "application/vnd.seemail", ContentType.See },
+ { "application/vnd.fdsn.seed", ContentType.Seed },
+ { "application/vnd.sema", ContentType.Sema },
+ { "application/vnd.semd", ContentType.Semd },
+ { "application/vnd.semf", ContentType.Semf },
+ { "application/senml+xml", ContentType.Senmlx },
+ { "application/sensml+xml", ContentType.Sensmlx },
+ { "application/java-serialized-object", ContentType.Ser },
+ { "application/set-payment-initiation", ContentType.Setpay },
+ { "application/set-registration-initiation", ContentType.Setreg },
+ { "application/vnd.spotfire.sfs", ContentType.Sfs },
+ { "text/x-sfv", ContentType.Sfv },
+ { "image/sgi", ContentType.Sgi },
+ { "application/vnd.stardivision.writer-global", ContentType.Sgl },
+ { "text/sgml", ContentType.Sgml },
+ { "application/x-sh", ContentType.Sh },
+ { "application/x-shar", ContentType.Shar },
+ { "text/shex", ContentType.Shex },
+ { "application/shf+xml", ContentType.Shf },
+ { "image/x-mrsid-image", ContentType.Sid },
+ { "application/sieve", ContentType.Sieve },
+ { "application/pgp-signature", ContentType.Sig },
+ { "audio/silk", ContentType.Sil },
+ { "application/vnd.symbian.install", ContentType.Sisx },
+ { "application/x-stuffit", ContentType.Sit },
+ { "application/x-stuffitx", ContentType.Sitx },
+ { "application/vnd.koan", ContentType.Skd },
+ { "application/vnd.ms-powerpoint.slide.macroenabled.12", ContentType.Sldm },
+ { "application/vnd.openxmlformats-officedocument.presentationml.slide", ContentType.Sldx },
+ { "text/slim", ContentType.Slim },
+ { "application/route-s-tsid+xml", ContentType.Sls },
+ { "application/vnd.epson.salt", ContentType.Slt },
+ { "application/vnd.stepmania.stepchart", ContentType.Sm },
+ { "application/vnd.stardivision.math", ContentType.Smf },
+ { "application/smil+xml", ContentType.Smil },
+ { "video/x-smv", ContentType.Smv },
+ { "application/vnd.stepmania.package", ContentType.Smzip },
+ { "application/x-font-snf", ContentType.Snf },
+ { "text/spdx", ContentType.Spdx },
+ { "application/vnd.yamaha.smaf-phrase", ContentType.Spf },
+ { "application/x-futuresplash", ContentType.Spl },
+ { "text/vnd.in3d.spot", ContentType.Spot },
+ { "application/scvp-vp-response", ContentType.Spp },
+ { "application/scvp-vp-request", ContentType.Spq },
+ { "application/x-sql", ContentType.Sql },
+ { "application/x-wais-source", ContentType.Src },
+ { "application/x-subrip", ContentType.Srt },
+ { "application/sru+xml", ContentType.Sru },
+ { "application/sparql-results+xml", ContentType.Srx },
+ { "application/ssdl+xml", ContentType.Ssdl },
+ { "application/vnd.kodak-descriptor", ContentType.Sse },
+ { "application/vnd.epson.ssf", ContentType.Ssf },
+ { "application/ssml+xml", ContentType.Ssml },
+ { "application/vnd.sailingtracker.track", ContentType.St },
+ { "application/vnd.sun.xml.calc.template", ContentType.Stc },
+ { "application/vnd.sun.xml.draw.template", ContentType.Std },
+ { "application/vnd.wt.stf", ContentType.Stf },
+ { "application/vnd.sun.xml.impress.template", ContentType.Sti },
+ { "application/hyperstudio", ContentType.Stk },
+ { "application/vnd.ms-pki.stl", ContentType.Stl },
+ { "model/step+xml", ContentType.Stpx },
+ { "model/step-xml+zip", ContentType.Stpxz },
+ { "model/step+zip", ContentType.Stpz },
+ { "application/vnd.pg.format", ContentType.Str },
+ { "application/vnd.sun.xml.writer.template", ContentType.Stw },
+ { "text/stylus", ContentType.Stylus },
+ { "image/vnd.dvb.subtitle", ContentType.Sub },
+ { "application/vnd.sus-calendar", ContentType.Sus },
+ { "application/x-sv4cpio", ContentType.Sv4cpio },
+ { "application/x-sv4crc", ContentType.Sv4crc },
+ { "application/vnd.dvb.service", ContentType.Svc },
+ { "application/vnd.svd", ContentType.Svd },
+ { "image/svg+xml", ContentType.Svg },
+ { "application/x-shockwave-flash", ContentType.Swf },
+ { "application/vnd.aristanetworks.swi", ContentType.Swi },
+ { "application/swid+xml", ContentType.Swidtag },
+ { "application/vnd.sun.xml.calc", ContentType.Sxc },
+ { "application/vnd.sun.xml.draw", ContentType.Sxd },
+ { "application/vnd.sun.xml.writer.global", ContentType.Sxg },
+ { "application/vnd.sun.xml.impress", ContentType.Sxi },
+ { "application/vnd.sun.xml.math", ContentType.Sxm },
+ { "application/vnd.sun.xml.writer", ContentType.Sxw },
+ { "application/x-t3vm-image", ContentType.T3 },
+ { "image/t38", ContentType.T38 },
+ { "application/vnd.mynfc", ContentType.Taglet },
+ { "application/vnd.tao.intent-module-archive", ContentType.Tao },
+ { "image/vnd.tencent.tap", ContentType.Tap },
+ { "application/x-tar", ContentType.Tar },
+ { "application/vnd.3gpp2.tcap", ContentType.Tcap },
+ { "application/x-tcl", ContentType.Tcl },
+ { "application/urc-targetdesc+xml", ContentType.Td },
+ { "application/vnd.smart.teacher", ContentType.Teacher },
+ { "application/tei+xml", ContentType.Tei },
+ { "application/x-tex", ContentType.Tex },
+ { "application/x-texinfo", ContentType.Texinfo },
+ { "text/plain", ContentType.Text },
+ { "application/thraud+xml", ContentType.Tfi },
+ { "application/x-tex-tfm", ContentType.Tfm },
+ { "image/tiff-fx", ContentType.Tfx },
+ { "image/x-tga", ContentType.Tga },
+ { "application/vnd.ms-officetheme", ContentType.Thmx },
+ { "image/tiff", ContentType.Tiff },
+ { "application/vnd.tmobile-livetv", ContentType.Tmo },
+ { "application/toml", ContentType.Toml },
+ { "application/x-bittorrent", ContentType.Torrent },
+ { "application/vnd.groove-tool-template", ContentType.Tpl },
+ { "application/vnd.trid.tpt", ContentType.Tpt },
+ { "application/vnd.trueapp", ContentType.Tra },
+ { "application/trig", ContentType.Trig },
+ { "application/x-msterminal", ContentType.Trm },
+ { "video/mp2t", ContentType.Ts },
+ { "application/timestamped-data", ContentType.Tsd },
+ { "text/tab-separated-values", ContentType.Tsv },
+ { "font/collection", ContentType.Ttc },
+ { "font/ttf", ContentType.Ttf },
+ { "text/turtle", ContentType.Ttl },
+ { "application/ttml+xml", ContentType.Ttml },
+ { "application/vnd.simtech-mindmapper", ContentType.Twd },
+ { "application/vnd.genomatix.tuxedo", ContentType.Txd },
+ { "application/vnd.mobius.txf", ContentType.Txf },
+ { "application/x-authorware-bin", ContentType.U32 },
+ { "message/global-delivery-status", ContentType.U8dsn },
+ { "message/global-headers", ContentType.U8hdr },
+ { "message/global-disposition-notification", ContentType.U8mdn },
+ { "message/global", ContentType.U8msg },
+ { "application/ubjson", ContentType.Ubj },
+ { "application/x-debian-package", ContentType.Udeb },
+ { "application/vnd.ufdl", ContentType.Ufdl },
+ { "application/x-glulx", ContentType.Ulx },
+ { "application/vnd.umajin", ContentType.Umj },
+ { "application/vnd.unity", ContentType.Unityweb },
+ { "application/vnd.uoml+xml", ContentType.Uoml },
+ { "text/uri-list", ContentType.Uri },
+ { "model/vnd.usdz+zip", ContentType.Usdz },
+ { "application/x-ustar", ContentType.Ustar },
+ { "application/vnd.uiq.theme", ContentType.Utz },
+ { "text/x-uuencode", ContentType.Uu },
+ { "audio/vnd.dece.audio", ContentType.Uva },
+ { "application/vnd.dece.data", ContentType.Uvd },
+ { "image/vnd.dece.graphic", ContentType.Uvg },
+ { "video/vnd.dece.hd", ContentType.Uvh },
+ { "video/vnd.dece.mobile", ContentType.Uvm },
+ { "video/vnd.dece.pd", ContentType.Uvp },
+ { "video/vnd.dece.sd", ContentType.Uvs },
+ { "application/vnd.dece.ttml+xml", ContentType.Uvt },
+ { "video/vnd.uvvu.mp4", ContentType.Uvu },
+ { "video/vnd.dece.video", ContentType.Uvv },
+ { "application/vnd.dece.zip", ContentType.Uvz },
+ { "application/x-virtualbox-vbox", ContentType.Vbox },
+ { "text/vcard", ContentType.Vcard },
+ { "application/x-cdlink", ContentType.Vcd },
+ { "text/x-vcard", ContentType.Vcf },
+ { "application/vnd.groove-vcard", ContentType.Vcg },
+ { "text/x-vcalendar", ContentType.Vcs },
+ { "application/vnd.vcx", ContentType.Vcx },
+ { "application/x-virtualbox-vdi", ContentType.Vdi },
+ { "model/vnd.sap.vds", ContentType.Vds },
+ { "application/x-virtualbox-vhd", ContentType.Vhd },
+ { "application/vnd.visionary", ContentType.Vis },
+ { "video/vnd.vivo", ContentType.Viv },
+ { "application/x-virtualbox-vmdk", ContentType.Vmdk },
+ { "video/x-ms-vob", ContentType.Vob },
+ { "model/vrml", ContentType.Vrml },
+ { "application/vnd.vsf", ContentType.Vsf },
+ { "application/vnd.visio", ContentType.Vss },
+ { "image/vnd.valve.source.texture", ContentType.Vtf },
+ { "text/vtt", ContentType.Vtt },
+ { "model/vnd.vtu", ContentType.Vtu },
+ { "application/voicexml+xml", ContentType.Vxml },
+ { "application/x-doom", ContentType.Wad },
+ { "application/vnd.sun.wadl+xml", ContentType.Wadl },
+ { "application/wasm", ContentType.Wasm },
+ { "audio/wav", ContentType.Wav },
+ { "audio/x-ms-wax", ContentType.Wax },
+ { "image/vnd.wap.wbmp", ContentType.Wbmp },
+ { "application/vnd.criticaltools.wbs+xml", ContentType.Wbs },
+ { "application/vnd.wap.wbxml", ContentType.Wbxml },
+ { "image/vnd.ms-photo", ContentType.Wdp },
+ { "audio/webm", ContentType.Weba },
+ { "application/x-web-app-manifest+json", ContentType.Webapp },
+ { "video/webm", ContentType.Webm },
+ { "image/webp", ContentType.Webp },
+ { "application/vnd.pmi.widget", ContentType.Wg },
+ { "application/widget", ContentType.Wgt },
+ { "application/vnd.ms-works", ContentType.Wks },
+ { "video/x-ms-wm", ContentType.Wm },
+ { "audio/x-ms-wma", ContentType.Wma },
+ { "application/x-ms-wmd", ContentType.Wmd },
+ { "application/x-msmetafile", ContentType.Wmf },
+ { "text/vnd.wap.wml", ContentType.Wml },
+ { "application/vnd.wap.wmlc", ContentType.Wmlc },
+ { "text/vnd.wap.wmlscript", ContentType.Wmls },
+ { "application/vnd.wap.wmlscriptc", ContentType.Wmlsc },
+ { "video/x-ms-wmv", ContentType.Wmv },
+ { "video/x-ms-wmx", ContentType.Wmx },
+ { "application/x-ms-wmz", ContentType.Wmz },
+ { "font/woff", ContentType.Woff },
+ { "font/woff2", ContentType.Woff2 },
+ { "application/vnd.wordperfect", ContentType.Wpd },
+ { "application/vnd.ms-wpl", ContentType.Wpl },
+ { "application/vnd.wqd", ContentType.Wqd },
+ { "application/x-mswrite", ContentType.Wri },
+ { "message/vnd.wfa.wsc", ContentType.Wsc },
+ { "application/wsdl+xml", ContentType.Wsdl },
+ { "application/wspolicy+xml", ContentType.Wspolicy },
+ { "application/vnd.webturbo", ContentType.Wtb },
+ { "video/x-ms-wvx", ContentType.Wvx },
+ { "model/x3d+xml", ContentType.X3d },
+ { "model/x3d+binary", ContentType.X3db },
+ { "model/x3d+vrml", ContentType.X3dv },
+ { "application/xaml+xml", ContentType.Xaml },
+ { "application/x-silverlight-app", ContentType.Xap },
+ { "application/vnd.xara", ContentType.Xar },
+ { "application/xcap-att+xml", ContentType.Xav },
+ { "application/x-ms-xbap", ContentType.Xbap },
+ { "application/vnd.fujixerox.docuworks.binder", ContentType.Xbd },
+ { "image/x-xbitmap", ContentType.Xbm },
+ { "application/xcap-caps+xml", ContentType.Xca },
+ { "application/calendar+xml", ContentType.Xcs },
+ { "application/xcap-diff+xml", ContentType.Xdf },
+ { "application/vnd.syncml.dm+xml", ContentType.Xdm },
+ { "application/vnd.adobe.xdp+xml", ContentType.Xdp },
+ { "application/dssc+xml", ContentType.Xdssc },
+ { "application/vnd.fujixerox.docuworks", ContentType.Xdw },
+ { "application/xcap-el+xml", ContentType.Xel },
+ { "application/xenc+xml", ContentType.Xenc },
+ { "application/patch-ops-error+xml", ContentType.Xer },
+ { "application/vnd.adobe.xfdf", ContentType.Xfdf },
+ { "application/vnd.xfdl", ContentType.Xfdl },
+ { "application/xhtml+xml", ContentType.Xhtml },
+ { "image/vnd.xiff", ContentType.Xif },
+ { "application/vnd.ms-excel.addin.macroenabled.12", ContentType.Xlam },
+ { "application/x-xliff+xml", ContentType.Xlf },
+ { "application/vnd.ms-excel", ContentType.Xls },
+ { "application/vnd.ms-excel.sheet.binary.macroenabled.12", ContentType.Xlsb },
+ { "application/vnd.ms-excel.sheet.macroenabled.12", ContentType.Xlsm },
+ { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ContentType.Xlsx },
+ { "application/vnd.ms-excel.template.macroenabled.12", ContentType.Xltm },
+ { "application/vnd.openxmlformats-officedocument.spreadsheetml.template", ContentType.Xltx },
+ { "audio/xm", ContentType.Xm },
+ { "application/xml", ContentType.Xml },
+ { "application/xcap-ns+xml", ContentType.Xns },
+ { "application/vnd.olpc-sugar", ContentType.Xo },
+ { "application/xop+xml", ContentType.Xop },
+ { "application/x-xpinstall", ContentType.Xpi },
+ { "application/xproc+xml", ContentType.Xpl },
+ { "image/x-xpixmap", ContentType.Xpm },
+ { "application/vnd.is-xpr", ContentType.Xpr },
+ { "application/vnd.ms-xpsdocument", ContentType.Xps },
+ { "application/vnd.intercon.formnet", ContentType.Xpw },
+ { "application/xslt+xml", ContentType.Xslt },
+ { "application/vnd.syncml+xml", ContentType.Xsm },
+ { "application/vnd.mozilla.xul+xml", ContentType.Xul },
+ { "application/xv+xml", ContentType.Xvml },
+ { "image/x-xwindowdump", ContentType.Xwd },
+ { "chemical/x-xyz", ContentType.Xyz },
+ { "application/x-xz", ContentType.Xz },
+ { "text/yaml", ContentType.Yaml },
+ { "application/yang", ContentType.Yang },
+ { "application/yin+xml", ContentType.Yin },
+ { "text/x-suse-ymp", ContentType.Ymp },
+ { "application/x-zmachine", ContentType.Z1 },
+ { "application/vnd.zzazz.deck+xml", ContentType.Zaz },
+ { "application/zip", ContentType.Zip },
+ { "application/vnd.zul", ContentType.Zir },
+ { "application/vnd.handheld-entertainment+xml", ContentType.Zmm },
+ };
+ }
+}
diff --git a/lib/Net.Http/src/Helpers/TransportReader.cs b/lib/Net.Http/src/Helpers/TransportReader.cs
new file mode 100644
index 0000000..a37bfe9
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/TransportReader.cs
@@ -0,0 +1,114 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TransportReader.cs
+*
+* TransportReader.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+
+
+namespace VNLib.Net.Http.Core
+{
+
+ /// <summary>
+ /// Structure implementation of <see cref="IVnTextReader"/>
+ /// </summary>
+ internal struct TransportReader : IVnTextReader
+ {
+ ///<inheritdoc/>
+ public readonly Encoding Encoding => _encoding;
+ ///<inheritdoc/>
+ public readonly ReadOnlyMemory<byte> LineTermination => _lineTermination;
+ ///<inheritdoc/>
+ public readonly Stream BaseStream => _transport;
+
+
+ private readonly SharedHeaderReaderBuffer BinBuffer;
+ private readonly Encoding _encoding;
+ private readonly Stream _transport;
+ private readonly ReadOnlyMemory<byte> _lineTermination;
+
+ private int BufWindowStart;
+ private int BufWindowEnd;
+
+ /// <summary>
+ /// Initializes a new <see cref="TransportReader"/> for reading text lines from the transport stream
+ /// </summary>
+ /// <param name="transport">The transport stream to read data from</param>
+ /// <param name="buffer">The shared binary buffer</param>
+ /// <param name="encoding">The encoding to use when reading bianry</param>
+ /// <param name="lineTermination">The line delimiter to search for</param>
+ public TransportReader(Stream transport, SharedHeaderReaderBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
+ {
+ BufWindowEnd = 0;
+ BufWindowStart = 0;
+ _encoding = encoding;
+ _transport = transport;
+ _lineTermination = lineTermination;
+ BinBuffer = buffer;
+ }
+
+ ///<inheritdoc/>
+ public readonly int Available => BufWindowEnd - BufWindowStart;
+
+ ///<inheritdoc/>
+ public readonly Span<byte> BufferedDataWindow => BinBuffer.BinBuffer[BufWindowStart..BufWindowEnd];
+
+
+ ///<inheritdoc/>
+ public void Advance(int count) => BufWindowStart += count;
+ ///<inheritdoc/>
+ public void FillBuffer()
+ {
+ //Get a buffer from the end of the current window to the end of the buffer
+ Span<byte> bufferWindow = BinBuffer.BinBuffer[BufWindowEnd..];
+ //Read from stream
+ int read = _transport.Read(bufferWindow);
+ //Update the end of the buffer window to the end of the read data
+ BufWindowEnd += read;
+ }
+ ///<inheritdoc/>
+ public ERRNO CompactBufferWindow()
+ {
+ //No data to compact if window is not shifted away from start
+ if (BufWindowStart > 0)
+ {
+ //Get span over engire buffer
+ Span<byte> buffer = BinBuffer.BinBuffer;
+ //Get data within window
+ Span<byte> usedData = buffer[BufWindowStart..BufWindowEnd];
+ //Copy remaining to the begining of the buffer
+ usedData.CopyTo(buffer);
+ //Buffer window start is 0
+ BufWindowStart = 0;
+ //Buffer window end is now the remaining size
+ BufWindowEnd = usedData.Length;
+ }
+ //Return the number of bytes of available space
+ return BinBuffer.BinLength - BufWindowEnd;
+ }
+ }
+}
diff --git a/lib/Net.Http/src/Helpers/VnWebHeaderCollection.cs b/lib/Net.Http/src/Helpers/VnWebHeaderCollection.cs
new file mode 100644
index 0000000..f67e7db
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/VnWebHeaderCollection.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: VnWebHeaderCollection.cs
+*
+* VnWebHeaderCollection.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Collections.Generic;
+
+
+namespace VNLib.Net.Http
+{
+ ///<inheritdoc/>
+ public sealed class VnWebHeaderCollection : WebHeaderCollection, IEnumerable<KeyValuePair<string?, string?>>
+ {
+ IEnumerator<KeyValuePair<string?, string?>> IEnumerable<KeyValuePair<string?, string?>>.GetEnumerator()
+ {
+ for (int i = 0; i < Keys.Count; i++)
+ {
+ yield return new(Keys[i], Get(i));
+ }
+ }
+ }
+}
diff --git a/lib/Net.Http/src/Helpers/WebHeaderExtensions.cs b/lib/Net.Http/src/Helpers/WebHeaderExtensions.cs
new file mode 100644
index 0000000..e51bdd5
--- /dev/null
+++ b/lib/Net.Http/src/Helpers/WebHeaderExtensions.cs
@@ -0,0 +1,60 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: WebHeaderExtensions.cs
+*
+* WebHeaderExtensions.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Net;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Extends the <see cref="WebHeaderCollection"/> to provide some check methods
+ /// </summary>
+ public static class WebHeaderExtensions
+ {
+ /// <summary>
+ /// Determines if the specified request header has been set in the current header collection
+ /// </summary>
+ /// <param name="headers"></param>
+ /// <param name="header">Header value to check</param>
+ /// <returns>true if the header was set, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HeaderSet(this WebHeaderCollection headers, HttpRequestHeader header) => !string.IsNullOrWhiteSpace(headers[header]);
+ /// <summary>
+ /// Determines if the specified response header has been set in the current header collection
+ /// </summary>
+ /// <param name="headers"></param>
+ /// <param name="header">Header value to check</param>
+ /// <returns>true if the header was set, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HeaderSet(this WebHeaderCollection headers, HttpResponseHeader header) => !string.IsNullOrWhiteSpace(headers[header]);
+ /// <summary>
+ /// Determines if the specified header has been set in the current header collection
+ /// </summary>
+ /// <param name="headers"></param>
+ /// <param name="header">Header value to check</param>
+ /// <returns>true if the header was set, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HeaderSet(this WebHeaderCollection headers, string header) => !string.IsNullOrWhiteSpace(headers[header]);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/HttpConfig.cs b/lib/Net.Http/src/HttpConfig.cs
new file mode 100644
index 0000000..8e73176
--- /dev/null
+++ b/lib/Net.Http/src/HttpConfig.cs
@@ -0,0 +1,154 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: HttpConfig.cs
+*
+* HttpConfig.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Text;
+using System.IO.Compression;
+
+using VNLib.Utils.Logging;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents configration variables used to create the instance and manage http connections
+ /// </summary>
+ public readonly struct HttpConfig
+ {
+ public HttpConfig(ILogProvider log)
+ {
+ ConnectionKeepAlive = TimeSpan.FromSeconds(100);
+ ServerLog = log;
+ }
+
+ /// <summary>
+ /// A log provider that all server related log entiries will be written to
+ /// </summary>
+ public readonly ILogProvider ServerLog { get; }
+ /// <summary>
+ /// The absolute request entity body size limit in bytes
+ /// </summary>
+ public readonly int MaxUploadSize { get; init; } = 5 * 1000 * 1024;
+ /// <summary>
+ /// The maximum size in bytes allowed for an MIME form-data content type upload
+ /// </summary>
+ /// <remarks>Set to 0 to disabled mulit-part/form-data uploads</remarks>
+ public readonly int MaxFormDataUploadSize { get; init; } = 40 * 1024;
+ /// <summary>
+ /// The maximum buffer size to use when parsing Multi-part/Form-data file uploads
+ /// </summary>
+ /// <remarks>
+ /// This value is used to create the buffer used to read data from the input stream
+ /// into memory for parsing. Form-data uploads must be parsed in memory because
+ /// the data is not delimited by a content length.
+ /// </remarks>
+ public readonly int FormDataBufferSize { get; init; } = 8192;
+ /// <summary>
+ /// The maximum response entity size in bytes for which the library will allow compresssing response data
+ /// </summary>
+ /// <remarks>Set this value to 0 to disable response compression</remarks>
+ public readonly int CompressionLimit { get; init; } = 1000 * 1024;
+ /// <summary>
+ /// The minimum size (in bytes) of respones data that will be compressed
+ /// </summary>
+ public readonly int CompressionMinimum { get; init; } = 4096;
+ /// <summary>
+ /// The maximum amount of time to listen for data from a connected, but inactive transport connection
+ /// before closing them
+ /// </summary>
+ public readonly TimeSpan ConnectionKeepAlive { get; init; }
+ /// <summary>
+ /// The encoding to use when sending and receiving HTTP data
+ /// </summary>
+ public readonly Encoding HttpEncoding { get; init; } = Encoding.UTF8;
+ /// <summary>
+ /// Sets the compression level for response entity streams of all supported types when
+ /// compression is used.
+ /// </summary>
+ public readonly CompressionLevel CompressionLevel { get; init; } = CompressionLevel.Optimal;
+ /// <summary>
+ /// Sets the default Http version for responses when the client version cannot be parsed from the request
+ /// </summary>
+ public readonly HttpVersion DefaultHttpVersion { get; init; } = HttpVersion.Http11;
+ /// <summary>
+ /// The buffer size used to read HTTP headers from the transport.
+ /// </summary>
+ /// <remarks>
+ /// Setting this value too low will result in header parsing failures
+ /// and 400 Bad Request responses. Setting it too high can result in
+ /// resource abuse or high memory usage. 8k is usually a good value.
+ /// </remarks>
+ public readonly int HeaderBufferSize { get; init; } = 8192;
+ /// <summary>
+ /// The amount of time (in milliseconds) to wait for data on a connection that is in a receive
+ /// state, aka active receive.
+ /// </summary>
+ public readonly int ActiveConnectionRecvTimeout { get; init; } = 5000;
+ /// <summary>
+ /// The amount of time (in milliseconds) to wait for data to be send to the client before
+ /// the connection is closed
+ /// </summary>
+ public readonly int SendTimeout { get; init; } = 5000;
+ /// <summary>
+ /// The maximum number of request headers allowed per request
+ /// </summary>
+ public readonly int MaxRequestHeaderCount { get; init; } = 100;
+ /// <summary>
+ /// The maximum number of open transport connections, before 503 errors
+ /// will be returned and new connections closed.
+ /// </summary>
+ /// <remarks>Set to 0 to disable request processing. Causes perminant 503 results</remarks>
+ public readonly int MaxOpenConnections { get; init; } = int.MaxValue;
+ /// <summary>
+ /// The size (in bytes) of the http response header accumulator buffer.
+ /// </summary>
+ /// <remarks>
+ /// Http responses use an internal accumulator to buffer all response headers
+ /// before writing them to the transport in on write operation. If this value
+ /// is too low, the response will fail to write. If it is too high, it
+ /// may cause resource exhaustion or high memory usage.
+ /// </remarks>
+ public readonly int ResponseHeaderBufferSize { get; init; } = 16 * 1024;
+ /// <summary>
+ /// The size (in bytes) of the buffer to use to discard unread request entity bodies
+ /// </summary>
+ public readonly int DiscardBufferSize { get; init; } = 64 * 1024;
+ /// <summary>
+ /// The size of the buffer to use when writing response data to the transport
+ /// </summary>
+ /// <remarks>
+ /// This value is the size of the buffer used to copy data from the response
+ /// entity stream, to the transport stream.
+ /// </remarks>
+ public readonly int ResponseBufferSize { get; init; } = 32 * 1024;
+ /// <summary>
+ /// The size of the buffer used to accumulate chunked response data before writing to the transport
+ /// </summary>
+ public readonly int ChunkedResponseAccumulatorSize { get; init; } = 64 * 1024;
+ /// <summary>
+ /// An <see cref="ILogProvider"/> for writing verbose request logs. Set to <c>null</c>
+ /// to disable verbose request logging
+ /// </summary>
+ public readonly ILogProvider? RequestDebugLog { get; init; } = null;
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/IAlternateProtocol.cs b/lib/Net.Http/src/IAlternateProtocol.cs
new file mode 100644
index 0000000..dc7072b
--- /dev/null
+++ b/lib/Net.Http/src/IAlternateProtocol.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IAlternateProtocol.cs
+*
+* IAlternateProtocol.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Allows implementation for a protocol swtich from HTTP to another protocol
+ /// </summary>
+ public interface IAlternateProtocol
+ {
+ /// <summary>
+ /// Initializes and executes the protocol-switch and the protocol handler
+ /// that is stored
+ /// </summary>
+ /// <param name="transport">The prepared transport stream for the new protocol</param>
+ /// <param name="handlerToken">A cancelation token that the caller may pass for operation cancelation and cleanup</param>
+ /// <returns>A task that will be awaited by the server, that when complete, will cleanup resources held by the connection</returns>
+ Task RunAsync(Stream transport, CancellationToken handlerToken);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/IConnectionInfo.cs b/lib/Net.Http/src/IConnectionInfo.cs
new file mode 100644
index 0000000..17ee16f
--- /dev/null
+++ b/lib/Net.Http/src/IConnectionInfo.cs
@@ -0,0 +1,150 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IConnectionInfo.cs
+*
+* IConnectionInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Text;
+using System.Collections.Generic;
+using System.Security.Authentication;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents a client's connection info as interpreted by the current server
+ /// </summary>
+ /// <remarks>Methods and properties are undefined when <see cref="IWebRoot.ClientConnectedAsync(IHttpEvent)"/> returns</remarks>
+ public interface IConnectionInfo
+ {
+ /// <summary>
+ /// Full request uri of current connection
+ /// </summary>
+ Uri RequestUri { get; }
+ /// <summary>
+ /// Current request path. Shortcut to <seealso cref="RequestUri"/> <see cref="Uri.LocalPath"/>
+ /// </summary>
+ string Path => RequestUri.LocalPath;
+ /// <summary>
+ /// Current connection's user-agent header, (may be null if no user-agent header found)
+ /// </summary>
+ string? UserAgent { get; }
+ /// <summary>
+ /// Current connection's headers
+ /// </summary>
+ IHeaderCollection Headers { get; }
+ /// <summary>
+ /// A value that indicates if the connection's origin header was set and it's
+ /// authority segment does not match the <see cref="RequestUri"/> authority
+ /// segment.
+ /// </summary>
+ bool CrossOrigin { get; }
+ /// <summary>
+ /// Is the current connecion a websocket request
+ /// </summary>
+ bool IsWebSocketRequest { get; }
+ /// <summary>
+ /// Request specified content-type
+ /// </summary>
+ ContentType ContentType { get; }
+ /// <summary>
+ /// Current request's method
+ /// </summary>
+ HttpMethod Method { get; }
+ /// <summary>
+ /// The current connection's HTTP protocol version
+ /// </summary>
+ HttpVersion ProtocolVersion { get; }
+ /// <summary>
+ /// Is the connection using transport security?
+ /// </summary>
+ bool IsSecure { get; }
+ /// <summary>
+ /// The negotiated transport protocol for the current connection
+ /// </summary>
+ SslProtocols SecurityProtocol { get; }
+ /// <summary>
+ /// Origin header of current connection if specified, null otherwise
+ /// </summary>
+ Uri? Origin { get; }
+ /// <summary>
+ /// Referer header of current connection if specified, null otherwise
+ /// </summary>
+ Uri? Referer { get; }
+ /// <summary>
+ /// The parsed range header, or -1,-1 if the range header was not set
+ /// </summary>
+ Tuple<long, long>? Range { get; }
+ /// <summary>
+ /// The server endpoint that accepted the connection
+ /// </summary>
+ IPEndPoint LocalEndpoint { get; }
+ /// <summary>
+ /// The raw <see cref="IPEndPoint"/> of the downstream connection.
+ /// </summary>
+ IPEndPoint RemoteEndpoint { get; }
+ /// <summary>
+ /// The encoding type used to decode and encode character data to and from the current client
+ /// </summary>
+ Encoding Encoding { get; }
+ /// <summary>
+ /// A <see cref="IReadOnlyDictionary{TKey, TValue}"/> of client request cookies
+ /// </summary>
+ IReadOnlyDictionary<string, string> RequestCookies { get; }
+ /// <summary>
+ /// Gets an <see cref="IEnumerator{T}"/> for the parsed accept header values
+ /// </summary>
+ IEnumerable<string> Accept { get; }
+ /// <summary>
+ /// Gets the underlying transport security information for the current connection
+ /// </summary>
+ TransportSecurityInfo? TransportSecurity { get; }
+
+ /// <summary>
+ /// Determines if the client accepts the response content type
+ /// </summary>
+ /// <param name="type">The desired content type</param>
+ /// <returns>True if the client accepts the content type, false otherwise</returns>
+ bool Accepts(ContentType type);
+
+ /// <summary>
+ /// Determines if the client accepts the response content type
+ /// </summary>
+ /// <param name="contentType">The desired content type</param>
+ /// <returns>True if the client accepts the content type, false otherwise</returns>
+ bool Accepts(string contentType);
+
+ /// <summary>
+ /// Adds a new cookie to the response. If a cookie with the same name and value
+ /// has been set, the old cookie is replaced with the new one.
+ /// </summary>
+ /// <param name="name">Cookie name/id</param>
+ /// <param name="value">Value to be stored in cookie</param>
+ /// <param name="domain">Domain for cookie to operate</param>
+ /// <param name="path">Path to store cookie</param>
+ /// <param name="Expires">Timespan representing how long the cookie should exist</param>
+ /// <param name="sameSite">Samesite attribute, Default = Lax</param>
+ /// <param name="httpOnly">Specify the HttpOnly flag</param>
+ /// <param name="secure">Specify the Secure flag</param>
+ void SetCookie(string name, string value, string? domain, string? path, TimeSpan Expires, CookieSameSite sameSite, bool httpOnly, bool secure);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/IHeaderCollection.cs b/lib/Net.Http/src/IHeaderCollection.cs
new file mode 100644
index 0000000..f7f147a
--- /dev/null
+++ b/lib/Net.Http/src/IHeaderCollection.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IHeaderCollection.cs
+*
+* IHeaderCollection.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Net;
+using System.Collections.Generic;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// The container for request and response headers
+ /// </summary>
+ public interface IHeaderCollection
+ {
+ /// <summary>
+ /// Allows for enumeratring all requesest headers
+ /// </summary>
+ IEnumerable<KeyValuePair<string, string>> RequestHeaders { get; }
+ /// <summary>
+ /// Allows for enumeratring all response headers
+ /// </summary>
+ IEnumerable<KeyValuePair<string, string>> ResponseHeaders { get; }
+ /// <summary>
+ /// Gets request header, or sets a response header
+ /// </summary>
+ /// <param name="index"></param>
+ /// <returns>Request header with key</returns>
+ string? this[string index] { get; set; }
+ /// <summary>
+ /// Sets a response header only with a response header index
+ /// </summary>
+ /// <param name="index">Response header</param>
+ string this[HttpResponseHeader index] { set; }
+ /// <summary>
+ /// Gets a request header
+ /// </summary>
+ /// <param name="index">The request header enum </param>
+ string? this[HttpRequestHeader index] { get; }
+ /// <summary>
+ /// Determines if the given header is set in current response headers
+ /// </summary>
+ /// <param name="header">Header value to check response headers for</param>
+ /// <returns>true if header exists in current response headers, false otherwise</returns>
+ bool HeaderSet(HttpResponseHeader header);
+ /// <summary>
+ /// Determines if the given request header is set in current request headers
+ /// </summary>
+ /// <param name="header">Header value to check request headers for</param>
+ /// <returns>true if header exists in current request headers, false otherwise</returns>
+ bool HeaderSet(HttpRequestHeader header);
+
+ /// <summary>
+ /// Overwrites (sets) the given response header to the exact value specified
+ /// </summary>
+ /// <param name="header">The enumrated header id</param>
+ /// <param name="value">The value to specify</param>
+ void Append(HttpResponseHeader header, string? value);
+ /// <summary>
+ /// Overwrites (sets) the given response header to the exact value specified
+ /// </summary>
+ /// <param name="header">The header name</param>
+ /// <param name="value">The value to specify</param>
+ void Append(string header, string? value);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/IMemoryResponseEntity.cs b/lib/Net.Http/src/IMemoryResponseEntity.cs
new file mode 100644
index 0000000..aa77f58
--- /dev/null
+++ b/lib/Net.Http/src/IMemoryResponseEntity.cs
@@ -0,0 +1,69 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IMemoryResponseEntity.cs
+*
+* IMemoryResponseEntity.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// <para>
+ /// A forward only memory backed response entity body reader. This interface exists
+ /// to provide a memory-backed response body that will be written "directly" to the
+ /// response stream. This avoids a buffer allocation and a copy.
+ /// </para>
+ /// <para>
+ /// The entity is only read foward, one time, so it is not seekable.
+ /// </para>
+ /// <para>
+ /// The <see cref="Close"/> method is always called by internal lifecycle hooks
+ /// when the entity is no longer needed. <see cref="Close"/> should avoid raising
+ /// excptions.
+ /// </para>
+ /// </summary>
+ public interface IMemoryResponseReader
+ {
+ /// <summary>
+ /// Gets a readonly buffer containing the remaining
+ /// data to be written
+ /// </summary>
+ /// <returns>A memory segment to send to the client</returns>
+ ReadOnlyMemory<byte> GetMemory();
+
+ /// <summary>
+ /// Advances the buffer by the number of bytes written
+ /// </summary>
+ /// <param name="written">The number of bytes written</param>
+ void Advance(int written);
+
+ /// <summary>
+ /// The number of bytes remaining to send
+ /// </summary>
+ int Remaining { get; }
+
+ /// <summary>
+ /// Raised when reading has completed
+ /// </summary>
+ void Close();
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/ITransportContext.cs b/lib/Net.Http/src/ITransportContext.cs
new file mode 100644
index 0000000..bd6ce05
--- /dev/null
+++ b/lib/Net.Http/src/ITransportContext.cs
@@ -0,0 +1,71 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ITransportContext.cs
+*
+* ITransportContext.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using System.Security.Authentication;
+
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents an active connection for application data processing
+ /// </summary>
+ public interface ITransportContext
+ {
+ /// <summary>
+ /// The transport network stream for application data marshaling
+ /// </summary>
+ Stream ConnectionStream { get; }
+ /// <summary>
+ /// The transport security layer security protocol
+ /// </summary>
+ SslProtocols SslVersion { get; }
+ /// <summary>
+ /// A copy of the local endpoint of the listening socket
+ /// </summary>
+ IPEndPoint LocalEndPoint { get; }
+ /// <summary>
+ /// The <see cref="IPEndPoint"/> representing the client's connection information
+ /// </summary>
+ IPEndPoint RemoteEndpoint { get; }
+
+ /// <summary>
+ /// Closes the connection when its no longer in use and cleans up held resources.
+ /// </summary>
+ /// <returns></returns>
+ /// <remarks>
+ /// This method will always be called by the server when a connection is complete
+ /// regardless of the state of the trasnport
+ /// </remarks>
+ ValueTask CloseConnectionAsync();
+
+ /// <summary>
+ /// Attemts to get the transport security details for the connection
+ /// </summary>
+ /// <returns>A the <see cref="TransportSecurityInfo"/> structure if applicable, null otherwise</returns>
+ TransportSecurityInfo? GetSecurityInfo();
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/ITransportProvider.cs b/lib/Net.Http/src/ITransportProvider.cs
new file mode 100644
index 0000000..13aae57
--- /dev/null
+++ b/lib/Net.Http/src/ITransportProvider.cs
@@ -0,0 +1,51 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: ITransportProvider.cs
+*
+* ITransportProvider.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Listens for network connections and captures the information
+ /// required for application processing
+ /// </summary>
+ public interface ITransportProvider
+ {
+ /// <summary>
+ /// Begins listening for connections (binds a socket if necessary) and is
+ /// called before the server begins listening for connections.
+ /// </summary>
+ /// <param name="stopToken">A token that is cancelled when the server is closed</param>
+ void Start(CancellationToken stopToken);
+
+ /// <summary>
+ /// Waits for a new connection to be established and returns its context. This method
+ /// should only return an established connection (ie: connected socket).
+ /// </summary>
+ /// <param name="cancellation">A token to cancel the wait operation</param>
+ /// <returns>A <see cref="ValueTask"/> that returns an established connection</returns>
+ ValueTask<ITransportContext> AcceptAsync(CancellationToken cancellation);
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/IWebRoot.cs b/lib/Net.Http/src/IWebRoot.cs
new file mode 100644
index 0000000..9b1a9c8
--- /dev/null
+++ b/lib/Net.Http/src/IWebRoot.cs
@@ -0,0 +1,58 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: IWebRoot.cs
+*
+* IWebRoot.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+
+namespace VNLib.Net.Http
+{
+ /// <summary>
+ /// Represents a root identifying the main endpoints of the server, and the primary processing actions
+ /// for requests to this endpoint
+ /// </summary>
+ public interface IWebRoot
+ {
+ /// <summary>
+ /// The hostname the server will listen for, and the hostname that will identify this root when a connection requests it
+ /// </summary>
+ string Hostname { get; }
+ /// <summary>
+ /// <para>
+ /// The main event handler for user code to process a request
+ /// </para>
+ /// <para>
+ /// NOTE: This function must be thread-safe!
+ /// </para>
+ /// </summary>
+ /// <param name="httpEvent">An active, unprocessed event capturing the request infomration into a standard format</param>
+ /// <returns>A <see cref="ValueTask"/> that the processor will await until the entity has been processed</returns>
+ ValueTask ClientConnectedAsync(IHttpEvent httpEvent);
+ /// <summary>
+ /// "Low-Level" 301 redirects
+ /// </summary>
+ IReadOnlyDictionary<string, Redirect> Redirects { get; }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Http/src/TransportSecurityInfo.cs b/lib/Net.Http/src/TransportSecurityInfo.cs
new file mode 100644
index 0000000..7c7a79c
--- /dev/null
+++ b/lib/Net.Http/src/TransportSecurityInfo.cs
@@ -0,0 +1,121 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TransportSecurityInfo.cs
+*
+* TransportSecurityInfo.cs is part of VNLib.Net.Http which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Http is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Net;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+
+
+namespace VNLib.Net.Http
+{
+
+ /// <summary>
+ /// Gets the transport TLS security information for the current connection
+ /// </summary>
+ public readonly struct TransportSecurityInfo
+ {
+ /// <summary>
+ /// Gets a Boolean value that indicates whether the certificate revocation list is checked during the certificate validation process.
+ /// </summary>
+ /// <returns>true if the certificate revocation list is checked during validation; otherwise, false.</returns>
+ public readonly bool CheckCertRevocationStatus { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the bulk encryption algorithm used by the connection.
+ /// </summary>
+ public readonly CipherAlgorithmType CipherAlgorithm { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the strength of the cipher algorithm used by the connection.
+ /// </summary>
+ public readonly int CipherStrength { get; init; }
+
+ /// <summary>
+ /// Gets the algorithm used for generating message authentication codes (MACs).
+ /// </summary>
+ public readonly HashAlgorithmType HashAlgorithm { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the strength of the hash algorithm used by this instance.
+ /// </summary>
+ public readonly int HashStrength { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether authentication was successful.
+ /// </summary>
+ public readonly bool IsAuthenticated { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether this connection uses data encryption.
+ /// </summary>
+ public readonly bool IsEncrypted { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether both server and client have been authenticated.
+ /// </summary>
+ public readonly bool IsMutuallyAuthenticated { get; init; }
+
+ /// <summary>
+ /// Gets a Boolean value that indicates whether the data sent using this connection is signed.
+ /// </summary>
+ public readonly bool IsSigned { get; init; }
+
+ /// <summary>
+ /// Gets the key exchange algorithm used by this connection
+ /// </summary>
+ public readonly ExchangeAlgorithmType KeyExchangeAlgorithm { get; init; }
+
+ /// <summary>
+ /// Gets a value that identifies the strength of the key exchange algorithm used by the transport connection
+ /// </summary>
+ public readonly int KeyExchangeStrength { get; init; }
+
+ /// <summary>
+ /// Gets the certificate used to authenticate the local endpoint.
+ /// </summary>
+ public readonly X509Certificate? LocalCertificate { get; init; }
+
+ /// <summary>
+ /// The negotiated application protocol in TLS handshake.
+ /// </summary>
+ public readonly SslApplicationProtocol NegotiatedApplicationProtocol { get; init; }
+
+ /// <summary>
+ /// Gets the cipher suite which was negotiated for this connection.
+ /// </summary>
+ public readonly TlsCipherSuite NegotiatedCipherSuite { get; init; }
+
+ /// <summary>
+ /// Gets the certificate used to authenticate the remote endpoint.
+ /// </summary>
+ public readonly X509Certificate? RemoteCertificate { get; init; }
+
+ /// <summary>
+ /// Gets the TransportContext used for authentication using extended protection.
+ /// </summary>
+ public readonly TransportContext TransportContext { get; init; }
+ }
+}
diff --git a/lib/Net.Http/src/VNLib.Net.Http.csproj b/lib/Net.Http/src/VNLib.Net.Http.csproj
new file mode 100644
index 0000000..30e698c
--- /dev/null
+++ b/lib/Net.Http/src/VNLib.Net.Http.csproj
@@ -0,0 +1,57 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <RootNamespace>VNLib.Net.Http</RootNamespace>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>$(Authors)</Company>
+ <Product>VNLib HTTP Library</Product>
+ <Description>Provides a high performance HTTP 0.9-1.1 application processing layer for handling transport *agnostic connections and asynchronous event support for applications serving HTTP
+requests such as web content. This library has a large focus on low/no GC allocations using unmanaged memory support provided by the VNLib.Utils library. No external dependencies
+outside of the VNLib ecosystem are required. The VNLib.Plugins and VNLib.Plugins.Essentials libraries are highly recommended for serving web content.</Description>
+ <Copyright>Copyright © 2022 Vaughn Nugent</Copyright>
+ <PackageId>VNLib.Net.Http</PackageId>
+ <Version>1.0.1.5</Version>
+ <NeutralLanguage>en-US</NeutralLanguage>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
+ <AssemblyName>VNLib.Net.Http</AssemblyName>
+ <Nullable>enable</Nullable>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <SignAssembly>True</SignAssembly>
+ <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+
+ <!-- Resolve nuget dll files and store them in the output dir -->
+ <PropertyGroup>
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'">
+ <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\Utils\src\VNLib.Utils.csproj" />
+ </ItemGroup>
+
+</Project>