commit 85dc5981cd6d35301dc53b478263d75e1bdb00ca Author: HiveBeats Date: Thu Jul 27 01:47:59 2023 +0400 feat: initial development diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6cead15 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,208 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.json] +indent_size = 2 + +[*.{cs,xaml}] +charset = utf-8 +end_of_line = crlf + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary : silent +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary : silent +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary : silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary : silent + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async : suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members : warning + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false : suggestion +dotnet_style_qualification_for_property = false : suggestion +dotnet_style_qualification_for_method = false : suggestion +dotnet_style_qualification_for_event = false : suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false : suggestion +csharp_style_var_when_type_is_apparent = false : none +csharp_style_var_elsewhere = false : suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true : suggestion +dotnet_style_predefined_type_for_member_access = true : suggestion + +# Naming Conventions + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ + +dotnet_naming_style.interfaces_style.capitalization = pascal_case +dotnet_naming_style.interfaces_style.required_prefix = I + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const + +# static fields using PascalCase +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = pascal_case_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal + +# interfaces must have an I prefix +dotnet_naming_rule.interfaces_must_be_interfaces_style.severity = suggestion +dotnet_naming_rule.interfaces_must_be_interfaces_style.symbols = interfaces +dotnet_naming_rule.interfaces_must_be_interfaces_style.style = interfaces_style +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = * + +# Code style defaults +csharp_using_directive_placement = outside_namespace : suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true : suggestion +csharp_preserve_single_line_blocks = true : none +csharp_preserve_single_line_statements = false : none +csharp_prefer_static_local_function = true : suggestion +csharp_prefer_simple_using_statement = false : none +csharp_style_prefer_switch_expression = true : suggestion + +# Code quality +dotnet_style_readonly_field = true : suggestion +dotnet_code_quality_unused_parameters = non_public : suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true : suggestion +dotnet_style_collection_initializer = true : suggestion +dotnet_style_explicit_tuple_names = true : suggestion +dotnet_style_coalesce_expression = true : suggestion +dotnet_style_null_propagation = true : suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true : suggestion +dotnet_style_prefer_inferred_tuple_names = true : suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true : suggestion +dotnet_style_prefer_auto_properties = true : suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true : refactoring +dotnet_style_prefer_conditional_expression_over_return = true : refactoring +csharp_prefer_simple_default_expression = true : suggestion +csharp_style_deconstructed_variable_declaration = true : suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true : refactoring +csharp_style_expression_bodied_constructors = true : refactoring +csharp_style_expression_bodied_operators = true : refactoring +csharp_style_expression_bodied_properties = true : refactoring +csharp_style_expression_bodied_indexers = true : refactoring +csharp_style_expression_bodied_accessors = true : refactoring +csharp_style_expression_bodied_lambdas = true : refactoring +csharp_style_expression_bodied_local_functions = true : refactoring + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion +csharp_style_pattern_matching_over_as_with_null_check = true : suggestion +csharp_style_inlined_variable_declaration = true : suggestion + +# Null checking preferences +csharp_style_throw_expression = true : suggestion +csharp_style_conditional_delegate_call = true : suggestion + +# Other features +csharp_style_prefer_index_operator = false : none +csharp_style_prefer_range_operator = false : none +csharp_style_pattern_local_over_anonymous_function = false : none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Analyzers +dotnet_code_quality.ca1802.api_surface = private, internal + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd, bat}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d13c54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,334 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ diff --git a/Blog.Server/Blog.Server.csproj b/Blog.Server/Blog.Server.csproj new file mode 100644 index 0000000..5d65e73 --- /dev/null +++ b/Blog.Server/Blog.Server.csproj @@ -0,0 +1,36 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/Blog.Server/Program.cs b/Blog.Server/Program.cs new file mode 100644 index 0000000..70ea709 --- /dev/null +++ b/Blog.Server/Program.cs @@ -0,0 +1,37 @@ +// See https://aka.ms/new-console-template for more information + +using HandlebarsDotNet; +using NaiveHttpServer; + + +var server = new Server("localhost", 3000); + +var source = File.ReadAllText(Path.Combine(Environment.CurrentDirectory, "Views/index.html")); + +var template = Handlebars.Compile(source); + +var data = new { + title = "My new post", + body = "This is my first post!" +}; + +var result = template(data); + +// Build Routers +var router = new RouterBuilder() + .Get("/", async ctx => + { + await ctx.Response.Html(result); + }).Build(); +server + .Use(Middlewares.Log) + .Use(Middlewares.ExceptionHandling) + .Use(Middlewares.StaticFile("css/", Path.Combine(Environment.CurrentDirectory,"Static/css/"))) + .Use(router) + .Use(Middlewares.NotFound(documentUrl: "http://api.project.com/v1")); + +server.Start(); + +Console.ReadKey(); + +server.Stop(); diff --git a/Blog.Server/Static/css/feed.css b/Blog.Server/Static/css/feed.css new file mode 100644 index 0000000..68cc161 --- /dev/null +++ b/Blog.Server/Static/css/feed.css @@ -0,0 +1,74 @@ +.blog-title { + font-family: 'Roboto', serif; + font-style: normal; + font-weight: 300; + font-size: 32px; + line-height: 40px; + color: var(--bs-info); +} + +.blog-nav-list { + font-weight: 400; + font-size: 16px; + line-height: 24px; + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; +} + +.blog-nav-list > li { + float: left; + padding-right: 0.25rem; + color: #D9D9D9; +} + +.active-nav-item { + color: var(--bs-info) !important; +} + +.box { + box-sizing: border-box; + background-clip: padding-box; +} + +.blog-author-avatar { + max-height: 32px; +} + +.blog-author { + size: 14px; + line-height: 32px; +} + +.post-link { + color: #4f4f4f !important; +} + +.tag { + border: 1px solid var(--bs-info); + border-radius: 4px; + justify-content: center; + align-items: center; + padding: 2px 16px; +} + +.static-avatar { + display: block; + overflow: hidden; + margin: 0 0 0 -6px; + width: 34px; + height: 34px; + border: 2px solid #fff; + border-radius: 100%; +} + +.author-profile-image, .avatar-wrapper { + display: block; + width: 100%; + height: 100%; + background: #e3e9ed; + border-radius: 100%; + -o-object-fit: cover; + object-fit: cover; +} diff --git a/Blog.Server/Static/css/main.css b/Blog.Server/Static/css/main.css new file mode 100644 index 0000000..694ec1f --- /dev/null +++ b/Blog.Server/Static/css/main.css @@ -0,0 +1,205 @@ +.timeline-card { + position: relative; + margin-left: 16px; +} + +.timeline-card:before { + content: ''; + display: inline-block; + position: absolute; + background-color: #fff; + border-radius: 100%; + width: 24px; + height: 24px; + top: 16px; + left: -12px; + border: 5px solid; + z-index: 2; +} + +.timeline-body { + border-left: 2px solid #E6E9ED; +} + +.timeline-card-primary:before { + border-color: var(--bs-primary); +} + +.timeline-card-info:before { + border-color: var(--bs-info); +} + +.timeline-card-secondary:before { + border-color: var(--bs-secondary); +} + +.timeline-card-success:before { + border-color: var(--bs-teal); +} + +html { + scroll-behavior: smooth; +} + +html, +body { + overflow-x: hidden; +} + +.container { + max-width: 1140px; +} + +.site-title { + font-size: 1.25rem; + line-height: 2.5rem; +} + +.nav-link { + padding: 0; + font-size: 1rem; + line-height: 2.5rem; + color: inherit; + opacity: 0.8; +} + +.header-social .nav-link { + font-size: 1.25rem; +} + +.nav-link:hover, +.nav-link:focus { + color: inherit; + opacity: 1; +} + +.nav-item+.nav-item { + margin-left: 1rem; +} + +.cover { + height: 500px; +} + +.cover>img { + -o-object-fit: cover; + object-fit: cover; + width: auto; +} + +.progress-bar { + text-transform: uppercase; + font-size: 10px; + letter-spacing: 1px; +} + +.text-small { + font-size: 0.85rem; +} + +.text-teal { + color: var(--bs-teal); +} + +footer a:not(.nav-link) { + color: inherit; + border-bottom: 1px dashed; + text-decoration: none; + cursor: pointer; +} + +.rounded-block { + border-radius: 0.5rem; + border: 1px solid rgba(0,0,0,.25); +} + +@media (min-width: 48em) { + .site-title { + float: left; + } + + .site-nav { + float: right; + } +} + +@media (max-width: 767px) { + + /* disable animations on mobile */ + [data-aos] { + opacity: 1 !important; + transform: translate(0) scale(1) !important; + } + + .p-5 { + padding: 2.5rem 2rem !important; + } + + .portfolio-section .m-5 { + margin: 2rem 0 1rem !important; + } + + .portfolio-reverse { + flex-direction: column-reverse; + } + + .portfolio-reverse .text-end { + text-align: start !important; + } +} + +@media print { + [data-aos] { + opacity: 1 !important; + transform: translate(0) scale(1) !important; + } + + body.bg-light { + background-color: #fff !important; + } + + .container { + width: auto; + max-width: 100%; + padding: 0; + } + + .cover { + height: 360px; + margin-bottom: 1.5rem; + } + + .cover>img { + display: none; + } + + .shadow-1-strong { + box-shadow: none !important; + } + + .resume-container>.my-5 { + margin: 0 !important; + } + + .my-5.p-5 { + padding: 1.5rem 0 !important; + } + + .about-section { + padding: 0 !important; + } + + .skills-section, + .work-experience-section, + .education-section, + .portfolio-section, + .reference-section, + .contact-section { + padding: 0 !important; + } + + .page-break { + padding-top: 5rem; + page-break-before: always; + } +} diff --git a/Blog.Server/Static/css/post.css b/Blog.Server/Static/css/post.css new file mode 100644 index 0000000..fe3223e --- /dev/null +++ b/Blog.Server/Static/css/post.css @@ -0,0 +1,51 @@ +.blog-title { + font-family: 'Roboto', serif; + font-style: normal; + font-weight: 300; + font-size: 32px; + line-height: 40px; + color: var(--bs-info); +} + +.static-avatar { + display: block; + overflow: hidden; + margin: 0 0 0 -6px; + width: 40px; + height: 40px; + border: 2px solid #fff; + border-radius: 100%; +} + +.author-profile-image, .avatar-wrapper { + display: block; + width: 100%; + height: 100%; + background: #e3e9ed; + border-radius: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.post-author { + color: #757575; + font-size: 14px; +} + +.wp-block-image > img { + max-width: 100%; + width: 100%; +} + +figcaption { + color: #757575; + font-size: 75%; + line-height: 1.5em; + text-align: center; +} + +@media (min-width: 768px) { + .post-content { + padding: 0 170px 6vw; + } +} diff --git a/Blog.Server/Static/css/site.css b/Blog.Server/Static/css/site.css new file mode 100644 index 0000000..97e699d --- /dev/null +++ b/Blog.Server/Static/css/site.css @@ -0,0 +1,18 @@ +html { + font-size: 14px; +} + +@media (min-width: 768px) { + html { + font-size: 16px; + } +} + +html { + position: relative; + min-height: 100%; +} + +body { + margin-bottom: 60px; +} diff --git a/Blog.Server/Views/index.html b/Blog.Server/Views/index.html new file mode 100644 index 0000000..9b6e060 --- /dev/null +++ b/Blog.Server/Views/index.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + Ivan Laletin - Senior Fullstack Developer + + + + + + + + + + + + +
+
+
+

Ivan Laletin

+ +
+
+
+
+ {{body}} +
+ + + + + + + diff --git a/Demo/Demo.csproj b/Demo/Demo.csproj new file mode 100644 index 0000000..224b934 --- /dev/null +++ b/Demo/Demo.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/Demo/Program.cs b/Demo/Program.cs new file mode 100644 index 0000000..350557f --- /dev/null +++ b/Demo/Program.cs @@ -0,0 +1,15 @@ +using NaiveHttpServer; + +var server = new Server("localhost", 2333); + +server + .Use(Middlewares.Log) + .Use(Middlewares.ExceptionHandling) + .Use(Middlewares.StaticFile("/files", Environment.CurrentDirectory)) + .Use(Middlewares.NotFound(documentUrl: "http://api.project.com/v1")); + +server.Start(); + +Console.ReadKey(); + +server.Stop(); diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a33d95c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Dingping Zhang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NaiveHttpServer.sln b/NaiveHttpServer.sln new file mode 100644 index 0000000..a59f941 --- /dev/null +++ b/NaiveHttpServer.sln @@ -0,0 +1,45 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NaiveHttpServer", "NaiveHttpServer\NaiveHttpServer.csproj", "{DBE214C2-832D-4F3A-8AA5-1B3717EFEE2A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F9C0C1FB-0DBA-444E-A01C-A2BD05127557}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{173B9B8A-AD3D-4BC3-917D-9E56E7B13681}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blog.Server", "Blog.Server\Blog.Server.csproj", "{1137F0C8-0B4A-4C6B-9FAD-4481E0E2C588}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DBE214C2-832D-4F3A-8AA5-1B3717EFEE2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBE214C2-832D-4F3A-8AA5-1B3717EFEE2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBE214C2-832D-4F3A-8AA5-1B3717EFEE2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBE214C2-832D-4F3A-8AA5-1B3717EFEE2A}.Release|Any CPU.Build.0 = Release|Any CPU + {173B9B8A-AD3D-4BC3-917D-9E56E7B13681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {173B9B8A-AD3D-4BC3-917D-9E56E7B13681}.Debug|Any CPU.Build.0 = Debug|Any CPU + {173B9B8A-AD3D-4BC3-917D-9E56E7B13681}.Release|Any CPU.ActiveCfg = Release|Any CPU + {173B9B8A-AD3D-4BC3-917D-9E56E7B13681}.Release|Any CPU.Build.0 = Release|Any CPU + {1137F0C8-0B4A-4C6B-9FAD-4481E0E2C588}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1137F0C8-0B4A-4C6B-9FAD-4481E0E2C588}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1137F0C8-0B4A-4C6B-9FAD-4481E0E2C588}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1137F0C8-0B4A-4C6B-9FAD-4481E0E2C588}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5472BDAB-9DDB-46B0-996F-B1FE7DF2035C} + EndGlobalSection +EndGlobal diff --git a/NaiveHttpServer/Context.cs b/NaiveHttpServer/Context.cs new file mode 100644 index 0000000..f6e0d71 --- /dev/null +++ b/NaiveHttpServer/Context.cs @@ -0,0 +1,29 @@ +using System.Net; + +namespace NaiveHttpServer +{ + public delegate bool ParameterProvider(string key, out string value); + + public class Context + { + public HttpListenerRequest Request { get; } + + public HttpListenerResponse Response { get; } + + public ILogger Logger { get; } + + public ParameterProvider TryGetParameter { get; set; } + + public Context(HttpListenerRequest request, HttpListenerResponse response, ILogger logger) + { + Request = request; + Response = response; + Logger = logger; + TryGetParameter = (string _, out string value) => + { + value = null!; + return false; + }; + } + } +} diff --git a/NaiveHttpServer/DefaultLogger.cs b/NaiveHttpServer/DefaultLogger.cs new file mode 100644 index 0000000..99c12b6 --- /dev/null +++ b/NaiveHttpServer/DefaultLogger.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading; + +namespace NaiveHttpServer +{ + internal class DefaultLogger : ILogger + { + public void Error(string message, Exception? exception = null) + { + Write(nameof(Error), message, exception); + } + + public void Warning(string message, Exception? exception = null) + { + Write(nameof(Warning), message, exception); + } + + public void Info(string message, Exception? exception = null) + { + Write(nameof(Info), message, exception); + } + + public void Debug(string message, Exception? exception = null) + { + Write(nameof(Debug), message, exception); + } + + private static void Write(string level, string message, Exception? exception) + { + Thread thread = Thread.CurrentThread; + string threadName = string.IsNullOrEmpty(thread.Name) ? thread.ManagedThreadId.ToString() : thread.Name!; + + string exceptionString = exception is null ? string.Empty : $"{Environment.NewLine}{exception.Message}{Environment.NewLine}{exception.StackTrace}{Environment.NewLine}"; + + System.Diagnostics.Debug.WriteLine($"{DateTime.Now:HH:mm:ss.fff} {level.ToUpperInvariant()} [{threadName}] {message}{exceptionString}"); + } + } +} diff --git a/NaiveHttpServer/ErrorCodes.cs b/NaiveHttpServer/ErrorCodes.cs new file mode 100644 index 0000000..58d7010 --- /dev/null +++ b/NaiveHttpServer/ErrorCodes.cs @@ -0,0 +1,9 @@ +namespace NaiveHttpServer +{ + public static class ErrorCodes + { + public const string Unknown = "UNKNOWN"; + public const string NotFoundApi = "NOT_FOUND_API"; + public const string NotFoundFile = "NOT_FOUND_FILE"; + } +} diff --git a/NaiveHttpServer/Extensions.cs b/NaiveHttpServer/Extensions.cs new file mode 100644 index 0000000..1a14641 --- /dev/null +++ b/NaiveHttpServer/Extensions.cs @@ -0,0 +1,83 @@ +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace NaiveHttpServer +{ + public static class Extensions + { + public static readonly JsonSerializerSettings JsonSettings = new() + { + MissingMemberHandling = MissingMemberHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore, + }; + + public static async Task Json(this HttpListenerResponse response, object value) + { + string jsonText = value.ToJson(); + byte[] bytes = Encoding.UTF8.GetBytes(jsonText); + + response.ContentEncoding = Encoding.UTF8; + response.ContentType = "application/json"; + response.ContentLength64 = bytes.Length; + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length); + } + + public static async Task File(this HttpListenerResponse response, string filePath) + { + if (!System.IO.File.Exists(filePath)) + { + return; + } + + await FileHelper.ReadAsync(filePath, async stream => + { + response.ContentType = MimeTypes.GetMimeType(filePath); + response.ContentLength64 = stream.Length; + await stream.CopyToAsync(response.OutputStream); + }); + } + + public static async Task Html(this HttpListenerResponse response, string template) + { + byte[] buffer = Encoding.UTF8.GetBytes(template); + response.ContentType = System.Net.Mime.MediaTypeNames.Text.Html; + response.ContentLength64 = buffer.Length; + await response.OutputStream.WriteAsync(buffer, 0, buffer.Length); + } + + public static async Task Error(this HttpListenerResponse response, string errorCode, string message, int statusCode = 500) + { + response.StatusCode = statusCode; + await response.Json(new + { + errorCode, + message, + }); + } + + public static async Task JsonFromBody(this HttpListenerRequest request) + { + string jsonText = await request.TextFromBody(); + return jsonText.ToObject(); + } + + public static async Task TextFromBody(this HttpListenerRequest request) + { + using StreamReader reader = new(request.InputStream); + return await reader.ReadToEndAsync(); + } + + public static string ToJson(this object value, Formatting formatting = Formatting.None) + { + return JsonConvert.SerializeObject(value, formatting, JsonSettings); + } + + public static T? ToObject(this string json) + { + return JsonConvert.DeserializeObject(json, JsonSettings); + } + } +} diff --git a/NaiveHttpServer/FileHelper.cs b/NaiveHttpServer/FileHelper.cs new file mode 100644 index 0000000..73267a9 --- /dev/null +++ b/NaiveHttpServer/FileHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace NaiveHttpServer +{ + public static class FileHelper + { + public static ILogger? Logger { get; set; } + + public static async Task WriteAsync(string path, Func writer, bool isBackup = true) + { + string tempFilePath = $"{path}.writing"; + using (var stream = new FileStream( + tempFilePath, + FileMode.Create, + FileAccess.Write, + FileShare.None, + 0x10000, + FileOptions.SequentialScan) + ) + { + await writer(stream); + } + + if (File.Exists(path)) + { + await SpinRetry(() => File.Replace(tempFilePath, path, isBackup ? BackupPath(path) : null, true)); + } + else + { + await SpinRetry(() => File.Move(tempFilePath, path)); + } + } + + public static async Task ReadAsync(string path, Func reader) + { + try + { + await CriticalReadAsync(path, reader); + } + catch (Exception e) + { + string backupPath = BackupPath(path); + if (!File.Exists(backupPath)) + { + throw; + } + + Logger?.Warning($"Can not read {path}, turn back to backup.", e); + await CriticalReadAsync(backupPath, reader); + } + } + + private static async Task CriticalReadAsync(string path, Func reader) + { + using FileStream stream = new( + path, + FileMode.OpenOrCreate, + FileAccess.Read, + FileShare.ReadWrite, + 0x10000, + FileOptions.SequentialScan); + await reader(stream); + } + + private static string BackupPath(string path) => $"{path}.backup"; + + private static async Task SpinRetry(Action action, int retryCount = 10) + { + for (int i = 0; i < retryCount; i++) + { + try + { + action(); + await Task.Delay(100); + break; + } + catch + { + if (i == retryCount - 1) + { + throw; + } + } + } + } + } +} diff --git a/NaiveHttpServer/HttpMethods.cs b/NaiveHttpServer/HttpMethods.cs new file mode 100644 index 0000000..a99d377 --- /dev/null +++ b/NaiveHttpServer/HttpMethods.cs @@ -0,0 +1,10 @@ +namespace NaiveHttpServer +{ + public static class HttpMethods + { + public const string Get = "GET"; + public const string Post = "POST"; + public const string Put = "PUT"; + public const string Delete = "DELETE"; + } +} diff --git a/NaiveHttpServer/ILogger.cs b/NaiveHttpServer/ILogger.cs new file mode 100644 index 0000000..c54f8c8 --- /dev/null +++ b/NaiveHttpServer/ILogger.cs @@ -0,0 +1,15 @@ +using System; + +namespace NaiveHttpServer +{ + public interface ILogger + { + void Error(string message, Exception? exception = null); + + void Warning(string message, Exception? exception = null); + + void Info(string message, Exception? exception = null); + + void Debug(string message, Exception? exception = null); + } +} diff --git a/NaiveHttpServer/IRouterBuilder.cs b/NaiveHttpServer/IRouterBuilder.cs new file mode 100644 index 0000000..aeccc54 --- /dev/null +++ b/NaiveHttpServer/IRouterBuilder.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace NaiveHttpServer +{ + public interface IRouterBuilder + { + IRouterBuilder Get(string url, Func handler); + + IRouterBuilder Post(string url, Func handler); + + IRouterBuilder Delete(string url, Func handler); + + IRouterBuilder Put(string url, Func handler); + + Middleware Build(); + } +} diff --git a/NaiveHttpServer/Middlewares.cs b/NaiveHttpServer/Middlewares.cs new file mode 100644 index 0000000..ff3ab21 --- /dev/null +++ b/NaiveHttpServer/Middlewares.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Web; + +namespace NaiveHttpServer +{ + public delegate Task Middleware(T ctx, Func next); + + public static class Middlewares + { + public static Middleware Empty() => (_, next) => next(); + + public static async Task Log(Context ctx, Func next) + { + ILogger logger = ctx.Logger; + HttpListenerRequest request = ctx.Request; + HttpListenerResponse response = ctx.Response; + + logger.Debug($"[In] {request.HttpMethod} {request.RawUrl}"); + await next(); + logger.Debug($"[Out] {request.HttpMethod} [{response.StatusCode}] {request.RawUrl}"); + } + + public static async Task ExceptionHandling(Context ctx, Func next) + { + using HttpListenerResponse response = ctx.Response; + try + { + await next(); + } + catch (Exception e) + { + ctx.Logger.Warning("Unexpected exception occurred.", e); + await response.Error(ErrorCodes.Unknown, e.Message); + response.StatusCode = e switch + { + FileNotFoundException => 404, + DirectoryNotFoundException => 404, + UnauthorizedAccessException => 403, + _ => 500, + }; + } + } + + public static Middleware NotFound(string documentUrl) + { + return async (ctx, _) => + { + ctx.Response.StatusCode = 404; + await ctx.Response.Error( + ErrorCodes.NotFoundApi, + $"Not found this api: '{ctx.Request.RawUrl}', and please read the API document: {documentUrl}."); + }; + } + + public static Middleware StaticFile(string route, string rootDir) + { + return async (ctx, next) => + { + // Don't use Request.RawUrl, because it contains url parameters. (e.g. '?a=1&b=2') + string relativePath = ctx.Request.Url.AbsolutePath.TrimStart('/'); + bool handled = relativePath.StartsWith(route); + if (!handled) + { + await next(); + return; + } + + string requestPath = HttpUtility.UrlDecode(relativePath) + .Substring(route.Length) + .ToLowerInvariant() + .TrimStart('/', '\\'); + string filePath = Path.Combine(rootDir, requestPath); + + switch (ctx.Request.HttpMethod) + { + case HttpMethods.Get: + await ReadLocalFile(filePath, ctx.Response, ctx.Logger); + break; + case HttpMethods.Put: + await WriteLocalFile(filePath, ctx.Request); + ctx.Response.StatusCode = 204; + break; + } + }; + } + + private static async Task ReadLocalFile(string filePath, HttpListenerResponse response, ILogger logger) + { + if (File.Exists(filePath)) + { + await response.File(filePath); + } + else if (Directory.Exists(filePath)) + { + string[] filePaths = Directory + .GetFileSystemEntries(filePath) + .Select(Path.GetFileName) + .ToArray(); + await response.Json(filePaths); + } + else + { + string message = $"Not found the file: '{filePath}'."; + logger.Warning(message); + await response.Error(ErrorCodes.NotFoundFile, message, 404); + } + } + + private static Task WriteLocalFile(string filePath, HttpListenerRequest request) + { + return FileHelper.WriteAsync(filePath, stream => request.InputStream.CopyToAsync(stream)); + } + + public static Middleware Then(this Middleware middleware, Middleware nextMiddleware) + { + return (ctx, next) => middleware(ctx, () => nextMiddleware(ctx, next)); + } + + public static Task Run(this Middleware middleware, T ctx) + { + return middleware(ctx, () => +#if NET45 + Task.FromResult(0) +#else + Task.CompletedTask +#endif + ); + } + } +} diff --git a/NaiveHttpServer/MimeTypes.cs b/NaiveHttpServer/MimeTypes.cs new file mode 100644 index 0000000..9ffbd97 --- /dev/null +++ b/NaiveHttpServer/MimeTypes.cs @@ -0,0 +1,1032 @@ +// + +namespace NaiveHttpServer +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.CompilerServices; + + /// + /// Provides utilities for mapping file names and extensions to MIME-types. + /// + [CompilerGenerated] + [DebuggerNonUserCode] + public static class MimeTypes + { + /// + /// The fallback MIME-type. Defaults to application/octet-stream. + /// + public static string FallbackMimeType { get; set; } + + private static readonly Dictionary TypeMap; + + static MimeTypes() + { + FallbackMimeType = "application/octet-stream"; + + TypeMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "123", "application/vnd.lotus-1-2-3" }, + { "3dml", "text/vnd.in3d.3dml" }, + { "3ds", "image/x-3ds" }, + { "3g2", "video/3gpp2" }, + { "3gp", "video/3gpp" }, + { "7z", "application/x-7z-compressed" }, + { "aab", "application/x-authorware-bin" }, + { "aac", "audio/x-aac" }, + { "aam", "application/x-authorware-map" }, + { "aas", "application/x-authorware-seg" }, + { "abw", "application/x-abiword" }, + { "ac", "application/pkix-attr-cert" }, + { "acc", "application/vnd.americandynamics.acc" }, + { "ace", "application/x-ace-compressed" }, + { "acu", "application/vnd.acucobol" }, + { "acutc", "application/vnd.acucorp" }, + { "adp", "audio/adpcm" }, + { "aep", "application/vnd.audiograph" }, + { "afm", "application/x-font-type1" }, + { "afp", "application/vnd.ibm.modcap" }, + { "ahead", "application/vnd.ahead.space" }, + { "ai", "application/postscript" }, + { "aif", "audio/x-aiff" }, + { "aifc", "audio/x-aiff" }, + { "aiff", "audio/x-aiff" }, + { "air", "application/vnd.adobe.air-application-installer-package+zip" }, + { "ait", "application/vnd.dvb.ait" }, + { "ami", "application/vnd.amiga.ami" }, + { "apk", "application/vnd.android.package-archive" }, + { "appcache", "text/cache-manifest" }, + { "application", "application/x-ms-application" }, + { "apr", "application/vnd.lotus-approach" }, + { "arc", "application/x-freearc" }, + { "asc", "application/pgp-signature" }, + { "asf", "video/x-ms-asf" }, + { "asm", "text/x-asm" }, + { "aso", "application/vnd.accpac.simply.aso" }, + { "asx", "video/x-ms-asf" }, + { "atc", "application/vnd.acucorp" }, + { "atom", "application/atom+xml" }, + { "atomcat", "application/atomcat+xml" }, + { "atomsvc", "application/atomsvc+xml" }, + { "atx", "application/vnd.antix.game-component" }, + { "au", "audio/basic" }, + { "avi", "video/x-msvideo" }, + { "aw", "application/applixware" }, + { "azf", "application/vnd.airzip.filesecure.azf" }, + { "azs", "application/vnd.airzip.filesecure.azs" }, + { "azw", "application/vnd.amazon.ebook" }, + { "bat", "application/x-msdownload" }, + { "bcpio", "application/x-bcpio" }, + { "bdf", "application/x-font-bdf" }, + { "bdm", "application/vnd.syncml.dm+wbxml" }, + { "bed", "application/vnd.realvnc.bed" }, + { "bh2", "application/vnd.fujitsu.oasysprs" }, + { "bin", "application/octet-stream" }, + { "blb", "application/x-blorb" }, + { "blorb", "application/x-blorb" }, + { "bmi", "application/vnd.bmi" }, + { "bmp", "image/bmp" }, + { "book", "application/vnd.framemaker" }, + { "box", "application/vnd.previewsystems.box" }, + { "boz", "application/x-bzip2" }, + { "bpk", "application/octet-stream" }, + { "btif", "image/prs.btif" }, + { "bz", "application/x-bzip" }, + { "bz2", "application/x-bzip2" }, + { "c", "text/x-c" }, + { "c11amc", "application/vnd.cluetrust.cartomobile-config" }, + { "c11amz", "application/vnd.cluetrust.cartomobile-config-pkg" }, + { "c4d", "application/vnd.clonk.c4group" }, + { "c4f", "application/vnd.clonk.c4group" }, + { "c4g", "application/vnd.clonk.c4group" }, + { "c4p", "application/vnd.clonk.c4group" }, + { "c4u", "application/vnd.clonk.c4group" }, + { "cab", "application/vnd.ms-cab-compressed" }, + { "caf", "audio/x-caf" }, + { "cap", "application/vnd.tcpdump.pcap" }, + { "car", "application/vnd.curl.car" }, + { "cat", "application/vnd.ms-pki.seccat" }, + { "cb7", "application/x-cbr" }, + { "cba", "application/x-cbr" }, + { "cbr", "application/x-cbr" }, + { "cbt", "application/x-cbr" }, + { "cbz", "application/x-cbr" }, + { "cc", "text/x-c" }, + { "cct", "application/x-director" }, + { "ccxml", "application/ccxml+xml" }, + { "cdbcmsg", "application/vnd.contact.cmsg" }, + { "cdf", "application/x-netcdf" }, + { "cdkey", "application/vnd.mediastation.cdkey" }, + { "cdmia", "application/cdmi-capability" }, + { "cdmic", "application/cdmi-container" }, + { "cdmid", "application/cdmi-domain" }, + { "cdmio", "application/cdmi-object" }, + { "cdmiq", "application/cdmi-queue" }, + { "cdx", "chemical/x-cdx" }, + { "cdxml", "application/vnd.chemdraw+xml" }, + { "cdy", "application/vnd.cinderella" }, + { "cer", "application/pkix-cert" }, + { "cfs", "application/x-cfs-compressed" }, + { "cgm", "image/cgm" }, + { "chat", "application/x-chat" }, + { "chm", "application/vnd.ms-htmlhelp" }, + { "chrt", "application/vnd.kde.kchart" }, + { "cif", "chemical/x-cif" }, + { "cii", "application/vnd.anser-web-certificate-issue-initiation" }, + { "cil", "application/vnd.ms-artgalry" }, + { "cla", "application/vnd.claymore" }, + { "class", "application/java-vm" }, + { "clkk", "application/vnd.crick.clicker.keyboard" }, + { "clkp", "application/vnd.crick.clicker.palette" }, + { "clkt", "application/vnd.crick.clicker.template" }, + { "clkw", "application/vnd.crick.clicker.wordbank" }, + { "clkx", "application/vnd.crick.clicker" }, + { "clp", "application/x-msclip" }, + { "cmc", "application/vnd.cosmocaller" }, + { "cmdf", "chemical/x-cmdf" }, + { "cml", "chemical/x-cml" }, + { "cmp", "application/vnd.yellowriver-custom-menu" }, + { "cmx", "image/x-cmx" }, + { "cod", "application/vnd.rim.cod" }, + { "com", "application/x-msdownload" }, + { "conf", "text/plain" }, + { "cpio", "application/x-cpio" }, + { "cpp", "text/x-c" }, + { "cpt", "application/mac-compactpro" }, + { "crd", "application/x-mscardfile" }, + { "crl", "application/pkix-crl" }, + { "crt", "application/x-x509-ca-cert" }, + { "cryptonote", "application/vnd.rig.cryptonote" }, + { "csh", "application/x-csh" }, + { "csml", "chemical/x-csml" }, + { "csp", "application/vnd.commonspace" }, + { "css", "text/css" }, + { "cst", "application/x-director" }, + { "csv", "text/csv" }, + { "cu", "application/cu-seeme" }, + { "curl", "text/vnd.curl" }, + { "cww", "application/prs.cww" }, + { "cxt", "application/x-director" }, + { "cxx", "text/x-c" }, + { "dae", "model/vnd.collada+xml" }, + { "daf", "application/vnd.mobius.daf" }, + { "dart", "application/vnd.dart" }, + { "dataless", "application/vnd.fdsn.seed" }, + { "davmount", "application/davmount+xml" }, + { "dbk", "application/docbook+xml" }, + { "dcr", "application/x-director" }, + { "dcurl", "text/vnd.curl.dcurl" }, + { "dd2", "application/vnd.oma.dd2+xml" }, + { "ddd", "application/vnd.fujixerox.ddd" }, + { "deb", "application/x-debian-package" }, + { "def", "text/plain" }, + { "deploy", "application/octet-stream" }, + { "der", "application/x-x509-ca-cert" }, + { "dfac", "application/vnd.dreamfactory" }, + { "dgc", "application/x-dgc-compressed" }, + { "dic", "text/x-c" }, + { "dir", "application/x-director" }, + { "dis", "application/vnd.mobius.dis" }, + { "dist", "application/octet-stream" }, + { "distz", "application/octet-stream" }, + { "djv", "image/vnd.djvu" }, + { "djvu", "image/vnd.djvu" }, + { "dll", "application/x-msdownload" }, + { "dmg", "application/x-apple-diskimage" }, + { "dmp", "application/vnd.tcpdump.pcap" }, + { "dms", "application/octet-stream" }, + { "dna", "application/vnd.dna" }, + { "doc", "application/msword" }, + { "docm", "application/vnd.ms-word.document.macroenabled.12" }, + { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, + { "dot", "application/msword" }, + { "dotm", "application/vnd.ms-word.template.macroenabled.12" }, + { "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, + { "dp", "application/vnd.osgi.dp" }, + { "dpg", "application/vnd.dpgraph" }, + { "dra", "audio/vnd.dra" }, + { "dsc", "text/prs.lines.tag" }, + { "dssc", "application/dssc+der" }, + { "dtb", "application/x-dtbook+xml" }, + { "dtd", "application/xml-dtd" }, + { "dts", "audio/vnd.dts" }, + { "dtshd", "audio/vnd.dts.hd" }, + { "dump", "application/octet-stream" }, + { "dvb", "video/vnd.dvb.file" }, + { "dvi", "application/x-dvi" }, + { "dwf", "model/vnd.dwf" }, + { "dwg", "image/vnd.dwg" }, + { "dxf", "image/vnd.dxf" }, + { "dxp", "application/vnd.spotfire.dxp" }, + { "dxr", "application/x-director" }, + { "ecelp4800", "audio/vnd.nuera.ecelp4800" }, + { "ecelp7470", "audio/vnd.nuera.ecelp7470" }, + { "ecelp9600", "audio/vnd.nuera.ecelp9600" }, + { "ecma", "application/ecmascript" }, + { "edm", "application/vnd.novadigm.edm" }, + { "edx", "application/vnd.novadigm.edx" }, + { "efif", "application/vnd.picsel" }, + { "ei6", "application/vnd.pg.osasli" }, + { "elc", "application/octet-stream" }, + { "emf", "application/x-msmetafile" }, + { "eml", "message/rfc822" }, + { "emma", "application/emma+xml" }, + { "emz", "application/x-msmetafile" }, + { "eol", "audio/vnd.digital-winds" }, + { "eot", "application/vnd.ms-fontobject" }, + { "eps", "application/postscript" }, + { "epub", "application/epub+zip" }, + { "es3", "application/vnd.eszigno3+xml" }, + { "esa", "application/vnd.osgi.subsystem" }, + { "esf", "application/vnd.epson.esf" }, + { "et3", "application/vnd.eszigno3+xml" }, + { "etx", "text/x-setext" }, + { "eva", "application/x-eva" }, + { "evy", "application/x-envoy" }, + { "exe", "application/x-msdownload" }, + { "exi", "application/exi" }, + { "ext", "application/vnd.novadigm.ext" }, + { "ez", "application/andrew-inset" }, + { "ez2", "application/vnd.ezpix-album" }, + { "ez3", "application/vnd.ezpix-package" }, + { "f", "text/x-fortran" }, + { "f4v", "video/x-f4v" }, + { "f77", "text/x-fortran" }, + { "f90", "text/x-fortran" }, + { "fbs", "image/vnd.fastbidsheet" }, + { "fcdt", "application/vnd.adobe.formscentral.fcdt" }, + { "fcs", "application/vnd.isac.fcs" }, + { "fdf", "application/vnd.fdf" }, + { "fe_launch", "application/vnd.denovo.fcselayout-link" }, + { "fg5", "application/vnd.fujitsu.oasysgp" }, + { "fgd", "application/x-director" }, + { "fh", "image/x-freehand" }, + { "fh4", "image/x-freehand" }, + { "fh5", "image/x-freehand" }, + { "fh7", "image/x-freehand" }, + { "fhc", "image/x-freehand" }, + { "fig", "application/x-xfig" }, + { "flac", "audio/x-flac" }, + { "fli", "video/x-fli" }, + { "flo", "application/vnd.micrografx.flo" }, + { "flv", "video/x-flv" }, + { "flw", "application/vnd.kde.kivio" }, + { "flx", "text/vnd.fmi.flexstor" }, + { "fly", "text/vnd.fly" }, + { "fm", "application/vnd.framemaker" }, + { "fnc", "application/vnd.frogans.fnc" }, + { "for", "text/x-fortran" }, + { "fpx", "image/vnd.fpx" }, + { "frame", "application/vnd.framemaker" }, + { "fsc", "application/vnd.fsc.weblaunch" }, + { "fst", "image/vnd.fst" }, + { "ftc", "application/vnd.fluxtime.clip" }, + { "fti", "application/vnd.anser-web-funds-transfer-initiation" }, + { "fvt", "video/vnd.fvt" }, + { "fxp", "application/vnd.adobe.fxp" }, + { "fxpl", "application/vnd.adobe.fxp" }, + { "fzs", "application/vnd.fuzzysheet" }, + { "g2w", "application/vnd.geoplan" }, + { "g3", "image/g3fax" }, + { "g3w", "application/vnd.geospace" }, + { "gac", "application/vnd.groove-account" }, + { "gam", "application/x-tads" }, + { "gbr", "application/rpki-ghostbusters" }, + { "gca", "application/x-gca-compressed" }, + { "gdl", "model/vnd.gdl" }, + { "geo", "application/vnd.dynageo" }, + { "gex", "application/vnd.geometry-explorer" }, + { "ggb", "application/vnd.geogebra.file" }, + { "ggt", "application/vnd.geogebra.tool" }, + { "ghf", "application/vnd.groove-help" }, + { "gif", "image/gif" }, + { "gim", "application/vnd.groove-identity-message" }, + { "gml", "application/gml+xml" }, + { "gmx", "application/vnd.gmx" }, + { "gnumeric", "application/x-gnumeric" }, + { "gph", "application/vnd.flographit" }, + { "gpx", "application/gpx+xml" }, + { "gqf", "application/vnd.grafeq" }, + { "gqs", "application/vnd.grafeq" }, + { "gram", "application/srgs" }, + { "gramps", "application/x-gramps-xml" }, + { "gre", "application/vnd.geometry-explorer" }, + { "grv", "application/vnd.groove-injector" }, + { "grxml", "application/srgs+xml" }, + { "gsf", "application/x-font-ghostscript" }, + { "gtar", "application/x-gtar" }, + { "gtm", "application/vnd.groove-tool-message" }, + { "gtw", "model/vnd.gtw" }, + { "gv", "text/vnd.graphviz" }, + { "gxf", "application/gxf" }, + { "gxt", "application/vnd.geonext" }, + { "h", "text/x-c" }, + { "h261", "video/h261" }, + { "h263", "video/h263" }, + { "h264", "video/h264" }, + { "hal", "application/vnd.hal+xml" }, + { "hbci", "application/vnd.hbci" }, + { "hdf", "application/x-hdf" }, + { "hh", "text/x-c" }, + { "hlp", "application/winhlp" }, + { "hpgl", "application/vnd.hp-hpgl" }, + { "hpid", "application/vnd.hp-hpid" }, + { "hps", "application/vnd.hp-hps" }, + { "hqx", "application/mac-binhex40" }, + { "htke", "application/vnd.kenameaapp" }, + { "htm", "text/html" }, + { "html", "text/html" }, + { "hvd", "application/vnd.yamaha.hv-dic" }, + { "hvp", "application/vnd.yamaha.hv-voice" }, + { "hvs", "application/vnd.yamaha.hv-script" }, + { "i2g", "application/vnd.intergeo" }, + { "icc", "application/vnd.iccprofile" }, + { "ice", "x-conference/x-cooltalk" }, + { "icm", "application/vnd.iccprofile" }, + { "ico", "image/x-icon" }, + { "ics", "text/calendar" }, + { "ief", "image/ief" }, + { "ifb", "text/calendar" }, + { "ifm", "application/vnd.shana.informed.formdata" }, + { "iges", "model/iges" }, + { "igl", "application/vnd.igloader" }, + { "igm", "application/vnd.insors.igm" }, + { "igs", "model/iges" }, + { "igx", "application/vnd.micrografx.igx" }, + { "iif", "application/vnd.shana.informed.interchange" }, + { "imp", "application/vnd.accpac.simply.imp" }, + { "ims", "application/vnd.ms-ims" }, + { "in", "text/plain" }, + { "ink", "application/inkml+xml" }, + { "inkml", "application/inkml+xml" }, + { "install", "application/x-install-instructions" }, + { "iota", "application/vnd.astraea-software.iota" }, + { "ipfix", "application/ipfix" }, + { "ipk", "application/vnd.shana.informed.package" }, + { "irm", "application/vnd.ibm.rights-management" }, + { "irp", "application/vnd.irepository.package+xml" }, + { "iso", "application/x-iso9660-image" }, + { "itp", "application/vnd.shana.informed.formtemplate" }, + { "ivp", "application/vnd.immervision-ivp" }, + { "ivu", "application/vnd.immervision-ivu" }, + { "jad", "text/vnd.sun.j2me.app-descriptor" }, + { "jam", "application/vnd.jam" }, + { "jar", "application/java-archive" }, + { "java", "text/x-java-source" }, + { "jisp", "application/vnd.jisp" }, + { "jlt", "application/vnd.hp-jlyt" }, + { "jnlp", "application/x-java-jnlp-file" }, + { "joda", "application/vnd.joost.joda-archive" }, + { "jpe", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "jpg", "image/jpeg" }, + { "jpgm", "video/jpm" }, + { "jpgv", "video/jpeg" }, + { "jpm", "video/jpm" }, + { "js", "application/javascript" }, + { "json", "application/json" }, + { "jsonml", "application/jsonml+json" }, + { "kar", "audio/midi" }, + { "karbon", "application/vnd.kde.karbon" }, + { "kfo", "application/vnd.kde.kformula" }, + { "kia", "application/vnd.kidspiration" }, + { "kml", "application/vnd.google-earth.kml+xml" }, + { "kmz", "application/vnd.google-earth.kmz" }, + { "kne", "application/vnd.kinar" }, + { "knp", "application/vnd.kinar" }, + { "kon", "application/vnd.kde.kontour" }, + { "kpr", "application/vnd.kde.kpresenter" }, + { "kpt", "application/vnd.kde.kpresenter" }, + { "kpxx", "application/vnd.ds-keypoint" }, + { "ksp", "application/vnd.kde.kspread" }, + { "ktr", "application/vnd.kahootz" }, + { "ktx", "image/ktx" }, + { "ktz", "application/vnd.kahootz" }, + { "kwd", "application/vnd.kde.kword" }, + { "kwt", "application/vnd.kde.kword" }, + { "lasxml", "application/vnd.las.las+xml" }, + { "latex", "application/x-latex" }, + { "lbd", "application/vnd.llamagraphics.life-balance.desktop" }, + { "lbe", "application/vnd.llamagraphics.life-balance.exchange+xml" }, + { "les", "application/vnd.hhe.lesson-player" }, + { "lha", "application/x-lzh-compressed" }, + { "link66", "application/vnd.route66.link66+xml" }, + { "list", "text/plain" }, + { "list3820", "application/vnd.ibm.modcap" }, + { "listafp", "application/vnd.ibm.modcap" }, + { "lnk", "application/x-ms-shortcut" }, + { "log", "text/plain" }, + { "lostxml", "application/lost+xml" }, + { "lrf", "application/octet-stream" }, + { "lrm", "application/vnd.ms-lrm" }, + { "ltf", "application/vnd.frogans.ltf" }, + { "lvp", "audio/vnd.lucent.voice" }, + { "lwp", "application/vnd.lotus-wordpro" }, + { "lzh", "application/x-lzh-compressed" }, + { "m13", "application/x-msmediaview" }, + { "m14", "application/x-msmediaview" }, + { "m1v", "video/mpeg" }, + { "m21", "application/mp21" }, + { "m2a", "audio/mpeg" }, + { "m2v", "video/mpeg" }, + { "m3a", "audio/mpeg" }, + { "m3u", "audio/x-mpegurl" }, + { "m3u8", "application/vnd.apple.mpegurl" }, + { "m4a", "audio/mp4" }, + { "m4u", "video/vnd.mpegurl" }, + { "m4v", "video/x-m4v" }, + { "ma", "application/mathematica" }, + { "mads", "application/mads+xml" }, + { "mag", "application/vnd.ecowin.chart" }, + { "maker", "application/vnd.framemaker" }, + { "man", "text/troff" }, + { "mar", "application/octet-stream" }, + { "mathml", "application/mathml+xml" }, + { "mb", "application/mathematica" }, + { "mbk", "application/vnd.mobius.mbk" }, + { "mbox", "application/mbox" }, + { "mc1", "application/vnd.medcalcdata" }, + { "mcd", "application/vnd.mcd" }, + { "mcurl", "text/vnd.curl.mcurl" }, + { "mdb", "application/x-msaccess" }, + { "mdi", "image/vnd.ms-modi" }, + { "me", "text/troff" }, + { "mesh", "model/mesh" }, + { "meta4", "application/metalink4+xml" }, + { "metalink", "application/metalink+xml" }, + { "mets", "application/mets+xml" }, + { "mfm", "application/vnd.mfmp" }, + { "mft", "application/rpki-manifest" }, + { "mgp", "application/vnd.osgeo.mapguide.package" }, + { "mgz", "application/vnd.proteus.magazine" }, + { "mid", "audio/midi" }, + { "midi", "audio/midi" }, + { "mie", "application/x-mie" }, + { "mif", "application/vnd.mif" }, + { "mime", "message/rfc822" }, + { "mj2", "video/mj2" }, + { "mjp2", "video/mj2" }, + { "mk3d", "video/x-matroska" }, + { "mka", "audio/x-matroska" }, + { "mks", "video/x-matroska" }, + { "mkv", "video/x-matroska" }, + { "mlp", "application/vnd.dolby.mlp" }, + { "mmd", "application/vnd.chipnuts.karaoke-mmd" }, + { "mmf", "application/vnd.smaf" }, + { "mmr", "image/vnd.fujixerox.edmics-mmr" }, + { "mng", "video/x-mng" }, + { "mny", "application/x-msmoney" }, + { "mobi", "application/x-mobipocket-ebook" }, + { "mods", "application/mods+xml" }, + { "mov", "video/quicktime" }, + { "movie", "video/x-sgi-movie" }, + { "mp2", "audio/mpeg" }, + { "mp21", "application/mp21" }, + { "mp2a", "audio/mpeg" }, + { "mp3", "audio/mpeg" }, + { "mp4", "video/mp4" }, + { "mp4a", "audio/mp4" }, + { "mp4s", "application/mp4" }, + { "mp4v", "video/mp4" }, + { "mpc", "application/vnd.mophun.certificate" }, + { "mpe", "video/mpeg" }, + { "mpeg", "video/mpeg" }, + { "mpg", "video/mpeg" }, + { "mpg4", "video/mp4" }, + { "mpga", "audio/mpeg" }, + { "mpkg", "application/vnd.apple.installer+xml" }, + { "mpm", "application/vnd.blueice.multipass" }, + { "mpn", "application/vnd.mophun.application" }, + { "mpp", "application/vnd.ms-project" }, + { "mpt", "application/vnd.ms-project" }, + { "mpy", "application/vnd.ibm.minipay" }, + { "mqy", "application/vnd.mobius.mqy" }, + { "mrc", "application/marc" }, + { "mrcx", "application/marcxml+xml" }, + { "ms", "text/troff" }, + { "mscml", "application/mediaservercontrol+xml" }, + { "mseed", "application/vnd.fdsn.mseed" }, + { "mseq", "application/vnd.mseq" }, + { "msf", "application/vnd.epson.msf" }, + { "msh", "model/mesh" }, + { "msi", "application/x-msdownload" }, + { "msl", "application/vnd.mobius.msl" }, + { "msty", "application/vnd.muvee.style" }, + { "mts", "model/vnd.mts" }, + { "mus", "application/vnd.musician" }, + { "musicxml", "application/vnd.recordare.musicxml+xml" }, + { "mvb", "application/x-msmediaview" }, + { "mwf", "application/vnd.mfer" }, + { "mxf", "application/mxf" }, + { "mxl", "application/vnd.recordare.musicxml" }, + { "mxml", "application/xv+xml" }, + { "mxs", "application/vnd.triscape.mxs" }, + { "mxu", "video/vnd.mpegurl" }, + { "n3", "text/n3" }, + { "nb", "application/mathematica" }, + { "nbp", "application/vnd.wolfram.player" }, + { "nc", "application/x-netcdf" }, + { "ncx", "application/x-dtbncx+xml" }, + { "nfo", "text/x-nfo" }, + { "n-gage", "application/vnd.nokia.n-gage.symbian.install" }, + { "ngdat", "application/vnd.nokia.n-gage.data" }, + { "nitf", "application/vnd.nitf" }, + { "nlu", "application/vnd.neurolanguage.nlu" }, + { "nml", "application/vnd.enliven" }, + { "nnd", "application/vnd.noblenet-directory" }, + { "nns", "application/vnd.noblenet-sealer" }, + { "nnw", "application/vnd.noblenet-web" }, + { "npx", "image/vnd.net-fpx" }, + { "nsc", "application/x-conference" }, + { "nsf", "application/vnd.lotus-notes" }, + { "ntf", "application/vnd.nitf" }, + { "nzb", "application/x-nzb" }, + { "oa2", "application/vnd.fujitsu.oasys2" }, + { "oa3", "application/vnd.fujitsu.oasys3" }, + { "oas", "application/vnd.fujitsu.oasys" }, + { "obd", "application/x-msbinder" }, + { "obj", "application/x-tgif" }, + { "oda", "application/oda" }, + { "odb", "application/vnd.oasis.opendocument.database" }, + { "odc", "application/vnd.oasis.opendocument.chart" }, + { "odf", "application/vnd.oasis.opendocument.formula" }, + { "odft", "application/vnd.oasis.opendocument.formula-template" }, + { "odg", "application/vnd.oasis.opendocument.graphics" }, + { "odi", "application/vnd.oasis.opendocument.image" }, + { "odm", "application/vnd.oasis.opendocument.text-master" }, + { "odp", "application/vnd.oasis.opendocument.presentation" }, + { "ods", "application/vnd.oasis.opendocument.spreadsheet" }, + { "odt", "application/vnd.oasis.opendocument.text" }, + { "oga", "audio/ogg" }, + { "ogg", "audio/ogg" }, + { "ogv", "video/ogg" }, + { "ogx", "application/ogg" }, + { "omdoc", "application/omdoc+xml" }, + { "onepkg", "application/onenote" }, + { "onetmp", "application/onenote" }, + { "onetoc", "application/onenote" }, + { "onetoc2", "application/onenote" }, + { "opf", "application/oebps-package+xml" }, + { "opml", "text/x-opml" }, + { "oprc", "application/vnd.palm" }, + { "opus", "audio/ogg" }, + { "org", "application/vnd.lotus-organizer" }, + { "osf", "application/vnd.yamaha.openscoreformat" }, + { "osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml" }, + { "otc", "application/vnd.oasis.opendocument.chart-template" }, + { "otf", "font/otf" }, + { "otg", "application/vnd.oasis.opendocument.graphics-template" }, + { "oth", "application/vnd.oasis.opendocument.text-web" }, + { "oti", "application/vnd.oasis.opendocument.image-template" }, + { "otp", "application/vnd.oasis.opendocument.presentation-template" }, + { "ots", "application/vnd.oasis.opendocument.spreadsheet-template" }, + { "ott", "application/vnd.oasis.opendocument.text-template" }, + { "oxps", "application/oxps" }, + { "oxt", "application/vnd.openofficeorg.extension" }, + { "p", "text/x-pascal" }, + { "p10", "application/pkcs10" }, + { "p12", "application/x-pkcs12" }, + { "p7b", "application/x-pkcs7-certificates" }, + { "p7c", "application/pkcs7-mime" }, + { "p7m", "application/pkcs7-mime" }, + { "p7r", "application/x-pkcs7-certreqresp" }, + { "p7s", "application/pkcs7-signature" }, + { "p8", "application/pkcs8" }, + { "pas", "text/x-pascal" }, + { "paw", "application/vnd.pawaafile" }, + { "pbd", "application/vnd.powerbuilder6" }, + { "pbm", "image/x-portable-bitmap" }, + { "pcap", "application/vnd.tcpdump.pcap" }, + { "pcf", "application/x-font-pcf" }, + { "pcl", "application/vnd.hp-pcl" }, + { "pclxl", "application/vnd.hp-pclxl" }, + { "pct", "image/x-pict" }, + { "pcurl", "application/vnd.curl.pcurl" }, + { "pcx", "image/x-pcx" }, + { "pdb", "application/vnd.palm" }, + { "pdf", "application/pdf" }, + { "pfa", "application/x-font-type1" }, + { "pfb", "application/x-font-type1" }, + { "pfm", "application/x-font-type1" }, + { "pfr", "application/font-tdpfr" }, + { "pfx", "application/x-pkcs12" }, + { "pgm", "image/x-portable-graymap" }, + { "pgn", "application/x-chess-pgn" }, + { "pgp", "application/pgp-encrypted" }, + { "pic", "image/x-pict" }, + { "pkg", "application/octet-stream" }, + { "pki", "application/pkixcmp" }, + { "pkipath", "application/pkix-pkipath" }, + { "plb", "application/vnd.3gpp.pic-bw-large" }, + { "plc", "application/vnd.mobius.plc" }, + { "plf", "application/vnd.pocketlearn" }, + { "pls", "application/pls+xml" }, + { "pml", "application/vnd.ctc-posml" }, + { "png", "image/png" }, + { "pnm", "image/x-portable-anymap" }, + { "portpkg", "application/vnd.macports.portpkg" }, + { "pot", "application/vnd.ms-powerpoint" }, + { "potm", "application/vnd.ms-powerpoint.template.macroenabled.12" }, + { "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" }, + { "ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12" }, + { "ppd", "application/vnd.cups-ppd" }, + { "ppm", "image/x-portable-pixmap" }, + { "pps", "application/vnd.ms-powerpoint" }, + { "ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12" }, + { "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, + { "ppt", "application/vnd.ms-powerpoint" }, + { "pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12" }, + { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, + { "pqa", "application/vnd.palm" }, + { "prc", "application/x-mobipocket-ebook" }, + { "pre", "application/vnd.lotus-freelance" }, + { "prf", "application/pics-rules" }, + { "ps", "application/postscript" }, + { "psb", "application/vnd.3gpp.pic-bw-small" }, + { "psd", "image/vnd.adobe.photoshop" }, + { "psf", "application/x-font-linux-psf" }, + { "pskcxml", "application/pskc+xml" }, + { "ptid", "application/vnd.pvi.ptid1" }, + { "pub", "application/x-mspublisher" }, + { "pvb", "application/vnd.3gpp.pic-bw-var" }, + { "pwn", "application/vnd.3m.post-it-notes" }, + { "pya", "audio/vnd.ms-playready.media.pya" }, + { "pyv", "video/vnd.ms-playready.media.pyv" }, + { "qam", "application/vnd.epson.quickanime" }, + { "qbo", "application/vnd.intu.qbo" }, + { "qfx", "application/vnd.intu.qfx" }, + { "qps", "application/vnd.publishare-delta-tree" }, + { "qt", "video/quicktime" }, + { "qwd", "application/vnd.quark.quarkxpress" }, + { "qwt", "application/vnd.quark.quarkxpress" }, + { "qxb", "application/vnd.quark.quarkxpress" }, + { "qxd", "application/vnd.quark.quarkxpress" }, + { "qxl", "application/vnd.quark.quarkxpress" }, + { "qxt", "application/vnd.quark.quarkxpress" }, + { "ra", "audio/x-pn-realaudio" }, + { "ram", "audio/x-pn-realaudio" }, + { "rar", "application/x-rar-compressed" }, + { "ras", "image/x-cmu-raster" }, + { "rcprofile", "application/vnd.ipunplugged.rcprofile" }, + { "rdf", "application/rdf+xml" }, + { "rdz", "application/vnd.data-vision.rdz" }, + { "rep", "application/vnd.businessobjects" }, + { "res", "application/x-dtbresource+xml" }, + { "rgb", "image/x-rgb" }, + { "rif", "application/reginfo+xml" }, + { "rip", "audio/vnd.rip" }, + { "ris", "application/x-research-info-systems" }, + { "rl", "application/resource-lists+xml" }, + { "rlc", "image/vnd.fujixerox.edmics-rlc" }, + { "rld", "application/resource-lists-diff+xml" }, + { "rm", "application/vnd.rn-realmedia" }, + { "rmi", "audio/midi" }, + { "rmp", "audio/x-pn-realaudio-plugin" }, + { "rms", "application/vnd.jcp.javame.midlet-rms" }, + { "rmvb", "application/vnd.rn-realmedia-vbr" }, + { "rnc", "application/relax-ng-compact-syntax" }, + { "roa", "application/rpki-roa" }, + { "roff", "text/troff" }, + { "rp9", "application/vnd.cloanto.rp9" }, + { "rpss", "application/vnd.nokia.radio-presets" }, + { "rpst", "application/vnd.nokia.radio-preset" }, + { "rq", "application/sparql-query" }, + { "rs", "application/rls-services+xml" }, + { "rsd", "application/rsd+xml" }, + { "rss", "application/rss+xml" }, + { "rtf", "application/rtf" }, + { "rtx", "text/richtext" }, + { "s", "text/x-asm" }, + { "s3m", "audio/s3m" }, + { "saf", "application/vnd.yamaha.smaf-audio" }, + { "sbml", "application/sbml+xml" }, + { "sc", "application/vnd.ibm.secure-container" }, + { "scd", "application/x-msschedule" }, + { "scm", "application/vnd.lotus-screencam" }, + { "scq", "application/scvp-cv-request" }, + { "scs", "application/scvp-cv-response" }, + { "scurl", "text/vnd.curl.scurl" }, + { "sda", "application/vnd.stardivision.draw" }, + { "sdc", "application/vnd.stardivision.calc" }, + { "sdd", "application/vnd.stardivision.impress" }, + { "sdkd", "application/vnd.solent.sdkm+xml" }, + { "sdkm", "application/vnd.solent.sdkm+xml" }, + { "sdp", "application/sdp" }, + { "sdw", "application/vnd.stardivision.writer" }, + { "see", "application/vnd.seemail" }, + { "seed", "application/vnd.fdsn.seed" }, + { "sema", "application/vnd.sema" }, + { "semd", "application/vnd.semd" }, + { "semf", "application/vnd.semf" }, + { "ser", "application/java-serialized-object" }, + { "setpay", "application/set-payment-initiation" }, + { "setreg", "application/set-registration-initiation" }, + { "sfd-hdstx", "application/vnd.hydrostatix.sof-data" }, + { "sfs", "application/vnd.spotfire.sfs" }, + { "sfv", "text/x-sfv" }, + { "sgi", "image/sgi" }, + { "sgl", "application/vnd.stardivision.writer-global" }, + { "sgm", "text/sgml" }, + { "sgml", "text/sgml" }, + { "sh", "application/x-sh" }, + { "shar", "application/x-shar" }, + { "shf", "application/shf+xml" }, + { "sid", "image/x-mrsid-image" }, + { "sig", "application/pgp-signature" }, + { "sil", "audio/silk" }, + { "silo", "model/mesh" }, + { "sis", "application/vnd.symbian.install" }, + { "sisx", "application/vnd.symbian.install" }, + { "sit", "application/x-stuffit" }, + { "sitx", "application/x-stuffitx" }, + { "skd", "application/vnd.koan" }, + { "skm", "application/vnd.koan" }, + { "skp", "application/vnd.koan" }, + { "skt", "application/vnd.koan" }, + { "sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12" }, + { "sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide" }, + { "slt", "application/vnd.epson.salt" }, + { "sm", "application/vnd.stepmania.stepchart" }, + { "smf", "application/vnd.stardivision.math" }, + { "smi", "application/smil+xml" }, + { "smil", "application/smil+xml" }, + { "smv", "video/x-smv" }, + { "smzip", "application/vnd.stepmania.package" }, + { "snd", "audio/basic" }, + { "snf", "application/x-font-snf" }, + { "so", "application/octet-stream" }, + { "spc", "application/x-pkcs7-certificates" }, + { "spf", "application/vnd.yamaha.smaf-phrase" }, + { "spl", "application/x-futuresplash" }, + { "spot", "text/vnd.in3d.spot" }, + { "spp", "application/scvp-vp-response" }, + { "spq", "application/scvp-vp-request" }, + { "spx", "audio/ogg" }, + { "sql", "application/x-sql" }, + { "src", "application/x-wais-source" }, + { "srt", "application/x-subrip" }, + { "sru", "application/sru+xml" }, + { "srx", "application/sparql-results+xml" }, + { "ssdl", "application/ssdl+xml" }, + { "sse", "application/vnd.kodak-descriptor" }, + { "ssf", "application/vnd.epson.ssf" }, + { "ssml", "application/ssml+xml" }, + { "st", "application/vnd.sailingtracker.track" }, + { "stc", "application/vnd.sun.xml.calc.template" }, + { "std", "application/vnd.sun.xml.draw.template" }, + { "stf", "application/vnd.wt.stf" }, + { "sti", "application/vnd.sun.xml.impress.template" }, + { "stk", "application/hyperstudio" }, + { "stl", "application/vnd.ms-pki.stl" }, + { "str", "application/vnd.pg.format" }, + { "stw", "application/vnd.sun.xml.writer.template" }, + { "sus", "application/vnd.sus-calendar" }, + { "susp", "application/vnd.sus-calendar" }, + { "sv4cpio", "application/x-sv4cpio" }, + { "sv4crc", "application/x-sv4crc" }, + { "svc", "application/vnd.dvb.service" }, + { "svd", "application/vnd.svd" }, + { "svg", "image/svg+xml" }, + { "svgz", "image/svg+xml" }, + { "swa", "application/x-director" }, + { "swf", "application/x-shockwave-flash" }, + { "swi", "application/vnd.aristanetworks.swi" }, + { "sxc", "application/vnd.sun.xml.calc" }, + { "sxd", "application/vnd.sun.xml.draw" }, + { "sxg", "application/vnd.sun.xml.writer.global" }, + { "sxi", "application/vnd.sun.xml.impress" }, + { "sxm", "application/vnd.sun.xml.math" }, + { "sxw", "application/vnd.sun.xml.writer" }, + { "t", "text/troff" }, + { "t3", "application/x-t3vm-image" }, + { "taglet", "application/vnd.mynfc" }, + { "tao", "application/vnd.tao.intent-module-archive" }, + { "tar", "application/x-tar" }, + { "tcap", "application/vnd.3gpp2.tcap" }, + { "tcl", "application/x-tcl" }, + { "teacher", "application/vnd.smart.teacher" }, + { "tei", "application/tei+xml" }, + { "teicorpus", "application/tei+xml" }, + { "tex", "application/x-tex" }, + { "texi", "application/x-texinfo" }, + { "texinfo", "application/x-texinfo" }, + { "text", "text/plain" }, + { "tfi", "application/thraud+xml" }, + { "tfm", "application/x-tex-tfm" }, + { "tga", "image/x-tga" }, + { "thmx", "application/vnd.ms-officetheme" }, + { "tif", "image/tiff" }, + { "tiff", "image/tiff" }, + { "tmo", "application/vnd.tmobile-livetv" }, + { "torrent", "application/x-bittorrent" }, + { "tpl", "application/vnd.groove-tool-template" }, + { "tpt", "application/vnd.trid.tpt" }, + { "tr", "text/troff" }, + { "tra", "application/vnd.trueapp" }, + { "trm", "application/x-msterminal" }, + { "tsd", "application/timestamped-data" }, + { "tsv", "text/tab-separated-values" }, + { "ttc", "font/collection" }, + { "ttf", "font/ttf" }, + { "ttl", "text/turtle" }, + { "twd", "application/vnd.simtech-mindmapper" }, + { "twds", "application/vnd.simtech-mindmapper" }, + { "txd", "application/vnd.genomatix.tuxedo" }, + { "txf", "application/vnd.mobius.txf" }, + { "txt", "text/plain" }, + { "u32", "application/x-authorware-bin" }, + { "udeb", "application/x-debian-package" }, + { "ufd", "application/vnd.ufdl" }, + { "ufdl", "application/vnd.ufdl" }, + { "ulx", "application/x-glulx" }, + { "umj", "application/vnd.umajin" }, + { "unityweb", "application/vnd.unity" }, + { "uoml", "application/vnd.uoml+xml" }, + { "uri", "text/uri-list" }, + { "uris", "text/uri-list" }, + { "urls", "text/uri-list" }, + { "ustar", "application/x-ustar" }, + { "utz", "application/vnd.uiq.theme" }, + { "uu", "text/x-uuencode" }, + { "uva", "audio/vnd.dece.audio" }, + { "uvd", "application/vnd.dece.data" }, + { "uvf", "application/vnd.dece.data" }, + { "uvg", "image/vnd.dece.graphic" }, + { "uvh", "video/vnd.dece.hd" }, + { "uvi", "image/vnd.dece.graphic" }, + { "uvm", "video/vnd.dece.mobile" }, + { "uvp", "video/vnd.dece.pd" }, + { "uvs", "video/vnd.dece.sd" }, + { "uvt", "application/vnd.dece.ttml+xml" }, + { "uvu", "video/vnd.uvvu.mp4" }, + { "uvv", "video/vnd.dece.video" }, + { "uvva", "audio/vnd.dece.audio" }, + { "uvvd", "application/vnd.dece.data" }, + { "uvvf", "application/vnd.dece.data" }, + { "uvvg", "image/vnd.dece.graphic" }, + { "uvvh", "video/vnd.dece.hd" }, + { "uvvi", "image/vnd.dece.graphic" }, + { "uvvm", "video/vnd.dece.mobile" }, + { "uvvp", "video/vnd.dece.pd" }, + { "uvvs", "video/vnd.dece.sd" }, + { "uvvt", "application/vnd.dece.ttml+xml" }, + { "uvvu", "video/vnd.uvvu.mp4" }, + { "uvvv", "video/vnd.dece.video" }, + { "uvvx", "application/vnd.dece.unspecified" }, + { "uvvz", "application/vnd.dece.zip" }, + { "uvx", "application/vnd.dece.unspecified" }, + { "uvz", "application/vnd.dece.zip" }, + { "vcard", "text/vcard" }, + { "vcd", "application/x-cdlink" }, + { "vcf", "text/x-vcard" }, + { "vcg", "application/vnd.groove-vcard" }, + { "vcs", "text/x-vcalendar" }, + { "vcx", "application/vnd.vcx" }, + { "vis", "application/vnd.visionary" }, + { "viv", "video/vnd.vivo" }, + { "vob", "video/x-ms-vob" }, + { "vor", "application/vnd.stardivision.writer" }, + { "vox", "application/x-authorware-bin" }, + { "vrml", "model/vrml" }, + { "vsd", "application/vnd.visio" }, + { "vsf", "application/vnd.vsf" }, + { "vss", "application/vnd.visio" }, + { "vst", "application/vnd.visio" }, + { "vsw", "application/vnd.visio" }, + { "vtu", "model/vnd.vtu" }, + { "vxml", "application/voicexml+xml" }, + { "w3d", "application/x-director" }, + { "wad", "application/x-doom" }, + { "wav", "audio/x-wav" }, + { "wax", "audio/x-ms-wax" }, + { "wbmp", "image/vnd.wap.wbmp" }, + { "wbs", "application/vnd.criticaltools.wbs+xml" }, + { "wbxml", "application/vnd.wap.wbxml" }, + { "wcm", "application/vnd.ms-works" }, + { "wdb", "application/vnd.ms-works" }, + { "wdp", "image/vnd.ms-photo" }, + { "weba", "audio/webm" }, + { "webm", "video/webm" }, + { "webp", "image/webp" }, + { "wg", "application/vnd.pmi.widget" }, + { "wgt", "application/widget" }, + { "wks", "application/vnd.ms-works" }, + { "wm", "video/x-ms-wm" }, + { "wma", "audio/x-ms-wma" }, + { "wmd", "application/x-ms-wmd" }, + { "wmf", "application/x-msmetafile" }, + { "wml", "text/vnd.wap.wml" }, + { "wmlc", "application/vnd.wap.wmlc" }, + { "wmls", "text/vnd.wap.wmlscript" }, + { "wmlsc", "application/vnd.wap.wmlscriptc" }, + { "wmv", "video/x-ms-wmv" }, + { "wmx", "video/x-ms-wmx" }, + { "woff", "font/woff" }, + { "woff2", "font/woff2" }, + { "wpd", "application/vnd.wordperfect" }, + { "wpl", "application/vnd.ms-wpl" }, + { "wps", "application/vnd.ms-works" }, + { "wqd", "application/vnd.wqd" }, + { "wri", "application/x-mswrite" }, + { "wrl", "model/vrml" }, + { "wsdl", "application/wsdl+xml" }, + { "wspolicy", "application/wspolicy+xml" }, + { "wtb", "application/vnd.webturbo" }, + { "wvx", "video/x-ms-wvx" }, + { "x32", "application/x-authorware-bin" }, + { "x3d", "model/x3d+xml" }, + { "x3db", "model/x3d+binary" }, + { "x3dbz", "model/x3d+binary" }, + { "x3dv", "model/x3d+vrml" }, + { "x3dvz", "model/x3d+vrml" }, + { "x3dz", "model/x3d+xml" }, + { "xaml", "application/xaml+xml" }, + { "xap", "application/x-silverlight-app" }, + { "xar", "application/vnd.xara" }, + { "xbap", "application/x-ms-xbap" }, + { "xbd", "application/vnd.fujixerox.docuworks.binder" }, + { "xbm", "image/x-xbitmap" }, + { "xdf", "application/xcap-diff+xml" }, + { "xdm", "application/vnd.syncml.dm+xml" }, + { "xdp", "application/vnd.adobe.xdp+xml" }, + { "xdssc", "application/dssc+xml" }, + { "xdw", "application/vnd.fujixerox.docuworks" }, + { "xenc", "application/xenc+xml" }, + { "xer", "application/patch-ops-error+xml" }, + { "xfdf", "application/vnd.adobe.xfdf" }, + { "xfdl", "application/vnd.xfdl" }, + { "xht", "application/xhtml+xml" }, + { "xhtml", "application/xhtml+xml" }, + { "xhvml", "application/xv+xml" }, + { "xif", "image/vnd.xiff" }, + { "xla", "application/vnd.ms-excel" }, + { "xlam", "application/vnd.ms-excel.addin.macroenabled.12" }, + { "xlc", "application/vnd.ms-excel" }, + { "xlf", "application/x-xliff+xml" }, + { "xlm", "application/vnd.ms-excel" }, + { "xls", "application/vnd.ms-excel" }, + { "xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12" }, + { "xlsm", "application/vnd.ms-excel.sheet.macroenabled.12" }, + { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, + { "xlt", "application/vnd.ms-excel" }, + { "xltm", "application/vnd.ms-excel.template.macroenabled.12" }, + { "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" }, + { "xlw", "application/vnd.ms-excel" }, + { "xm", "audio/xm" }, + { "xml", "application/xml" }, + { "xo", "application/vnd.olpc-sugar" }, + { "xop", "application/xop+xml" }, + { "xpi", "application/x-xpinstall" }, + { "xpl", "application/xproc+xml" }, + { "xpm", "image/x-xpixmap" }, + { "xpr", "application/vnd.is-xpr" }, + { "xps", "application/vnd.ms-xpsdocument" }, + { "xpw", "application/vnd.intercon.formnet" }, + { "xpx", "application/vnd.intercon.formnet" }, + { "xsl", "application/xml" }, + { "xslt", "application/xslt+xml" }, + { "xsm", "application/vnd.syncml+xml" }, + { "xspf", "application/xspf+xml" }, + { "xul", "application/vnd.mozilla.xul+xml" }, + { "xvm", "application/xv+xml" }, + { "xvml", "application/xv+xml" }, + { "xwd", "image/x-xwindowdump" }, + { "xyz", "chemical/x-xyz" }, + { "xz", "application/x-xz" }, + { "yang", "application/yang" }, + { "yin", "application/yin+xml" }, + { "z1", "application/x-zmachine" }, + { "z2", "application/x-zmachine" }, + { "z3", "application/x-zmachine" }, + { "z4", "application/x-zmachine" }, + { "z5", "application/x-zmachine" }, + { "z6", "application/x-zmachine" }, + { "z7", "application/x-zmachine" }, + { "z8", "application/x-zmachine" }, + { "zaz", "application/vnd.zzazz.deck+xml" }, + { "zip", "application/zip" }, + { "zir", "application/vnd.zul" }, + { "zirz", "application/vnd.zul" }, + { "zmm", "application/vnd.handheld-entertainment+xml" }, + }; + } + + /// + /// Gets the MIME-type for the given file name, + /// or if a mapping doesn't exist. + /// + /// The name of the file. + /// The MIME-type for the given file name. + public static string GetMimeType(string fileName) + { + var dotIndex = fileName.LastIndexOf('.'); + + return dotIndex != -1 && + fileName.Length > dotIndex + 1 && + TypeMap.TryGetValue(fileName.Substring(dotIndex + 1), out var result) + ? result + : FallbackMimeType; + } + } +} diff --git a/NaiveHttpServer/MimeTypes.tt b/NaiveHttpServer/MimeTypes.tt new file mode 100644 index 0000000..567b681 --- /dev/null +++ b/NaiveHttpServer/MimeTypes.tt @@ -0,0 +1,95 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Net" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +<# var mediaTypes = GetMediaTypeList(); #> +// + +namespace <#=System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint").ToString()#> +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.CompilerServices; + + /// + /// Provides utilities for mapping file names and extensions to MIME-types. + /// + [CompilerGenerated] + [DebuggerNonUserCode] + public static class MimeTypes + { + /// + /// The fallback MIME-type. Defaults to application/octet-stream. + /// + public static string FallbackMimeType { get; set; } + + private static readonly Dictionary TypeMap; + + static MimeTypes() + { + FallbackMimeType = "application/octet-stream"; + + TypeMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + <# foreach (var mediaType in mediaTypes) { #> + { "<#= mediaType.Item1 #>", "<#= mediaType.Item2 #>" }, + <# } #> + }; + } + + /// + /// Gets the MIME-type for the given file name, + /// or if a mapping doesn't exist. + /// + /// The name of the file. + /// The MIME-type for the given file name. + public static string GetMimeType(string fileName) + { + var dotIndex = fileName.LastIndexOf('.'); + + return dotIndex != -1 && + fileName.Length > dotIndex + 1 && + TypeMap.TryGetValue(fileName.Substring(dotIndex + 1), out var result) + ? result + : FallbackMimeType; + } + } +} +<#+ + private static IList> GetMediaTypeList() + { + using (var client = new WebClient()) + { + var list = client.DownloadString(new Uri("http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types")); + + var lines = SplitString(list, '\r', '\n'); + + return GetMediaTypes(lines).ToList(); + } + } + + private static IEnumerable> GetMediaTypes(IEnumerable lines) + { + return lines.Where(x => !x.StartsWith("#")) + .Select(line => SplitString(line, '\t', ' ')) + .SelectMany(CreateMediaTypes) + .GroupBy(x => x.Item1) + .Where(x => x.Count() == 1) + .Select(x => x.Single()) + .OrderBy(x => x.Item1); + } + + private static string[] SplitString(string line, params char[] separator) + { + return line.Split(separator, StringSplitOptions.RemoveEmptyEntries); + } + + private static IEnumerable> CreateMediaTypes(string[] parts) + { + return parts.Skip(1).Select(extension => Tuple.Create(extension, parts.First())); + } +#> diff --git a/NaiveHttpServer/NaiveHttpServer.csproj b/NaiveHttpServer/NaiveHttpServer.csproj new file mode 100644 index 0000000..93a9f60 --- /dev/null +++ b/NaiveHttpServer/NaiveHttpServer.csproj @@ -0,0 +1,50 @@ + + + + netstandard2.0;net462 + 1.0.0 + latest + true + true + enable + + true + Dingping Zhang + Copyright (c) 2020-2021 Dingping Zhang + https://github.com/DingpingZhang/NaiveHttpServer + https://github.com/DingpingZhang/NaiveHttpServer + true + MIT + A simple C# http server based on the HttpListener. + http;http-server;file-server;simple-http-server + + + + + + + + + + + + + + TextTemplatingFileGenerator + MimeTypes.cs + + + + + + + + + + True + True + MimeTypes.tt + + + + diff --git a/NaiveHttpServer/NetAclChecker.cs b/NaiveHttpServer/NetAclChecker.cs new file mode 100644 index 0000000..17a3877 --- /dev/null +++ b/NaiveHttpServer/NetAclChecker.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; + +namespace NaiveHttpServer +{ + public static class NetAclChecker + { + public static ILogger? Logger { get; set; } + + public static void AddAddress(string address) + { + AddAddress(address, Environment.UserDomainName, Environment.UserName); + } + + public static void AddAddress(string address, string domain, string user) + { + string args = $@"http add urlacl url={address}, user={domain}\{user}"; + + try + { + ProcessStartInfo processStartInfo = new("netsh", args) + { + Verb = "runas", + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + }; + + var process = Process.Start(processStartInfo); + process?.WaitForExit(); + } + catch (Win32Exception e) + { + if (e.NativeErrorCode == 1223) + { + Logger?.Info("User canceled the operation by rejected the UAC."); + } + else + { + Logger?.Warning($"Failed to 'netsh http add urlacl {address}' with an {nameof(Win32Exception)}.", e); + } + } + catch (Exception e) + { + Logger?.Warning($"Failed to 'netsh http add urlacl {address}'.", e); + } + } + } +} diff --git a/NaiveHttpServer/RouterBuilder.cs b/NaiveHttpServer/RouterBuilder.cs new file mode 100644 index 0000000..156e4e7 --- /dev/null +++ b/NaiveHttpServer/RouterBuilder.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; + +namespace NaiveHttpServer +{ + public class RouterBuilder : IRouterBuilder + { + private static readonly Regex PathParameterRegex = new("(?<=/):(.+?)(?:(?=/)|$)", RegexOptions.Compiled); + private static readonly char[] Separator = { '/' }; + + private readonly List<(string url, Func handler)> _getRoutes = new(); + private readonly List<(string url, Func handler)> _postRoutes = new(); + private readonly List<(string url, Func handler)> _deleteRoutes = new(); + private readonly List<(string url, Func handler)> _putRoutes = new(); + + public IRouterBuilder Get(string url, Func handler) + { + _getRoutes.Add((url, handler)); + return this; + } + + public IRouterBuilder Post(string url, Func handler) + { + _postRoutes.Add((url, handler)); + return this; + } + + public IRouterBuilder Delete(string url, Func handler) + { + _deleteRoutes.Add((url, handler)); + return this; + } + + public IRouterBuilder Put(string url, Func handler) + { + _putRoutes.Add((url, handler)); + return this; + } + + public Middleware Build() + { + var getRoutes = GenerateRegexRoutes(_getRoutes); + var postRoutes = GenerateRegexRoutes(_postRoutes); + var deleteRoutes = GenerateRegexRoutes(_deleteRoutes); + var putRoutes = GenerateRegexRoutes(_putRoutes); + + return async (ctx, next) => + { + bool handled = ctx.Request.HttpMethod.ToUpperInvariant() switch + { + HttpMethods.Get => await TryMatch(getRoutes, ctx), + HttpMethods.Post => await TryMatch(postRoutes, ctx), + HttpMethods.Delete => await TryMatch(deleteRoutes, ctx), + HttpMethods.Put => await TryMatch(putRoutes, ctx), + _ => false, + }; + + if (!handled) + { + await next(); + } + }; + } + + private static IReadOnlyList<(Regex regex, Func handler)> GenerateRegexRoutes(IEnumerable<(string url, Func handler)> routes) + { + var toSortRoutes = routes + .Select(item => ( + fragmentCount: item.url.Split(Separator, StringSplitOptions.RemoveEmptyEntries).Length, + item.url, + item.handler)) + .ToList(); + toSortRoutes.Sort((x, y) => y.fragmentCount - x.fragmentCount); + + return toSortRoutes + .Select(item => (regex: GetPathRegex(item.url), item.handler)) + .ToList(); + } + + private static Regex GetPathRegex(string url) + { + HashSet parameterNames = new(); + string urlRegex = PathParameterRegex + .Replace(url.Trim('/'), match => + { + if (!parameterNames.Add(match.Value)) + { + throw new ArgumentException($"Cannot contains duplicate variable name: '{match.Value}'.", nameof(url)); + } + + return $"(?<{match.Groups[1]}>.+?)"; + }); + + return new Regex($"{urlRegex}$", RegexOptions.Compiled); + } + + private static async Task TryMatch(IEnumerable<(Regex regex, Func handler)> routes, Context ctx) + { + string requestPath = ctx.Request.Url.LocalPath.ToLowerInvariant(); + + foreach ((Regex regex, Func handler) in routes) + { + Match match = regex.Match(requestPath); + if (match.Success) + { + NameValueCollection query = HttpUtility.ParseQueryString(ctx.Request.Url.Query, Encoding.UTF8); + + ctx.TryGetParameter = (string key, out string value) => + { + Group group = match.Groups[key]; + value = HttpUtility.UrlDecode(group.Value); + + if (!group.Success) + { + value = query.Get(key); + } + + return !string.IsNullOrEmpty(value); + }; + + await handler(ctx); + return true; + } + } + + return false; + } + } +} diff --git a/NaiveHttpServer/Server.cs b/NaiveHttpServer/Server.cs new file mode 100644 index 0000000..8d4b16e --- /dev/null +++ b/NaiveHttpServer/Server.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; + +namespace NaiveHttpServer +{ + public class Server + { + private ILogger _logger = new DefaultLogger(); + private HttpListener? _listener; + private Middleware _middleware = Middlewares.Empty(); + + public bool IsRunning => _listener is { IsListening: true }; + + public string HostUrl { get; } + + public Server(string host, int port) + { + HostUrl = $"http://{host}:{port}/"; + } + + public Server Use(ILogger logger) + { + _logger = logger; + return this; + } + + public Server Use(Middleware middleware) + { + _middleware = _middleware.Then(middleware); + return this; + } + + public bool Start() + { + try + { + StartHttpListener(); + return true; + } + catch (HttpListenerException e) + { + _logger.Warning("Failed to start HttpListener.", e); + if (e.ErrorCode == 5) + { + NetAclChecker.AddAddress(HostUrl); + StartHttpListener(); + return true; + } + + return false; + } + } + + public void Stop() + { + if (_listener is { IsListening: true }) + { + _listener.Stop(); + _logger.Info("Http server has been stopped."); + } + } + + private void StartHttpListener() + { + _listener = new HttpListener(); + _listener.Prefixes.Add(HostUrl); + _listener.Start(); + + AsyncProcessRequest(); + + _logger.Info($"Http server has started listening: {HostUrl}..."); + } + + private void AsyncProcessRequest() + { + Task.Run(async () => + { + while (_listener!.IsListening) + { + try + { + HttpListenerContext context = await _listener.GetContextAsync(); + Context ctx = new(context.Request, context.Response, _logger); + +#pragma warning disable 4014 + _middleware.Run(ctx); +#pragma warning restore 4014 + } + catch (IOException e) + { + _logger.Warning(nameof(IOException), e); + } + catch (HttpListenerException e) + { + const int errorOperationAborted = 995; + if (e.ErrorCode == errorOperationAborted) + { + // The IO operation has been aborted because of either a thread exit or an application request. + break; + } + + _logger.Warning(nameof(HttpListenerException), e); + } + catch (InvalidOperationException e) + { + _logger.Warning(nameof(InvalidOperationException), e); + } + } + }); + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..799865f --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Credits + +[NaiveHttpServer](https://github.com/DingpingZhang/NaiveHttpServer) - A simple library wrapper around HttpListener to build minimal HttpServer +[Handlebars.Net](https://github.com/Handlebars-Net/Handlebars.Net) - Handlebars port to .NET