├── .editorconfig ├── .github ├── .changelog-config.json └── workflows │ ├── generate-changelog.yml │ ├── pr-checks.yml │ └── update-dependencies.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── NuGet.Config ├── README.md ├── assets ├── nf-logo.png └── readme.txt ├── azure-pipelines.yml ├── doc └── POSTcapture.jpg ├── key.snk ├── nanoFramework.WebServer.FileSystem.nuspec ├── nanoFramework.WebServer.FileSystem ├── Properties │ └── AssemblyInfo.cs ├── nanoFramework.WebServer.FileSystem.nfproj ├── packages.config └── packages.lock.json ├── nanoFramework.WebServer.nuspec ├── nanoFramework.WebServer.sln ├── nanoFramework.WebServer ├── Authentication.cs ├── AuthenticationAttribute.cs ├── AuthenticationType.cs ├── CallbackRoutes.cs ├── CaseSensitiveAttribute.cs ├── Header.cs ├── HttpListenerRequestExtensions.cs ├── HttpMultipartParser │ ├── FilePart.cs │ ├── HashtableUtility.cs │ ├── HeaderUtility.cs │ ├── LineBuffer.cs │ ├── LineReader.cs │ ├── MultipartFormDataParser.cs │ ├── MultipartFormDataParserException.cs │ └── ParameterPart.cs ├── HttpProtocol.cs ├── MethodAttribute.cs ├── Properties │ └── AssemblyInfo.cs ├── RouteAttribute.cs ├── UrlParameter.cs ├── WebServer.cs ├── WebServerEventArgs.cs ├── WebServerStatus.cs ├── WebServerStatusEventArgs.cs ├── nanoFramework.WebServer.nfproj ├── packages.config └── packages.lock.json ├── spelling_exclusion.dic ├── template.vssettings ├── tests ├── WebServerE2ETests │ ├── .gitignore │ ├── AuthController.cs │ ├── MixedController.cs │ ├── PostPutController.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SimpleRouteController.cs │ ├── Template │ │ ├── .gitignore │ │ └── WiFi.cs │ ├── WebServerE2ETests.nfproj │ ├── nanoFramework WebServer E2E Tests.postman_collection.json │ ├── packages.config │ ├── packages.lock.json │ └── requests.http └── nanoFramework.WebServer.Tests │ ├── HttpMultipartParser │ ├── FormDataProvider.cs │ ├── MultipartFormDataHeaderTests.cs │ └── MultipartFormDataParserTests.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── WebServerTests.cs │ ├── nano.runsettings │ ├── nanoFramework.WebServer.Tests.nfproj │ ├── packages.config │ └── packages.lock.json └── version.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig for Visual Studio 2022: https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022 2 | 3 | # This is a top-most .editorconfig file 4 | root = true 5 | 6 | #===================================================== 7 | # 8 | # nanoFramework specific settings 9 | # 10 | # 11 | #===================================================== 12 | [*] 13 | # Generic EditorConfig settings 14 | end_of_line = crlf 15 | charset = utf-8-bom 16 | 17 | # Visual Studio spell checker 18 | spelling_languages = en-us 19 | spelling_checkable_types = strings,identifiers,comments 20 | spelling_error_severity = information 21 | spelling_exclusion_path = spelling_exclusion.dic 22 | 23 | #===================================================== 24 | # 25 | # Settings copied from the .NET runtime 26 | # 27 | # https://github.com/dotnet/runtime 28 | # 29 | #===================================================== 30 | # Default settings: 31 | # A newline ending every file 32 | # Use 4 spaces as indentation 33 | insert_final_newline = true 34 | indent_style = space 35 | indent_size = 4 36 | trim_trailing_whitespace = true 37 | 38 | # Generated code 39 | [*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] 40 | generated_code = true 41 | 42 | # C# files 43 | [*.cs] 44 | # New line preferences 45 | csharp_new_line_before_open_brace = all 46 | csharp_new_line_before_else = true 47 | csharp_new_line_before_catch = true 48 | csharp_new_line_before_finally = true 49 | csharp_new_line_before_members_in_object_initializers = true 50 | csharp_new_line_before_members_in_anonymous_types = true 51 | csharp_new_line_between_query_expression_clauses = true 52 | 53 | # Indentation preferences 54 | csharp_indent_block_contents = true 55 | csharp_indent_braces = false 56 | csharp_indent_case_contents = true 57 | csharp_indent_case_contents_when_block = false 58 | csharp_indent_switch_labels = true 59 | csharp_indent_labels = one_less_than_current 60 | 61 | # Modifier preferences 62 | csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion 63 | 64 | # avoid this. unless absolutely necessary 65 | dotnet_style_qualification_for_field = false:suggestion 66 | dotnet_style_qualification_for_property = false:suggestion 67 | dotnet_style_qualification_for_method = false:suggestion 68 | dotnet_style_qualification_for_event = false:suggestion 69 | 70 | # Types: use keywords instead of BCL types, and permit var only when the type is clear 71 | csharp_style_var_for_built_in_types = false:suggestion 72 | csharp_style_var_when_type_is_apparent = false:none 73 | csharp_style_var_elsewhere = false:suggestion 74 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 75 | dotnet_style_predefined_type_for_member_access = true:suggestion 76 | 77 | # name all constant fields using PascalCase 78 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 79 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 80 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 81 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 82 | dotnet_naming_symbols.constant_fields.required_modifiers = const 83 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 84 | 85 | # static fields should have s_ prefix 86 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion 87 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields 88 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style 89 | dotnet_naming_symbols.static_fields.applicable_kinds = field 90 | dotnet_naming_symbols.static_fields.required_modifiers = static 91 | dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected 92 | dotnet_naming_style.static_prefix_style.required_prefix = s_ 93 | dotnet_naming_style.static_prefix_style.capitalization = camel_case 94 | 95 | # internal and private fields should be _camelCase 96 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 97 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 98 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 99 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 100 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 101 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 102 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 103 | 104 | # Code style defaults 105 | csharp_using_directive_placement = outside_namespace:suggestion 106 | dotnet_sort_system_directives_first = true 107 | csharp_prefer_braces = true:silent 108 | csharp_preserve_single_line_blocks = true:none 109 | csharp_preserve_single_line_statements = false:none 110 | csharp_prefer_static_local_function = true:suggestion 111 | csharp_prefer_simple_using_statement = false:none 112 | csharp_style_prefer_switch_expression = true:suggestion 113 | dotnet_style_readonly_field = true:suggestion 114 | 115 | # Expression-level preferences 116 | dotnet_style_object_initializer = true:suggestion 117 | dotnet_style_collection_initializer = true:suggestion 118 | dotnet_style_prefer_collection_expression = when_types_exactly_match 119 | dotnet_style_explicit_tuple_names = true:suggestion 120 | dotnet_style_coalesce_expression = true:suggestion 121 | dotnet_style_null_propagation = true:suggestion 122 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 123 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 124 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 125 | dotnet_style_prefer_auto_properties = true:suggestion 126 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 127 | dotnet_style_prefer_conditional_expression_over_return = true:silent 128 | csharp_prefer_simple_default_expression = true:suggestion 129 | 130 | # Expression-bodied members 131 | csharp_style_expression_bodied_methods = true:silent 132 | csharp_style_expression_bodied_constructors = true:silent 133 | csharp_style_expression_bodied_operators = true:silent 134 | csharp_style_expression_bodied_properties = true:silent 135 | csharp_style_expression_bodied_indexers = true:silent 136 | csharp_style_expression_bodied_accessors = true:silent 137 | csharp_style_expression_bodied_lambdas = true:silent 138 | csharp_style_expression_bodied_local_functions = true:silent 139 | 140 | # Pattern matching 141 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 142 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 143 | csharp_style_inlined_variable_declaration = true:suggestion 144 | 145 | # Null checking preferences 146 | csharp_style_throw_expression = true:suggestion 147 | csharp_style_conditional_delegate_call = true:suggestion 148 | 149 | # Other features 150 | csharp_style_prefer_index_operator = false:none 151 | csharp_style_prefer_range_operator = false:none 152 | csharp_style_pattern_local_over_anonymous_function = false:none 153 | 154 | # Space preferences 155 | csharp_space_after_cast = false 156 | csharp_space_after_colon_in_inheritance_clause = true 157 | csharp_space_after_comma = true 158 | csharp_space_after_dot = false 159 | csharp_space_after_keywords_in_control_flow_statements = true 160 | csharp_space_after_semicolon_in_for_statement = true 161 | csharp_space_around_binary_operators = before_and_after 162 | csharp_space_around_declaration_statements = do_not_ignore 163 | csharp_space_before_colon_in_inheritance_clause = true 164 | csharp_space_before_comma = false 165 | csharp_space_before_dot = false 166 | csharp_space_before_open_square_brackets = false 167 | csharp_space_before_semicolon_in_for_statement = false 168 | csharp_space_between_empty_square_brackets = false 169 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 170 | csharp_space_between_method_call_name_and_opening_parenthesis = false 171 | csharp_space_between_method_call_parameter_list_parentheses = false 172 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 173 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 174 | csharp_space_between_method_declaration_parameter_list_parentheses = false 175 | csharp_space_between_parentheses = false 176 | csharp_space_between_square_brackets = false 177 | 178 | # License header 179 | file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. 180 | 181 | # C++ Files 182 | [*.{cpp,h,in}] 183 | curly_bracket_next_line = true 184 | indent_brace_style = Allman 185 | 186 | # Xml project files 187 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] 188 | indent_size = 2 189 | 190 | [*.{csproj,vbproj,proj,nativeproj,locproj}] 191 | charset = utf-8 192 | 193 | # Xml build files 194 | [*.builds] 195 | indent_size = 2 196 | 197 | # Xml files 198 | [*.{xml,stylecop,resx,ruleset}] 199 | indent_size = 2 200 | 201 | # Xml config files 202 | [*.{props,targets,config,nuspec}] 203 | indent_size = 2 204 | 205 | # YAML config files 206 | [*.{yml,yaml}] 207 | indent_size = 2 208 | 209 | # Shell scripts 210 | [*.sh] 211 | end_of_line = lf 212 | [*.{cmd,bat}] 213 | end_of_line = crlf -------------------------------------------------------------------------------- /.github/.changelog-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## New Features and enhancements", 5 | "labels": [ 6 | "Type: enhancement" 7 | ], 8 | "exhaustive": true 9 | }, 10 | { 11 | "title": "## Bug Fixes", 12 | "labels": [ 13 | "Type: bug" 14 | ], 15 | "exhaustive": true 16 | }, 17 | { 18 | "title": "## Documentation", 19 | "labels": [ 20 | "Type: documentation" 21 | ], 22 | "exhaustive": true 23 | }, 24 | { 25 | "title": "## ⚠️ Breaking Changes", 26 | "labels": [ 27 | "Breaking-Change" 28 | ], 29 | "exhaustive": true 30 | }, 31 | { 32 | "title": "## Updated dependencies", 33 | "labels": [ 34 | "Type: dependencies" 35 | ], 36 | "exhaustive": true 37 | } 38 | ], 39 | "sort": "ASC", 40 | "template": "${{CHANGELOG}}\n\n**Full Changelog:** ${{RELEASE_DIFF}}\n\nThe following NuGet packages are available from this release:\n\n:package: [nanoFramework.WebServer](https://www.nuget.org/packages/nanoFramework.WebServer/)\n:package: [nanoFramework.WebServer.FileSystem](https://www.nuget.org/packages/nanoFramework.WebServer.FileSystem/)", 41 | "pr_template": "* ${{TITLE}} by @${{AUTHOR}} in #${{NUMBER}}", 42 | "empty_template": "- no changes", 43 | "max_tags_to_fetch": 200, 44 | "max_pull_requests": 200 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/generate-changelog.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) .NET Foundation and Contributors 2 | # See LICENSE file in the project root for full license information. 3 | 4 | name: Generate Changelog 5 | run-name: Generate changelog 6 | 7 | on: 8 | push: 9 | tags: 10 | - '*' 11 | 12 | jobs: 13 | compose_changelog: 14 | name: nanoFramework 15 | uses: nanoframework/nf-tools/.github/workflows/generate-changelog.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) .NET Foundation and Contributors 2 | # See LICENSE file in the project root for full license information. 3 | 4 | name: PR Checks 5 | 6 | on: 7 | pull_request: 8 | 9 | jobs: 10 | check_package_lock: 11 | name: nanoFramework 12 | uses: nanoframework/nf-tools/.github/workflows/check-package-lock.yml@main 13 | check_nuget_latest: 14 | name: nanoFramework 15 | uses: nanoframework/nf-tools/.github/workflows/check-packages-updated.yml@main 16 | secrets: inherit 17 | with: 18 | solution: 'nanoFramework.WebServer.sln' 19 | -------------------------------------------------------------------------------- /.github/workflows/update-dependencies.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) .NET Foundation and Contributors 2 | # See LICENSE file in the project root for full license information. 3 | 4 | # This workflow will periodically check .NET nanoFramework dependencies and updates them in the repository it's running. 5 | 6 | name: Daily update dependencies 7 | 8 | on: 9 | schedule: 10 | # At 00:10 UTC . 11 | - cron: '10 00 * * Wed,Fri' 12 | repository_dispatch: 13 | types: update-dependencies 14 | 15 | jobs: 16 | update-dependencies: 17 | name: nanoFramework 18 | uses: nanoframework/nf-tools/.github/workflows/update-dependencies.yml@main 19 | secrets: inherit 20 | with: 21 | solutionsToCheck: 'nanoFramework.WebServer.sln' 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | *.VC.db 83 | *.VC.VC.opendb 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | # TODO: Comment the next line if you want to checkin your web deploy settings 143 | # but database connection strings (with potential passwords) will be unencrypted 144 | *.pubxml 145 | *.publishproj 146 | 147 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 148 | # checkin your Azure Web App publish settings, but sensitive information contained 149 | # in these scripts will be unencrypted 150 | PublishScripts/ 151 | 152 | # NuGet Packages 153 | *.nupkg 154 | # The packages folder can be ignored because of Package Restore 155 | **/packages/* 156 | # except build/, which is used as an MSBuild target. 157 | !**/packages/build/ 158 | # Uncomment if necessary however generally it will be regenerated when needed 159 | #!**/packages/repositories.config 160 | # NuGet v3's project.json files produces more ignoreable files 161 | *.nuget.props 162 | *.nuget.targets 163 | 164 | # Microsoft Azure Build Output 165 | csx/ 166 | *.build.csdef 167 | 168 | # Microsoft Azure Emulator 169 | ecf/ 170 | rcf/ 171 | 172 | # Windows Store app package directories and files 173 | AppPackages/ 174 | BundleArtifacts/ 175 | Package.StoreAssociation.xml 176 | _pkginfo.txt 177 | 178 | # Visual Studio cache files 179 | # files ending in .cache can be ignored 180 | *.[Cc]ache 181 | # but keep track of directories ending in .cache 182 | !*.[Cc]ache/ 183 | 184 | # Others 185 | ClientBin/ 186 | ~$* 187 | *~ 188 | *.dbmdl 189 | *.dbproj.schemaview 190 | *.pfx 191 | *.publishsettings 192 | node_modules/ 193 | orleans.codegen.cs 194 | 195 | # Since there are multiple workflows, uncomment next line to ignore bower_components 196 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 197 | #bower_components/ 198 | 199 | # RIA/Silverlight projects 200 | Generated_Code/ 201 | 202 | # Backup & report files from converting an old project file 203 | # to a newer Visual Studio version. Backup files are not needed, 204 | # because we have git ;-) 205 | _UpgradeReport_Files/ 206 | Backup*/ 207 | UpgradeLog*.XML 208 | UpgradeLog*.htm 209 | 210 | # SQL Server files 211 | *.mdf 212 | *.ldf 213 | 214 | # Business Intelligence projects 215 | *.rdl.data 216 | *.bim.layout 217 | *.bim_*.settings 218 | 219 | # Microsoft Fakes 220 | FakesAssemblies/ 221 | 222 | # GhostDoc plugin setting file 223 | *.GhostDoc.xml 224 | 225 | # Node.js Tools for Visual Studio 226 | .ntvs_analysis.dat 227 | 228 | # Visual Studio 6 build log 229 | *.plg 230 | 231 | # Visual Studio 6 workspace options file 232 | *.opt 233 | 234 | # Visual Studio LightSwitch build output 235 | **/*.HTMLClient/GeneratedArtifacts 236 | **/*.DesktopClient/GeneratedArtifacts 237 | **/*.DesktopClient/ModelManifest.xml 238 | **/*.Server/GeneratedArtifacts 239 | **/*.Server/ModelManifest.xml 240 | _Pvt_Extensions 241 | 242 | # Paket dependency manager 243 | .paket/paket.exe 244 | paket-files/ 245 | 246 | # FAKE - F# Make 247 | .fake/ 248 | 249 | # JetBrains Rider 250 | .idea/ 251 | *.sln.iml 252 | 253 | #sonarcloud 254 | .sonarqube 255 | 256 | #SoundCloud 257 | *.sonarqube/ 258 | 259 | #VSCode 260 | .vscode 261 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_lib-nanoframework.WebServer&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_lib-nanoframework.WebServer&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.WebServer.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.WebServer/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) 2 | 3 | ![nanoFramework logo](https://raw.githubusercontent.com/nanoframework/Home/main/resources/logo/nanoFramework-repo-logo.png) 4 | 5 | ----- 6 | 7 | ### Welcome to the .NET **nanoFramework** WebServer repository 8 | 9 | ## Build status 10 | 11 | | Component | Build Status | NuGet Package | 12 | |:-|---|---| 13 | | nanoFramework.WebServer | [![Build Status](https://dev.azure.com/nanoframework/nanoFramework.WebServer/_apis/build/status/nanoFramework.WebServer?repoName=nanoframework%2FnanoFramework.WebServer&branchName=main)](https://dev.azure.com/nanoframework/nanoFramework.WebServer/_build/latest?definitionId=65&repoName=nanoframework%2FnanoFramework.WebServer&branchName=main) | [![NuGet](https://img.shields.io/nuget/v/nanoFramework.WebServer.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.WebServer/) | 14 | | nanoFramework.WebServer.FileSystem | [![Build Status](https://dev.azure.com/nanoframework/nanoFramework.WebServer/_apis/build/status/nanoFramework.WebServer?repoName=nanoframework%2FnanoFramework.WebServer&branchName=main)](https://dev.azure.com/nanoframework/nanoFramework.WebServer/_build/latest?definitionId=65&repoName=nanoframework%2FnanoFramework.WebServer&branchName=main) | [![NuGet](https://img.shields.io/nuget/v/nanoFramework.WebServer.FileSystem.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.WebServer.FileSystem/) | 15 | 16 | ## .NET nanoFramework WebServer 17 | 18 | This library was coded by [Laurent Ellerbach](https://github.com/Ellerbach) who generously offered it to the .NET **nanoFramework** project. 19 | 20 | This is a simple nanoFramework WebServer. Features: 21 | 22 | - Handle multi-thread requests 23 | - Serve static files from any storage using [`nanoFramework.WebServer.FileSystem` NuGet](https://www.nuget.org/packages/nanoFramework.WebServer.FileSystem). Requires a target device with support for storage (having `System.IO.FileSystem` capability). 24 | - Handle parameter in URL 25 | - Possible to have multiple WebServer running at the same time 26 | - supports GET/PUT and any other word 27 | - Supports any type of header 28 | - Supports content in POST 29 | - Reflection for easy usage of controllers and notion of routes 30 | - Helpers to return error code directly facilitating REST API 31 | - HTTPS support 32 | - [URL decode/encode](https://github.com/nanoframework/lib-nanoFramework.System.Net.Http/blob/develop/nanoFramework.System.Net.Http/Http/System.Net.HttpUtility.cs) 33 | 34 | Limitations: 35 | 36 | - Does not support any zip in the request or response stream 37 | 38 | ## Usage 39 | 40 | You just need to specify a port and a timeout for the queries and add an event handler when a request is incoming. With this first way, you will have an event raised every time you'll receive a request. 41 | 42 | ```csharp 43 | using (WebServer server = new WebServer(80, HttpProtocol.Http) 44 | { 45 | // Add a handler for commands that are received by the server. 46 | server.CommandReceived += ServerCommandReceived; 47 | 48 | // Start the server. 49 | server.Start(); 50 | 51 | Thread.Sleep(Timeout.Infinite); 52 | } 53 | ``` 54 | 55 | You can as well pass a controller where you can use decoration for the routes and method supported. 56 | 57 | ```csharp 58 | using (WebServer server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(ControllerPerson), typeof(ControllerTest) })) 59 | { 60 | // Start the server. 61 | server.Start(); 62 | 63 | Thread.Sleep(Timeout.Infinite); 64 | } 65 | ``` 66 | 67 | In this case, you're passing 2 classes where you have public methods decorated which will be called every time the route is found. 68 | 69 | With the previous example, a very simple and straight forward Test controller will look like that: 70 | 71 | ```csharp 72 | public class ControllerTest 73 | { 74 | [Route("test"), Route("Test2"), Route("tEst42"), Route("TEST")] 75 | [CaseSensitive] 76 | [Method("GET")] 77 | public void RoutePostTest(WebServerEventArgs e) 78 | { 79 | string route = $"The route asked is {e.Context.Request.RawUrl.TrimStart('/').Split('/')[0]}"; 80 | e.Context.Response.ContentType = "text/plain"; 81 | WebServer.OutPutStream(e.Context.Response, route); 82 | } 83 | 84 | [Route("test/any")] 85 | public void RouteAnyTest(WebServerEventArgs e) 86 | { 87 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 88 | } 89 | } 90 | ``` 91 | 92 | In this example, the `RoutePostTest` will be called every time the called url will be `test` or `Test2` or `tEst42` or `TEST`, the url can be with parameters and the method GET. Be aware that `Test` won't call the function, neither `test/`. 93 | 94 | The `RouteAnyTest`is called whenever the url is `test/any` whatever the method is. 95 | 96 | There is a more advance example with simple REST API to get a list of Person and add a Person. Check it in the [sample](./WebServer.Sample/ControllerPerson.cs). 97 | 98 | > [!Important] 99 | > 100 | > By default the routes are not case sensitive and the attribute **must** be lowercase. 101 | > If you want to use case sensitive routes like in the previous example, use the attribute `CaseSensitive`. As in the previous example, you **must** write the route as you want it to be responded to. 102 | 103 | ## A simple GPIO controller REST API 104 | 105 | You will find in simple [GPIO controller sample](https://github.com/nanoframework/Samples/tree/main/samples/Webserver/WebServer.GpioRest) REST API. The controller not case sensitive and is working like this: 106 | 107 | - To open the pin 2 as output: http://yoururl/open/2/output 108 | - To open pin 4 as input: http://yoururl/open/4/input 109 | - To write the value high to pin 2: http://yoururl/write/2/high 110 | - You can use high or 1, it has the same effect and will place the pin in high value 111 | - You can use low of 0, it has the same effect and will place the pin in low value 112 | - To read the pin 4: http://yoururl/read/4, you will get as a raw text `high`or `low`depending on the state 113 | 114 | ## Authentication on controllers 115 | 116 | Controllers support authentication. 3 types of authentications are currently implemented on controllers only: 117 | 118 | - Basic: the classic user and password following the HTTP standard. Usage: 119 | - `[Authentication("Basic")]` will use the default credential of the webserver 120 | - `[Authentication("Basic:myuser mypassword")]` will use myuser as a user and my password as a password. Note: the user cannot contains spaces. 121 | - APiKey in header: add ApiKey in headers with the API key. Usage: 122 | - `[Authentication("ApiKey")]` will use the default credential of the webserver 123 | - `[Authentication("ApiKeyc:akey")]` will use akey as ApiKey. 124 | - None: no authentication required. Usage: 125 | - `[Authentication("None")]` will use the default credential of the webserver 126 | 127 | The Authentication attribute applies to both public Classes an public Methods. 128 | 129 | As for the rest of the controller, you can add attributes to define them, override them. The following example gives an idea of what can be done: 130 | 131 | ```csharp 132 | [Authentication("Basic")] 133 | class ControllerAuth 134 | { 135 | [Route("authbasic")] 136 | public void Basic(WebServerEventArgs e) 137 | { 138 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 139 | } 140 | 141 | [Route("authbasicspecial")] 142 | [Authentication("Basic:user2 password")] 143 | public void Special(WebServerEventArgs e) 144 | { 145 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 146 | } 147 | 148 | [Authentication("ApiKey:superKey1234")] 149 | [Route("authapi")] 150 | public void Key(WebServerEventArgs e) 151 | { 152 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 153 | } 154 | 155 | [Route("authnone")] 156 | [Authentication("None")] 157 | public void None(WebServerEventArgs e) 158 | { 159 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 160 | } 161 | 162 | [Authentication("ApiKey")] 163 | [Route("authdefaultapi")] 164 | public void DefaultApi(WebServerEventArgs e) 165 | { 166 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 167 | } 168 | } 169 | ``` 170 | 171 | And you can pass default credentials to the server: 172 | 173 | ```csharp 174 | using (WebServer server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(ControllerPerson), typeof(ControllerTest), typeof(ControllerAuth) })) 175 | { 176 | // To test authentication with various scenarios 177 | server.ApiKey = "ATopSecretAPIKey1234"; 178 | server.Credential = new NetworkCredential("topuser", "topPassword"); 179 | 180 | // Start the server. 181 | server.Start(); 182 | 183 | Thread.Sleep(Timeout.Infinite); 184 | } 185 | ``` 186 | 187 | With the previous example the following happens: 188 | 189 | - All the controller by default, even when nothing is specified will use the controller credentials. In our case, the Basic authentication with the default user (topuser) and password (topPassword) will be used. 190 | - When calling http://yoururl/authbasic from a browser, you will be prompted for the user and password, use the default one topuser and topPassword to get access 191 | - When calling http://yoururl/authnone, you won't be prompted because the authentication has been overridden for no authentication 192 | - When calling http://yoururl/authbasicspecial, the user and password are different from the defautl ones, user2 and password is the right couple here 193 | - If you would have define in the controller a specific user and password like `[Authentication("Basic:myuser mypassword")]`, then the default one for all the controller would have been myuser and mypassword 194 | - When calling http://yoururl/authapi, you must pass the header `ApiKey` (case sensitive) with the value `superKey1234` to get authorized, this is overridden the default Basic authentication 195 | - When calling http://yoururl/authdefaultapi, the default key `ATopSecretAPIKey1234` will be used so you have to pass it in the headers of the request 196 | 197 | All up, this is an example to show how to use authentication, it's been defined to allow flexibility. 198 | 199 | The webserver supports having multiple authentication methods or credentials for the same route. Each pair of authentication method plus credentials should have its own method in the controller: 200 | 201 | ```csharp 202 | class MixedController 203 | { 204 | 205 | [Route("sameroute")] 206 | [Authentication("Basic")] 207 | public void Basic(WebServerEventArgs e) 208 | { 209 | WebServer.OutPutStream(e.Context.Response, "sameroute: Basic"); 210 | } 211 | 212 | [Authentication("ApiKey:superKey1234")] 213 | [Route("sameroute")] 214 | public void Key(WebServerEventArgs e) 215 | { 216 | WebServer.OutPutStream(e.Context.Response, "sameroute: API key #1"); 217 | } 218 | 219 | [Authentication("ApiKey:superKey5678")] 220 | [Route("sameroute")] 221 | public void Key2(WebServerEventArgs e) 222 | { 223 | WebServer.OutPutStream(e.Context.Response, "sameroute: API key #2"); 224 | } 225 | 226 | [Route("sameroute")] 227 | public void None(WebServerEventArgs e) 228 | { 229 | WebServer.OutPutStream(e.Context.Response, "sameroute: Public"); 230 | } 231 | } 232 | ``` 233 | 234 | The webserver selects the route for a request: 235 | 236 | - If there are no matching methods, a not-found response (404) is returned. 237 | - If authentication information is passed in the header of the request, then only methods that require authentication are considered. If one of the method's credentials matches the credentials passed in the request, that method is called. Otherwise a non-authorized response (401) will be returned. 238 | - If no authentication information is passed in the header of the request: 239 | - If one of the methods does not require authentication, that method is called. 240 | - Otherwise a non-authorized response (401) will be returned. If one of the methods requires basic authentication, the `WWW-Authenticate` header is included to request credentials. 241 | 242 | The webserver does not support more than one matching method. Calling multiple methods most likely results in an exception as a subsequent method tries to modify a response that is already processed by the first method. The webserver does not know what to do and returns an internal server error (500). The body of the response lists the matching methods. 243 | 244 | Having multiple matching methods is considered a programming error. One way this occurs is if two methods in a controller accidentally have the same route. Returning an internal server error with the names of the methods makes it easy to discover the error. It is expected that the error is discovered and fixed in testing. Then the internal error will not occur in the application that is deployed to a device. 245 | 246 | ## Managing incoming queries thru events 247 | 248 | Very basic usage is the following: 249 | 250 | ```csharp 251 | private static void ServerCommandReceived(object source, WebServerEventArgs e) 252 | { 253 | var url = e.Context.Request.RawUrl; 254 | Debug.WriteLine($"Command received: {url}, Method: {e.Context.Request.HttpMethod}"); 255 | 256 | if (url.ToLower() == "/sayhello") 257 | { 258 | // This is simple raw text returned 259 | WebServer.OutPutStream(e.Context.Response, "It's working, url is empty, this is just raw text, /sayhello is just returning a raw text"); 260 | } 261 | else 262 | { 263 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.NotFound); 264 | } 265 | } 266 | ``` 267 | 268 | You can do more advance scenario like returning a full HTML page: 269 | 270 | ```csharp 271 | WebServer.OutPutStream(e.Context.Response, "" + 272 | "Hi from nanoFramework ServerYou want me to say hello in a real HTML page!
Generate an internal text.txt file
" + 273 | "Download the Text.txt file
" + 274 | "Try this url with parameters: /param.htm?param1=42&second=24&NAme=Ellerbach"); 275 | ``` 276 | 277 | And can get parameters from a URL a an example from the previous link on the param.html page: 278 | 279 | ```csharp 280 | if (url.ToLower().IndexOf("/param.htm") == 0) 281 | { 282 | // Test with parameters 283 | var parameters = WebServer.decryptParam(url); 284 | string toOutput = "" + 285 | "Hi from nanoFramework ServerHere are the parameters of this URL:
"; 286 | foreach (var par in parameters) 287 | { 288 | toOutput += $"Parameter name: {par.Name}, Value: {par.Value}
"; 289 | } 290 | toOutput += ""; 291 | WebServer.OutPutStream(e.Context.Response, toOutput); 292 | } 293 | ``` 294 | 295 | And server static files: 296 | 297 | ```csharp 298 | // E = USB storage 299 | // D = SD Card 300 | // I = Internal storage 301 | // Adjust this based on your configuration 302 | const string DirectoryPath = "I:\\"; 303 | string[] _listFiles; 304 | 305 | // Gets the list of all files in a specific directory 306 | // See the MountExample for more details if you need to mount an SD card and adjust here 307 | // https://github.com/nanoframework/Samples/blob/main/samples/System.IO.FileSystem/MountExample/Program.cs 308 | _listFiles = Directory.GetFiles(DirectoryPath); 309 | // Remove the root directory 310 | for (int i = 0; i < _listFiles.Length; i++) 311 | { 312 | _listFiles[i] = _listFiles[i].Substring(DirectoryPath.Length); 313 | } 314 | 315 | var fileName = url.Substring(1); 316 | // Note that the file name is case sensitive 317 | // Very simple example serving a static file on an SD card 318 | foreach (var file in _listFiles) 319 | { 320 | if (file == fileName) 321 | { 322 | WebServer.SendFileOverHTTP(e.Context.Response, DirectoryPath + file); 323 | return; 324 | } 325 | } 326 | 327 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.NotFound); 328 | ``` 329 | 330 | > [!Important] 331 | > 332 | > Serving files requires the `nanoFramework.WebServer.FileSystem` nuget **AND** that the device supports storage so `System.IO.FileSystem`. 333 | 334 | And also **REST API** is supported, here is a comprehensive example: 335 | 336 | ```csharp 337 | if (url.ToLower().IndexOf("/api/") == 0) 338 | { 339 | string ret = $"Your request type is: {e.Context.Request.HttpMethod}\r\n"; 340 | ret += $"The request URL is: {e.Context.Request.RawUrl}\r\n"; 341 | var parameters = WebServer.DecodeParam(e.Context.Request.RawUrl); 342 | if (parameters != null) 343 | { 344 | ret += "List of url parameters:\r\n"; 345 | foreach (var param in parameters) 346 | { 347 | ret += $" Parameter name: {param.Name}, value: {param.Value}\r\n"; 348 | } 349 | } 350 | 351 | if (e.Context.Request.Headers != null) 352 | { 353 | ret += $"Number of headers: {e.Context.Request.Headers.Count}\r\n"; 354 | } 355 | else 356 | { 357 | ret += "There is no header in this request\r\n"; 358 | } 359 | 360 | foreach (var head in e.Context.Request.Headers?.AllKeys) 361 | { 362 | ret += $" Header name: {head}, Values:"; 363 | var vals = e.Context.Request.Headers.GetValues(head); 364 | foreach (var val in vals) 365 | { 366 | ret += $"{val} "; 367 | } 368 | 369 | ret += "\r\n"; 370 | } 371 | 372 | if (e.Context.Request.ContentLength64 > 0) 373 | { 374 | 375 | ret += $"Size of content: {e.Context.Request.ContentLength64}\r\n"; 376 | 377 | var contentTypes = e.Context.Request.Headers?.GetValues("Content-Type"); 378 | var isMultipartForm = contentTypes != null && contentTypes.Length > 0 && contentTypes[0].StartsWith("multipart/form-data;"); 379 | 380 | if(isMultipartForm) 381 | { 382 | var form = e.Context.Request.ReadForm(); 383 | ret += $"Received a form with {form.Parameters.Length} parameters and {form.Files.Length} files."; 384 | } 385 | else 386 | { 387 | var body = e.Context.Request.ReadBody(); 388 | 389 | ret += $"Request body hex string representation:\r\n"; 390 | for (int i = 0; i < body.Length; i++) 391 | { 392 | ret += body[i].ToString("X") + " "; 393 | } 394 | } 395 | 396 | } 397 | 398 | WebServer.OutPutStream(e.Context.Response, ret); 399 | } 400 | ``` 401 | 402 | This API example is basic but as you get the method, you can choose what to do. 403 | 404 | As you get the url, you can check for a specific controller called. And you have the parameters and the content payload! 405 | 406 | Notice the extension methods to read the body of the request: 407 | 408 | - ReadBody will read the data from the InputStream while the data is flowing in which might be in multiple passes depending on the size of the body 409 | - ReadForm allows to read a multipart/form-data form and returns the text key/value pairs as well as any files in the request 410 | 411 | Example of a result with call: 412 | 413 | ![result](./doc/POSTcapture.jpg) 414 | 415 | And more! Check the complete example for more about this WebServer! 416 | 417 | ## Using HTTPS 418 | 419 | You will need to generate a certificate and keys: 420 | 421 | ```csharp 422 | X509Certificate _myWebServerCertificate509 = new X509Certificate2(_myWebServerCrt, _myWebServerPrivateKey, "1234"); 423 | 424 | // X509 RSA key PEM format 2048 bytes 425 | // generate with openssl: 426 | // > openssl req -newkey rsa:2048 -nodes -keyout selfcert.key -x509 -days 365 -out selfcert.crt 427 | // and paste selfcert.crt content below: 428 | private const string _myWebServerCrt = 429 | @"-----BEGIN CERTIFICATE----- 430 | MORETEXT 431 | -----END CERTIFICATE-----"; 432 | 433 | // this one is generated with the command below. We need a password. 434 | // > openssl rsa -des3 -in selfcert.key -out selfcertenc.key 435 | // the one below was encoded with '1234' as the password. 436 | private const string _myWebServerPrivateKey = 437 | @"-----BEGIN RSA PRIVATE KEY----- 438 | MORETEXTANDENCRYPTED 439 | -----END RSA PRIVATE KEY-----"; 440 | 441 | using (WebServer server = new WebServer(443, HttpProtocol.Https) 442 | { 443 | // Add a handler for commands that are received by the server. 444 | server.CommandReceived += ServerCommandReceived; 445 | server.HttpsCert = _myWebServerCertificate509; 446 | 447 | server.SslProtocols = System.Net.Security.SslProtocols.Tls | System.Net.Security.SslProtocols.Tls11 | System.Net.Security.SslProtocols.Tls12; 448 | // Start the server. 449 | server.Start(); 450 | 451 | Thread.Sleep(Timeout.Infinite); 452 | } 453 | ``` 454 | 455 | > [!IMPORTANT] 456 | > Because the certificate above is not issued from a Certificate Authority it won't be recognized as a valid certificate. If you want to access the nanoFramework device with your browser, for example, you'll have to add the [CRT file](WebServer.Sample\webserver-cert.crt) as a trusted one. On Windows, you just have to double click on the CRT file and then click "Install Certificate...". 457 | 458 | You can of course use the routes as defined earlier. Both will work, event or route with the notion of controller. 459 | 460 | ## WebServer status 461 | 462 | It is possible to subscribe to an event to get the WebServer status. That can be useful to restart the server, put in place a retry mechanism or equivalent. 463 | 464 | ```csharp 465 | server.WebServerStatusChanged += WebServerStatusChanged; 466 | 467 | private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs e) 468 | { 469 | // Do whatever you need like restarting the server 470 | Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped" )}"); 471 | } 472 | ``` 473 | 474 | ## E2E tests 475 | 476 | There is a collection of postman tests `nanoFramework WebServer E2E Tests.postman_collection.json` in WebServerE2ETests which should be used for testing WebServer in real world scenario. Usage is simple: 477 | - Import json file into Postman 478 | - Deploy WebServerE2ETests to your device - copy IP 479 | - Set the `base_url` variable to match your device IP address 480 | - Choose request you want to test or run whole collection and check tests results. 481 | 482 | The WebServerE2ETests project requires the name and credentials for the WiFi access point. That is stored in the WiFi.cs file that is not part of the git repository. Build the WebServerE2ETests to create a template for that file, then change the SSID and credentials. Your credentials will not be part of a commit. 483 | 484 | ## Feedback and documentation 485 | 486 | For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home). 487 | 488 | Join our Discord community [here](https://discord.gg/gCyBu8T). 489 | 490 | ## Credits 491 | 492 | The list of contributors to this project can be found at [CONTRIBUTORS](https://github.com/nanoframework/Home/blob/main/CONTRIBUTORS.md). 493 | 494 | ## License 495 | 496 | The **nanoFramework** WebServer library is licensed under the [MIT license](LICENSE.md). 497 | 498 | ## Code of Conduct 499 | 500 | This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behaviour in our community. 501 | For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). 502 | 503 | ## .NET Foundation 504 | 505 | This project is supported by the [.NET Foundation](https://dotnetfoundation.org). 506 | -------------------------------------------------------------------------------- /assets/nf-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoframework/nanoFramework.WebServer/b8d286946806404e6888bbd23a95b40a200e4579/assets/nf-logo.png -------------------------------------------------------------------------------- /assets/readme.txt: -------------------------------------------------------------------------------- 1 | _____ _ 2 | _ __ __ _ _ __ ___ | ___| __ __ _ _ __ ___ _____ _____ _ __| | __ 3 | | '_ \ / _` | '_ \ / _ \| |_ | '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ / 4 | | | | | (_| | | | | (_) | _|| | | (_| | | | | | | __/\ V V / (_) | | | < 5 | |_| |_|\__,_|_| |_|\___/|_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_\ 6 | 7 | =================================================================================== 8 | 9 | API docs: https://docs.nanoframework.net/api/nanoFramework.WebServer.html 10 | 11 | Browse our samples repository: https://github.com/nanoframework/samples 12 | 13 | Check our documentation online: https://docs.nanoframework.net/ 14 | 15 | Join our lively Discord community: https://discord.gg/gCyBu8T 16 | 17 | Report issues: https://github.com/nanoframework/Home/issues 18 | 19 | Follow us on Twitter: https://twitter.com/nanoframework 20 | 21 | Follow our YouTube channel: https://www.youtube.com/c/nanoFramework 22 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) .NET Foundation and Contributors 2 | # See LICENSE file in the project root for full license information. 3 | 4 | trigger: 5 | branches: 6 | include: 7 | - main 8 | - develop 9 | - release-* 10 | paths: 11 | exclude: 12 | - .github/* 13 | - .gitignore 14 | - CHANGELOG.md 15 | - LICENSE.md 16 | - README.md 17 | - NuGet.Config 18 | - assets/* 19 | - template.vssettings 20 | - spelling_exclusion.dic 21 | 22 | # PR always trigger build 23 | pr: 24 | autoCancel: true 25 | 26 | # add nf-tools repo to resources (for Azure Pipelines templates) 27 | resources: 28 | repositories: 29 | - repository: templates 30 | type: github 31 | name: nanoframework/nf-tools 32 | endpoint: nanoframework 33 | 34 | pool: 35 | vmImage: 'windows-latest' 36 | 37 | variables: 38 | - group: sign-client-credentials 39 | - name: DOTNET_NOLOGO 40 | value: true 41 | - name: buildPlatform 42 | value: 'Any CPU' 43 | - name: buildConfiguration 44 | value: 'Release' 45 | - name: solution 46 | value: 'nanoFramework.WebServer.sln' 47 | - name: nugetPackageName 48 | value: 'nanoFramework.WebServer' 49 | 50 | steps: 51 | 52 | # step from template @ nf-tools repo 53 | # all build, update and publish steps 54 | - template: azure-pipelines-templates/class-lib-build-only.yml@templates 55 | parameters: 56 | sonarCloudProject: 'nanoframework_lib-nanoframework.WebServer' 57 | 58 | # build the 2 libs step 59 | - template: azure-pipelines-templates/class-lib-package.yml@templates 60 | parameters: 61 | nugetPackageName: 'nanoFramework.WebServer' 62 | 63 | - template: azure-pipelines-templates/class-lib-package.yml@templates 64 | parameters: 65 | nugetPackageName: 'nanoFramework.WebServer.FileSystem' 66 | 67 | # publish the 2 libs 68 | - template: azure-pipelines-templates/class-lib-publish.yml@templates 69 | 70 | # create GitHub release build from main branch 71 | - task: GithubRelease@1 72 | condition: >- 73 | and( 74 | succeeded(), 75 | eq(variables['System.PullRequest.PullRequestId'], ''), 76 | startsWith(variables['Build.SourceBranch'], 'refs/heads/main'), 77 | not(contains(variables['Build.SourceBranch'], 'preview')), 78 | eq(variables['StartReleaseCandidate'], false) 79 | ) 80 | displayName: Create/Update GitHub release 81 | inputs: 82 | action: edit 83 | gitHubConnection: 'github.com_nano-$(System.TeamProject)' 84 | tagSource: userSpecifiedTag 85 | tag: v$(MY_NUGET_VERSION) 86 | title: '$(nugetPackageName) Library v$(MY_NUGET_VERSION)' 87 | releaseNotesSource: inline 88 | releaseNotesInline: 'Check the [changelog]($(Build.Repository.Uri)/blob/$(Build.SourceBranchName)/CHANGELOG.md).

Install from NuGet


The following NuGet packages are available for download from this release:
:package: [nanoFramework.WebServer](https://www.nuget.org/packages/$(nugetPackageName)/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION).
:package: [nanoFramework.WebServer.FileSystem (requires support of storage through System.IO.FileSystem)](https://www.nuget.org/packages/nanoFramework.WebServer.FileSystem/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION)' 89 | assets: '$(Build.ArtifactStagingDirectory)/*.nupkg' 90 | assetUploadMode: replace 91 | isPreRelease: false 92 | addChangeLog: false 93 | 94 | # step from template @ nf-tools repo 95 | # report error 96 | - template: azure-pipelines-templates/discord-webhook-task.yml@templates 97 | parameters: 98 | status: 'failure' 99 | webhookUrl: '$(DiscordWebhook)' 100 | message: '' 101 | -------------------------------------------------------------------------------- /doc/POSTcapture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoframework/nanoFramework.WebServer/b8d286946806404e6888bbd23a95b40a200e4579/doc/POSTcapture.jpg -------------------------------------------------------------------------------- /key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanoframework/nanoFramework.WebServer/b8d286946806404e6888bbd23a95b40a200e4579/key.snk -------------------------------------------------------------------------------- /nanoFramework.WebServer.FileSystem.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nanoFramework.WebServer.FileSystem 5 | nanoFramework.WebServer.FileSystem 6 | $version$ 7 | Laurent Ellerbach,nanoframework 8 | false 9 | LICENSE.md 10 | 11 | 12 | docs\README.md 13 | false 14 | https://github.com/nanoframework/nanoFramework.WebServer 15 | images\nf-logo.png 16 | 17 | Copyright (c) .NET Foundation and Contributors 18 | This is a simple multithread WebServer supporting simple controller and event based calls. 19 | Perfect for .NET nanoFramework REST API based project. Support all type of Http Methods. 20 | Perfect for simple embedded web pages, with Support of file on a storage (USB, SD Card, in Memory). 21 | Supports both HTTPS and HTTP. 22 | Use this version if you want to serve local files and have support for System.IO.FileSystem on your device. 23 | Otherwise use 'nanoFramework.WebServer' nuget. 24 | http https webserver net netmf nf nanoframework 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /nanoFramework.WebServer.FileSystem/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("nanoFramework.WebServer")] 9 | [assembly: AssemblyCompany("nanoFramework Contributors")] 10 | [assembly: AssemblyProduct("nanoFramework.WebServer.FileSystem")] 11 | [assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] 12 | 13 | 14 | // Setting ComVisible to false makes the types in this assembly not visible 15 | // to COM components. If you need to access a type in this assembly from 16 | // COM, set the ComVisible attribute to true on that type. 17 | [assembly: ComVisible(false)] 18 | 19 | -------------------------------------------------------------------------------- /nanoFramework.WebServer.FileSystem/nanoFramework.WebServer.FileSystem.nfproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | $(MSBuildExtensionsPath)\nanoFramework\v1.0\ 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 12 | 9d8a2d18-8036-4880-b46b-d5218247257d 13 | Library 14 | Properties 15 | 512 16 | nanoFramework.WebServer 17 | nanoFramework.WebServer 18 | v1.0 19 | bin\$(Configuration)\nanoFramework.WebServer.xml 20 | true 21 | true 22 | FILESYSTEM; 23 | 24 | 25 | true 26 | 27 | 28 | ..\key.snk 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll 64 | 65 | 66 | ..\packages\nanoFramework.Runtime.Events.1.11.32\lib\nanoFramework.Runtime.Events.dll 67 | 68 | 69 | ..\packages\nanoFramework.System.Collections.1.5.67\lib\nanoFramework.System.Collections.dll 70 | 71 | 72 | ..\packages\nanoFramework.System.Text.1.3.42\lib\nanoFramework.System.Text.dll 73 | 74 | 75 | ..\packages\nanoFramework.System.IO.FileSystem.1.1.87\lib\System.IO.FileSystem.dll 76 | 77 | 78 | ..\packages\nanoFramework.System.IO.Streams.1.1.96\lib\System.IO.Streams.dll 79 | 80 | 81 | ..\packages\nanoFramework.System.Net.1.11.43\lib\System.Net.dll 82 | 83 | 84 | ..\packages\nanoFramework.System.Net.Http.Server.1.5.196\lib\System.Net.Http.dll 85 | 86 | 87 | ..\packages\nanoFramework.System.Threading.1.1.52\lib\System.Threading.dll 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /nanoFramework.WebServer.FileSystem/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /nanoFramework.WebServer.FileSystem/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETnanoFramework,Version=v1.0": { 5 | "nanoFramework.CoreLibrary": { 6 | "type": "Direct", 7 | "requested": "[1.17.11, 1.17.11]", 8 | "resolved": "1.17.11", 9 | "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" 10 | }, 11 | "nanoFramework.Runtime.Events": { 12 | "type": "Direct", 13 | "requested": "[1.11.32, 1.11.32]", 14 | "resolved": "1.11.32", 15 | "contentHash": "NyLUIwJDlpl5VKSd+ljmdDtO2WHHBvPvruo1ccaL+hd79z+6XMYze1AccOVXKGiZenLBCwDmFHwpgIQyHkM7GA==" 16 | }, 17 | "nanoFramework.System.Collections": { 18 | "type": "Direct", 19 | "requested": "[1.5.67, 1.5.67]", 20 | "resolved": "1.5.67", 21 | "contentHash": "MjSipUB70vrxjqTm1KfKTUqqjd0wbweiNyYFXONi0XClrH6HXsuX2lhDqXM8NWuYnWyYOqx8y20sXbvsH+4brg==" 22 | }, 23 | "nanoFramework.System.IO.FileSystem": { 24 | "type": "Direct", 25 | "requested": "[1.1.87, 1.1.87]", 26 | "resolved": "1.1.87", 27 | "contentHash": "Et2CmX2P9qtOUrMfQy9ijKZ31J/87aEb+iDGziD3Sys6KZRJKkFH3DHuYMLwzrWwZxiZ2McuN1ul0UzI02QKSg==" 28 | }, 29 | "nanoFramework.System.IO.Streams": { 30 | "type": "Direct", 31 | "requested": "[1.1.96, 1.1.96]", 32 | "resolved": "1.1.96", 33 | "contentHash": "kJSy4EJwChO4Vq3vGWP9gNRPFDnTsDU5HxzeI7NDO+RjbDsx7B8EhKymoeTPLJCxQq8y/0P1KG2XCxGpggW+fw==" 34 | }, 35 | "nanoFramework.System.Net": { 36 | "type": "Direct", 37 | "requested": "[1.11.43, 1.11.43]", 38 | "resolved": "1.11.43", 39 | "contentHash": "USwz59gxcNUzsiXfQohWSi8ANNwGDsp+qG4zBtHZU3rKMtvTsLI3rxdfMC77VehKqsCPn7aK3PU2oCRFo+1Rgg==" 40 | }, 41 | "nanoFramework.System.Net.Http.Server": { 42 | "type": "Direct", 43 | "requested": "[1.5.196, 1.5.196]", 44 | "resolved": "1.5.196", 45 | "contentHash": "cjr5Rj39duOjGcyvo/LMFdoeTeLg0zpFgFB7wJUXw0+65EiENEnJwqqR1CfbJEvBBpBMJdH/yLkK/8DU8Jk3XQ==" 46 | }, 47 | "nanoFramework.System.Text": { 48 | "type": "Direct", 49 | "requested": "[1.3.42, 1.3.42]", 50 | "resolved": "1.3.42", 51 | "contentHash": "68HPjhersNpssbmEMUHdMw3073MHfGTfrkbRk9eILKbNPFfPFck7m4y9BlAi6DaguUJaeKxgyIojXF3SQrF8/A==" 52 | }, 53 | "nanoFramework.System.Threading": { 54 | "type": "Direct", 55 | "requested": "[1.1.52, 1.1.52]", 56 | "resolved": "1.1.52", 57 | "contentHash": "kv+US/+7QKV1iT/snxBh032vwZ+3krJ4vujlSsvmS2nNj/nK64R3bq/ST3bCFquxHDD0mog8irtCBCsFazr4kA==" 58 | }, 59 | "Nerdbank.GitVersioning": { 60 | "type": "Direct", 61 | "requested": "[3.7.115, 3.7.115]", 62 | "resolved": "3.7.115", 63 | "contentHash": "EpXamaAdRfG/BMxGgvZlTM0npRnkmXUjAj8OdNKd17t4oN+2nvjdv/KnFmzOOMDqvlwB49UCwtOHJrAQTfUBtQ==" 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /nanoFramework.WebServer.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nanoFramework.WebServer 5 | nanoFramework.WebServer 6 | $version$ 7 | Laurent Ellerbach,nanoframework 8 | false 9 | LICENSE.md 10 | 11 | 12 | docs\README.md 13 | false 14 | https://github.com/nanoframework/nanoFramework.WebServer 15 | images\nf-logo.png 16 | 17 | Copyright (c) .NET Foundation and Contributors 18 | This is a simple multithread WebServer supporting simple controller and event based calls. 19 | Perfect for .NET nanoFramework REST API based project. Supports both HTTPS and HTTP. 20 | If serving files from local storage is a requirement, please use instead the 'nanoFramework.WebServer.FileSystem' nuget. 21 | 22 | http https webserver net netmf nf nanoframework 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /nanoFramework.WebServer.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.1.32319.34 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.WebServer", "nanoFramework.WebServer\nanoFramework.WebServer.nfproj", "{87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{023C4887-C73C-4F74-ABA5-204D7690F8F5}" 8 | ProjectSection(SolutionItems) = preProject 9 | NuGet.Config = NuGet.Config 10 | README.md = README.md 11 | version.json = version.json 12 | EndProjectSection 13 | EndProject 14 | Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.WebServer.FileSystem", "nanoFramework.WebServer.FileSystem\nanoFramework.WebServer.FileSystem.nfproj", "{9D8A2D18-8036-4880-B46B-D5218247257D}" 15 | EndProject 16 | Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "nanoFramework.WebServer.Tests", "tests\nanoFramework.WebServer.Tests\nanoFramework.WebServer.Tests.nfproj", "{2C2B4750-2A48-4D19-9404-178AAB946482}" 17 | EndProject 18 | Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "WebServerE2ETests", "tests\WebServerE2ETests\WebServerE2ETests.nfproj", "{A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E76226D2-994C-4EE1-B346-050F31B175BD}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 31 | {87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {87AAA5FE-CBB6-497F-97B7-7AF21B9A0C4E}.Release|Any CPU.Deploy.0 = Release|Any CPU 34 | {9D8A2D18-8036-4880-B46B-D5218247257D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {9D8A2D18-8036-4880-B46B-D5218247257D}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {9D8A2D18-8036-4880-B46B-D5218247257D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 37 | {9D8A2D18-8036-4880-B46B-D5218247257D}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {9D8A2D18-8036-4880-B46B-D5218247257D}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {9D8A2D18-8036-4880-B46B-D5218247257D}.Release|Any CPU.Deploy.0 = Release|Any CPU 40 | {2C2B4750-2A48-4D19-9404-178AAB946482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {2C2B4750-2A48-4D19-9404-178AAB946482}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {2C2B4750-2A48-4D19-9404-178AAB946482}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 43 | {2C2B4750-2A48-4D19-9404-178AAB946482}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {2C2B4750-2A48-4D19-9404-178AAB946482}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {2C2B4750-2A48-4D19-9404-178AAB946482}.Release|Any CPU.Deploy.0 = Release|Any CPU 46 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 49 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46}.Release|Any CPU.Deploy.0 = Release|Any CPU 52 | EndGlobalSection 53 | GlobalSection(SolutionProperties) = preSolution 54 | HideSolutionNode = FALSE 55 | EndGlobalSection 56 | GlobalSection(NestedProjects) = preSolution 57 | {2C2B4750-2A48-4D19-9404-178AAB946482} = {E76226D2-994C-4EE1-B346-050F31B175BD} 58 | {A0611EAD-FB04-44E7-BAD3-459DD0A7FF46} = {E76226D2-994C-4EE1-B346-050F31B175BD} 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {262CE437-AD82-4481-8B77-593288986C70} 62 | EndGlobalSection 63 | EndGlobal 64 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/Authentication.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | using System.Net; 8 | using System.Text; 9 | 10 | namespace nanoFramework.WebServer 11 | { 12 | /// 13 | /// The authentication to be used by the server. 14 | /// 15 | public class Authentication 16 | { 17 | /// 18 | /// The type of authentication. 19 | /// 20 | public AuthenticationType AuthenticationType { get; internal set; } 21 | 22 | /// 23 | /// The network credential user and password. 24 | /// 25 | public NetworkCredential Credentials { get; internal set; } = null; 26 | 27 | /// 28 | /// The API Key to use for authentication. 29 | /// 30 | public string ApiKey { get; internal set; } = null; 31 | 32 | /// 33 | /// Creates an autentication class from a credential. 34 | /// 35 | /// The credentials. 36 | public Authentication(NetworkCredential credential) 37 | { 38 | AuthenticationType = AuthenticationType.Basic; 39 | Credentials = credential; 40 | } 41 | 42 | /// 43 | /// Creates an authentication from a key. 44 | /// 45 | /// The key. 46 | public Authentication(string apiKey) 47 | { 48 | AuthenticationType = AuthenticationType.ApiKey; 49 | ApiKey = apiKey; 50 | } 51 | 52 | /// 53 | /// Creates an empty authenticate. 54 | /// 55 | public Authentication() 56 | { 57 | AuthenticationType = AuthenticationType.None; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/AuthenticationAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | using System.Text; 8 | 9 | namespace nanoFramework.WebServer 10 | { 11 | /// 12 | /// Authentication attribute for classes and method 13 | /// 14 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] 15 | public class AuthenticationAttribute : Attribute 16 | { 17 | /// 18 | /// The authentication method, examples: 19 | /// - Basic:user password 20 | /// - Basic 21 | /// - ApiKey:OneApiKey 22 | /// - ApiKey 23 | /// - None 24 | /// In case of Basic and ApiKey alone, the default one passed at server properties ones will be used 25 | /// The Basic authentication is a classical http basic authentication and the couple user password have to be separated with a space, the password can contain spaces but not the user name. Basic and the user name has to be separated with a : 26 | /// ApiKey and the current apikey has to be separated with : 27 | /// The current ApiKey can contain only characters that are allow in http headers 28 | /// 29 | public string AuthenticationMethod { get; set; } 30 | 31 | /// 32 | /// The constructor for the Authentication attribute 33 | /// 34 | /// 35 | public AuthenticationAttribute(string auth) 36 | { 37 | AuthenticationMethod = auth; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/AuthenticationType.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | using System.Text; 8 | 9 | namespace nanoFramework.WebServer 10 | { 11 | /// 12 | /// The type of authentication to use. 13 | /// 14 | public enum AuthenticationType 15 | { 16 | /// 17 | /// No authentication is needed. 18 | /// 19 | None, 20 | 21 | /// 22 | /// Basic authentication with user and password. 23 | /// 24 | Basic, 25 | 26 | /// 27 | /// Using an ApiKey. 28 | /// 29 | ApiKey 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/CallbackRoutes.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System.Reflection; 7 | 8 | namespace nanoFramework.WebServer 9 | { 10 | /// 11 | /// Callback function for the various routes 12 | /// 13 | public class CallbackRoutes 14 | { 15 | /// 16 | /// The method to call for a specific route 17 | /// 18 | public MethodInfo Callback { get; set; } 19 | 20 | /// 21 | /// The route ex: api/gpio 22 | /// 23 | public string Route { get; set; } 24 | 25 | /// 26 | /// Is the root case sensitive? 27 | /// 28 | public bool CaseSensitive { get; set; } 29 | 30 | /// 31 | /// The http method ex GET or POST, leave string.Empty for any 32 | /// 33 | public string Method { get; set; } 34 | 35 | /// 36 | /// the authentication details 37 | /// 38 | public Authentication Authentication { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/CaseSensitiveAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | 8 | namespace nanoFramework.WebServer 9 | { 10 | /// 11 | /// If the route is case sensitive or not 12 | /// 13 | [AttributeUsage(AttributeTargets.Method)] 14 | public class CaseSensitiveAttribute : Attribute 15 | { } 16 | } 17 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/Header.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | using System.Text; 8 | 9 | namespace nanoFramework.WebServer 10 | { 11 | /// 12 | /// Header class 13 | /// 14 | public class Header 15 | { 16 | /// 17 | /// Name of the header 18 | /// 19 | public string Name { get; set; } 20 | 21 | /// 22 | /// Value of the header 23 | /// 24 | public string Value { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpListenerRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Net; 7 | using System.Threading; 8 | using nanoFramework.WebServer.HttpMultipartParser; 9 | 10 | namespace nanoFramework.WebServer 11 | { 12 | /// Contains extension methods for HttpListenerRequest 13 | public static class HttpListenerRequestExtensions 14 | { 15 | /// 16 | /// Reads a Multipart form from the request 17 | /// 18 | /// The request to read the form from 19 | /// A MultipartFormDataParser containing a collection of the parameters and files in the form. 20 | public static MultipartFormDataParser ReadForm(this HttpListenerRequest httpListenerRequest) => 21 | MultipartFormDataParser.Parse(httpListenerRequest.InputStream); 22 | 23 | /// 24 | /// Reads a body from the HttpListenerRequest inputstream 25 | /// 26 | /// The request to read the body from 27 | /// A byte[] containing the body of the request 28 | public static byte[] ReadBody(this HttpListenerRequest httpListenerRequest) 29 | { 30 | byte[] body = new byte[httpListenerRequest.ContentLength64]; 31 | byte[] buffer = new byte[4096]; 32 | Stream stream = httpListenerRequest.InputStream; 33 | 34 | int position = 0; 35 | 36 | while (true) 37 | { 38 | // The stream is (should be) a NetworkStream which might still be receiving data while 39 | // we're already processing. Give the stream a chance to receive more data or we might 40 | // end up with "zero bytes read" too soon... 41 | Thread.Sleep(1); 42 | 43 | long length = stream.Length; 44 | 45 | if (length > buffer.Length) 46 | { 47 | length = buffer.Length; 48 | } 49 | 50 | int bytesRead = stream.Read(buffer, 0, (int)length); 51 | 52 | if (bytesRead == 0) 53 | { 54 | break; 55 | } 56 | 57 | Array.Copy(buffer, 0, body, position, bytesRead); 58 | 59 | position += bytesRead; 60 | } 61 | 62 | return body; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/FilePart.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System.Collections; 5 | using System.IO; 6 | 7 | namespace nanoFramework.WebServer.HttpMultipartParser 8 | { 9 | /// Represents a single file extracted from a multipart/form-data stream. 10 | public class FilePart 11 | { 12 | /// Initializes a new instance of the class. 13 | /// The name of the input field used for the upload. 14 | /// The name of the file. 15 | /// The file data. 16 | /// Additional properties associated with this file. 17 | /// The content type. 18 | /// The content disposition. 19 | public FilePart(string name, string fileName, Stream data, Hashtable additionalProperties, string contentType, string contentDisposition) 20 | { 21 | string[] parts = fileName?.Split(GetInvalidFileNameChars()); 22 | 23 | Name = name; 24 | FileName = parts != null && parts.Length > 0 ? parts[parts.Length - 1] : string.Empty; 25 | Data = data; 26 | ContentType = contentType; 27 | ContentDisposition = contentDisposition; 28 | AdditionalProperties = additionalProperties; 29 | } 30 | 31 | /// Gets the data. 32 | public Stream Data 33 | { 34 | get; 35 | } 36 | 37 | /// Gets the file name. 38 | public string FileName 39 | { 40 | get; 41 | } 42 | 43 | /// Gets the name. 44 | public string Name 45 | { 46 | get; 47 | } 48 | 49 | /// Gets the content-type. Defaults to text/plain if unspecified. 50 | public string ContentType 51 | { 52 | get; 53 | } 54 | 55 | /// Gets the content-disposition. Defaults to form-data if unspecified. 56 | public string ContentDisposition 57 | { 58 | get; 59 | } 60 | 61 | /// 62 | /// Gets the additional properties associated with this file. 63 | /// An additional property is any property other than the "well known" ones such as name, filename, content-type, etc. 64 | /// 65 | public Hashtable AdditionalProperties 66 | { 67 | get; 68 | private set; 69 | } 70 | 71 | private static char[] GetInvalidFileNameChars() => new char[] 72 | { 73 | '\"', '<', '>', '|', '\0', 74 | (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, 75 | (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, 76 | (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, 77 | (char)31, ':', '*', '?', '\\', '/' 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/HashtableUtility.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System.Collections; 5 | 6 | namespace nanoFramework.WebServer.HttpMultipartParser 7 | { 8 | internal static class HashtableUtility 9 | { 10 | public static bool TryGetValue(this Hashtable hashtable, string key, out string value) 11 | { 12 | if (hashtable != null && hashtable.Contains(key)) 13 | { 14 | var obj = hashtable[key]; 15 | value = obj == null ? string.Empty : obj.ToString(); 16 | return true; 17 | } 18 | 19 | value = null; 20 | return false; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/HeaderUtility.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System.Collections; 5 | using System.Text; 6 | 7 | namespace nanoFramework.WebServer.HttpMultipartParser 8 | { 9 | /// 10 | /// Provides parsing headers from a Http Multipart Form 11 | /// 12 | public static class HeaderUtility 13 | { 14 | /// 15 | /// Reads headers from a line of text. 16 | /// Headers are delimited by a semi-colon ';' 17 | /// Key-value pairs are separated by colon ':' or equals '=' 18 | /// Values can be delimited by quotes '"' or not 19 | /// 20 | /// The line of text containing one or more headers 21 | /// 22 | /// The hashtable that will receive the key values. 23 | /// Passed in since a Multipart Part can contain multiple lines of headers 24 | /// 25 | public static void ParseHeaders(string text, Hashtable headers) 26 | { 27 | bool inQuotes = false; 28 | bool inKey = true; 29 | StringBuilder key = new(); 30 | StringBuilder value = new(); 31 | 32 | foreach (char c in text) 33 | { 34 | if (c == '"') 35 | { 36 | inQuotes = !inQuotes; 37 | } 38 | else if (inQuotes) 39 | { 40 | value.Append(c); 41 | } 42 | else if (c == ';') 43 | { 44 | headers[key.ToString().ToLower()] = value.ToString(); 45 | key.Clear(); 46 | inKey = true; 47 | } 48 | else if (c == '=' || c == ':') 49 | { 50 | value = value.Clear(); 51 | inKey = false; 52 | } 53 | else if (c != ' ') 54 | { 55 | if (inKey) 56 | { 57 | key.Append(c); 58 | } 59 | else 60 | { 61 | value.Append(c); 62 | } 63 | } 64 | } 65 | 66 | if (key.Length > 0) 67 | { 68 | headers.Add(key.ToString().ToLower(), value.ToString()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/LineBuffer.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | using System.Collections; 6 | 7 | namespace nanoFramework.WebServer.HttpMultipartParser 8 | { 9 | internal sealed class LineBuffer : IDisposable 10 | { 11 | private readonly ArrayList _data = new(); 12 | 13 | public void Dispose() => _data.Clear(); 14 | 15 | public void Write(SpanByte spanByte) 16 | { 17 | _data.Add(spanByte.ToArray()); 18 | Length += spanByte.Length; 19 | } 20 | 21 | public int Length { get; private set; } = 0; 22 | 23 | public byte[] ToArray(bool clear = false) 24 | { 25 | byte[] result = new byte[Length]; 26 | int pos = 0; 27 | 28 | foreach (object data in _data) 29 | { 30 | if (data is byte b) 31 | { 32 | result[pos++] = b; 33 | } 34 | else if (data is byte[] array) 35 | { 36 | Array.Copy(array, 0, result, pos, array.Length); 37 | pos += array.Length; 38 | } 39 | } 40 | 41 | if (clear) 42 | { 43 | Clear(); 44 | } 45 | 46 | return result; 47 | } 48 | 49 | public void Clear() 50 | { 51 | _data.Clear(); 52 | Length = 0; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/LineReader.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace nanoFramework.WebServer.HttpMultipartParser 10 | { 11 | /// Provides methods to read a stream line by line while still returning the bytes. 12 | internal class LineReader 13 | { 14 | private readonly Stream _stream; 15 | private readonly byte[] _buffer; 16 | private readonly LineBuffer _lineBuffer = new(); 17 | int _availableBytes = -1; 18 | int _position = 0; 19 | 20 | /// Initializes a new instance of the class. 21 | /// The input stream to read from. 22 | /// The buffer size to use for new buffers. 23 | public LineReader(Stream stream, int bufferSize) 24 | { 25 | _stream = stream; 26 | _buffer = new byte[bufferSize]; 27 | } 28 | 29 | /// 30 | /// Reads a line from the stack delimited by the newline for this platform. 31 | /// The newline characters will not be included in the stream. 32 | /// 33 | /// 34 | /// If true the newline characters will be stripped when the line returns. 35 | /// When reading binary data, newline characters are meaningfull and should be returned 36 | /// 37 | /// The byte[] containing the line or null if end of stream. 38 | public byte[] ReadByteLine(bool excludeNewLine = true) 39 | { 40 | while (ReadFromStream() > 0) 41 | { 42 | for (int i = _position; i < _availableBytes; i++) 43 | { 44 | if (_buffer[i] == '\n') 45 | { 46 | // newline found, time to return the line 47 | int length = GetLength(excludeNewLine, i); 48 | 49 | byte[] line = GetLine(length); 50 | 51 | _position = i + 1; 52 | 53 | return line; 54 | } 55 | } 56 | 57 | // if we get here, no newline found in current buffer 58 | // store what we have left in the buffer into the lineBuffer 59 | _lineBuffer.Write(new SpanByte(_buffer, _position, _availableBytes - _position)); 60 | _position = _availableBytes; 61 | } 62 | 63 | #pragma warning disable S1168 // null and empty do have meaning 64 | // no more bytes available, return what's in the lineBuffer 65 | // if lineBuffer is empty, we're truly done, return null! 66 | return _lineBuffer.Length == 0 ? null : _lineBuffer.ToArray(true); 67 | #pragma warning restore S1168 68 | } 69 | 70 | private byte[] GetLine(int length) 71 | { 72 | byte[] line; 73 | if (_lineBuffer.Length > 0) 74 | { 75 | _lineBuffer.Write(new SpanByte(_buffer, _position, length)); 76 | line = _lineBuffer.ToArray(true); 77 | } 78 | else 79 | { 80 | line = new byte[length]; 81 | Array.Copy(_buffer, _position, line, 0, length); 82 | } 83 | 84 | return line; 85 | } 86 | 87 | private int GetLength(bool excludeNewLine, int currentPosition) 88 | { 89 | int length = currentPosition - _position + 1; 90 | 91 | if (excludeNewLine) 92 | { 93 | length -= currentPosition > 0 && _buffer[currentPosition - 1] == '\r' ? 2 : 1; 94 | } 95 | 96 | return length; 97 | } 98 | 99 | private int ReadFromStream() 100 | { 101 | if (_position >= _availableBytes) 102 | { 103 | // The stream is (should be) a NetworkStream which might still be receiving data while 104 | // we're already processing. Give the stream a chance to receive more data or we might 105 | // end up with "zero bytes read" too soon... 106 | Thread.Sleep(1); 107 | 108 | long streamLength = _stream.Length; 109 | 110 | if (streamLength > _buffer.Length) 111 | { 112 | streamLength = _buffer.Length; 113 | } 114 | 115 | _availableBytes = _stream.Read(_buffer, 0, (int)streamLength); 116 | _position = 0; 117 | } 118 | 119 | return _availableBytes; 120 | } 121 | 122 | /// 123 | /// Reads a line from the stack delimited by the newline for this platform. 124 | /// The newline characters will not be included in the stream. 125 | /// 126 | /// The containing the line or null if end of stream. 127 | public string ReadLine() 128 | { 129 | byte[] data = ReadByteLine(); 130 | return data == null ? null : Encoding.UTF8.GetString(data, 0, data.Length); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/MultipartFormDataParser.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.IO; 7 | using System.Text; 8 | 9 | namespace nanoFramework.WebServer.HttpMultipartParser 10 | { 11 | /// 12 | /// Provides methods to parse a 13 | /// 14 | /// multipart/form-data 15 | /// 16 | /// stream into it's parameters and file data. 17 | /// 18 | public class MultipartFormDataParser 19 | { 20 | private const int defaultBufferSize = 4096; 21 | 22 | private readonly bool _ignoreInvalidParts; 23 | private readonly int _binaryBufferSize; 24 | private string _boundary; 25 | private byte[] _boundaryBinary; 26 | private readonly Stream _stream; 27 | private bool _readEndBoundary; 28 | private readonly ArrayList _files = new(); 29 | private readonly ArrayList _parameters = new(); 30 | 31 | /// Initializes a new instance of the class 32 | /// The stream containing the multipart data. 33 | /// The size of the buffer to use for parsing the multipart form data. 34 | /// By default the parser will throw an exception if it encounters an invalid part. Set this to true to ignore invalid parts. 35 | public MultipartFormDataParser(Stream stream, int binaryBufferSize = defaultBufferSize, bool ignoreInvalidParts = false) 36 | { 37 | _stream = stream ?? throw new ArgumentNullException(nameof(stream)); 38 | _binaryBufferSize = binaryBufferSize; 39 | _ignoreInvalidParts = ignoreInvalidParts; 40 | } 41 | 42 | /// Gets the mapping of parameters parsed files. The name of a given field maps to the parsed file data. 43 | public FilePart[] Files => _files.ToArray(typeof(FilePart)) as FilePart[]; 44 | 45 | /// Gets the parameters. Several ParameterParts may share the same name. 46 | public ParameterPart[] Parameters => _parameters.ToArray(typeof(ParameterPart)) as ParameterPart[]; 47 | 48 | /// Parse the stream into a new instance of the class 49 | /// The stream containing the multipart data. 50 | /// The size of the buffer to use for parsing the multipart form data. 51 | /// By default the parser will throw an exception if it encounters an invalid part. Set this to true to ignore invalid parts. 52 | /// A new instance of the class. 53 | public static MultipartFormDataParser Parse(Stream stream, int binaryBufferSize = defaultBufferSize, bool ignoreInvalidParts = false) 54 | { 55 | var parser = new MultipartFormDataParser(stream, binaryBufferSize, ignoreInvalidParts); 56 | parser.Run(); 57 | return parser; 58 | } 59 | 60 | private void Run() 61 | { 62 | var reader = new LineReader(_stream, _binaryBufferSize); 63 | 64 | _boundary = DetectBoundary(reader); 65 | _boundaryBinary = Encoding.UTF8.GetBytes(_boundary); 66 | 67 | // we have read until we encountered the boundary so we should be at the first section => parse it! 68 | while (!_readEndBoundary) 69 | { 70 | ParseSection(reader); 71 | } 72 | } 73 | 74 | private static string DetectBoundary(LineReader reader) 75 | { 76 | string line = string.Empty; 77 | while (line == string.Empty) 78 | { 79 | line = reader.ReadLine(); 80 | } 81 | 82 | if (string.IsNullOrEmpty(line)) 83 | { 84 | // EMF001: Unable to determine boundary: either the stream is empty or we reached the end of the stream 85 | throw new MultipartFormDataParserException("EMF001"); 86 | } 87 | else if (!line.StartsWith("--")) 88 | { 89 | // EMF002: Unable to determine boundary: content does not start with a valid multipart boundary 90 | throw new MultipartFormDataParserException("EMF002"); 91 | } 92 | 93 | return line.EndsWith("--") ? line.Substring(0, line.Length - 2) : line; 94 | } 95 | 96 | private void ParseSection(LineReader reader) 97 | { 98 | Hashtable parameters = new(); 99 | 100 | string line = reader.ReadLine(); 101 | while (line != string.Empty) 102 | { 103 | if (line == null || line.StartsWith(_boundary)) 104 | { 105 | // EMF003: Unexpected end of section 106 | throw new MultipartFormDataParserException("EMF003"); 107 | } 108 | 109 | HeaderUtility.ParseHeaders(line, parameters); 110 | 111 | line = reader.ReadLine(); 112 | } 113 | 114 | if (IsFilePart(parameters)) 115 | { 116 | ParseFilePart(parameters, reader); 117 | } 118 | else if (IsParameterPart(parameters)) 119 | { 120 | ParseParameterPart(parameters, reader); 121 | } 122 | else if (_ignoreInvalidParts) 123 | { 124 | SkipPart(reader); 125 | } 126 | else 127 | { 128 | // EMF004: Unable to determine the section type. Some possible reasons include: 129 | // - section is malformed 130 | // - required parameters such as 'name', 'content-type' or 'filename' are missing 131 | // - section contains nothing but empty lines. 132 | throw new MultipartFormDataParserException("EMF004"); 133 | } 134 | } 135 | 136 | private bool IsFilePart(Hashtable parameters) => parameters.Contains("filename") || 137 | parameters.Contains("content-type") || 138 | (!parameters.Contains("name") && parameters.Count > 0); 139 | 140 | private bool IsParameterPart(Hashtable parameters) => parameters.Contains("name"); 141 | 142 | private void ParseFilePart(Hashtable parameters, LineReader reader) 143 | { 144 | // Read the parameters 145 | parameters.TryGetValue("name", out string name); 146 | parameters.TryGetValue("filename", out string filename); 147 | parameters.TryGetValue("content-type", out string contentType); 148 | parameters.TryGetValue("content-disposition", out string contentDisposition); 149 | 150 | RemoveWellKnownParameters(parameters); 151 | 152 | // Default values if expected parameters are missing 153 | contentType ??= "text/plain"; 154 | contentDisposition ??= "form-data"; 155 | 156 | MemoryStream stream = new(); 157 | 158 | while (true) 159 | { 160 | byte[] line = reader.ReadByteLine(false); 161 | 162 | if (CheckForBoundary(line)) 163 | { 164 | TrimEndline(stream); 165 | 166 | stream.Position = 0; 167 | 168 | _files.Add(new FilePart(name, filename, stream, parameters, contentType, contentDisposition)); 169 | break; 170 | } 171 | 172 | stream.Write(line, 0, line.Length); 173 | } 174 | } 175 | 176 | private static void TrimEndline(MemoryStream stream) 177 | { 178 | if (stream.Length == 0) 179 | { 180 | return; 181 | } 182 | 183 | stream.Position = stream.Length - 1; 184 | 185 | int b = stream.ReadByte(); 186 | int clip = 0; 187 | 188 | if (b == '\n') 189 | { 190 | clip = 1; 191 | 192 | if (stream.Length > 1) 193 | { 194 | stream.Position = stream.Length - 2; 195 | b = stream.ReadByte(); 196 | 197 | if (b == '\r') 198 | { 199 | clip = 2; 200 | } 201 | } 202 | } 203 | 204 | if (clip > 0) 205 | { 206 | stream.SetLength(stream.Length - clip); 207 | } 208 | } 209 | 210 | private void ParseParameterPart(Hashtable parameters, LineReader reader) 211 | { 212 | StringBuilder sb = new(); 213 | 214 | while (true) 215 | { 216 | byte[] line = reader.ReadByteLine(); 217 | 218 | if (line == null || CheckForBoundary(line)) 219 | { 220 | _parameters.Add(new ParameterPart(parameters["name"].ToString(), sb.ToString())); 221 | break; 222 | } 223 | 224 | sb.Append(Encoding.UTF8.GetString(line, 0, line.Length)); 225 | } 226 | } 227 | 228 | private void SkipPart(LineReader reader) 229 | { 230 | while (true) 231 | { 232 | byte[] line = reader.ReadByteLine(); 233 | if (line == null || CheckForBoundary(line)) 234 | break; 235 | } 236 | } 237 | 238 | private bool CheckForBoundary(byte[] line) 239 | { 240 | if (line == null) 241 | { 242 | _readEndBoundary = true; 243 | return true; 244 | } 245 | 246 | int length = _boundaryBinary.Length; 247 | 248 | if (line.Length < length) 249 | { 250 | return false; 251 | } 252 | 253 | for (int i = 0; i < length; i++) 254 | { 255 | if (line[i] != _boundaryBinary[i]) 256 | { 257 | return false; 258 | } 259 | } 260 | 261 | // if we get here we have a boundary, check if it is the endboundary 262 | if (line.Length >= length + 2 && line[length] == '-' && line[length + 1] == '-') 263 | { 264 | _readEndBoundary = true; 265 | } 266 | 267 | return true; 268 | } 269 | 270 | private void RemoveWellKnownParameters(Hashtable parameters) 271 | { 272 | string[] wellKnownParameters = new[] { "name", "filename", "filename*", "content-type", "content-disposition" }; 273 | 274 | foreach (string parameter in wellKnownParameters) 275 | if (parameters.Contains(parameter)) 276 | parameters.Remove(parameter); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/MultipartFormDataParserException.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | 6 | namespace nanoFramework.WebServer.HttpMultipartParser 7 | { 8 | /// 9 | /// Specific exception while parsing a multipart form 10 | /// 11 | public class MultipartFormDataParserException : Exception 12 | { 13 | /// 14 | /// Initializes a MultipartFormDataParserException 15 | /// 16 | /// 17 | public MultipartFormDataParserException(string message) : base(message) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpMultipartParser/ParameterPart.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | namespace nanoFramework.WebServer.HttpMultipartParser 5 | { 6 | /// Represents a single parameter extracted from a multipart/form-data stream. 7 | public class ParameterPart 8 | { 9 | /// Initializes a new instance of the class. 10 | /// The name. 11 | /// The data. 12 | public ParameterPart(string name, string data) 13 | { 14 | Name = name; 15 | Data = data; 16 | } 17 | 18 | /// Gets the data. 19 | public string Data 20 | { 21 | get; 22 | } 23 | 24 | /// Gets the name. 25 | public string Name 26 | { 27 | get; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/HttpProtocol.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | namespace nanoFramework.WebServer 7 | { 8 | /// 9 | /// Http protocol used 10 | /// 11 | public enum HttpProtocol 12 | { 13 | /// 14 | /// Http protocol 15 | /// 16 | Http, 17 | 18 | /// 19 | /// Https protocol 20 | /// 21 | Https 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/MethodAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | 8 | namespace nanoFramework.WebServer 9 | { 10 | /// 11 | /// The HTTP Method. 12 | /// 13 | /// 14 | /// No validation is performed if the HTTP method is a valid one. 15 | /// For details on how to use, see: https://github.com/nanoframework/nanoFramework.WebServer#usage 16 | /// 17 | [AttributeUsage(AttributeTargets.Method)] 18 | public class MethodAttribute : Attribute 19 | { 20 | /// 21 | /// Gets the method. 22 | /// 23 | public string Method { get; } 24 | 25 | /// 26 | /// Creates a method attribute. 27 | /// 28 | /// The method. 29 | public MethodAttribute(string method) 30 | { 31 | Method = method; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("nanoFramework.WebServer")] 9 | [assembly: AssemblyCompany("nanoFramework Contributors")] 10 | [assembly: AssemblyProduct("nanoFramework.WebServer")] 11 | [assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] 12 | 13 | 14 | // Setting ComVisible to false makes the types in this assembly not visible 15 | // to COM components. If you need to access a type in this assembly from 16 | // COM, set the ComVisible attribute to true on that type. 17 | [assembly: ComVisible(false)] 18 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/RouteAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | 8 | namespace nanoFramework.WebServer 9 | { 10 | /// 11 | /// Route custom attribute. 12 | /// 13 | /// 14 | /// For example: test/any. 15 | /// For details on how to use, see: https://github.com/nanoframework/nanoFramework.WebServer#usage 16 | /// 17 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 18 | public class RouteAttribute : Attribute 19 | { 20 | /// 21 | /// Gets or sets the route. 22 | /// 23 | public string Route { get; set; } 24 | 25 | /// 26 | /// A route attribute. 27 | /// 28 | /// The complete route like 'route/second/third'. 29 | public RouteAttribute(string route) 30 | { 31 | Route = route; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/UrlParameter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System; 7 | using System.Text; 8 | 9 | namespace nanoFramework.WebServer 10 | { 11 | /// 12 | /// Represent an URL parameter Name=Value 13 | /// 14 | public class UrlParameter 15 | { 16 | /// 17 | /// Name of the parameter 18 | /// 19 | public string Name { get; set; } 20 | 21 | /// 22 | /// Valeu of the parameter 23 | /// 24 | public string Value { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/WebServerEventArgs.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using System.Net; 7 | 8 | namespace nanoFramework.WebServer 9 | { 10 | /// 11 | /// Web server event argument class 12 | /// 13 | public class WebServerEventArgs 14 | { 15 | /// 16 | /// Constructor for the event arguments 17 | /// 18 | public WebServerEventArgs(HttpListenerContext context) 19 | { 20 | Context = context; 21 | } 22 | 23 | /// 24 | /// The response class 25 | /// 26 | public HttpListenerContext Context { get; protected set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/WebServerStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 2 | // See LICENSE file in the project root for full license information. 3 | 4 | namespace nanoFramework.WebServer 5 | { 6 | /// 7 | /// Represents the status of the server. 8 | /// 9 | public enum WebServerStatus 10 | { 11 | /// 12 | /// The server is stopped. 13 | /// 14 | Stopped, 15 | 16 | /// 17 | /// The server is running. 18 | /// 19 | Running, 20 | } 21 | } -------------------------------------------------------------------------------- /nanoFramework.WebServer/WebServerStatusEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 2 | // See LICENSE file in the project root for full license information. 3 | 4 | namespace nanoFramework.WebServer 5 | { 6 | /// 7 | /// Provides data for the WebServerStatus event. 8 | /// 9 | public class WebServerStatusEventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the WebServerStatusEventArgs class with the specified status. 13 | /// 14 | /// The status of the web server. 15 | public WebServerStatusEventArgs(WebServerStatus status) 16 | { 17 | Status = status; 18 | } 19 | 20 | /// 21 | /// Gets the status of the web server. 22 | /// 23 | public WebServerStatus Status { get; protected set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/nanoFramework.WebServer.nfproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | $(MSBuildExtensionsPath)\nanoFramework\v1.0\ 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 12 | 87aaa5fe-cbb6-497f-97b7-7af21b9a0c4e 13 | Library 14 | Properties 15 | 512 16 | nanoFramework.WebServer 17 | nanoFramework.WebServer 18 | v1.0 19 | bin\$(Configuration)\nanoFramework.WebServer.xml 20 | true 21 | true 22 | 23 | 24 | true 25 | 26 | 27 | ..\key.snk 28 | 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll 63 | 64 | 65 | ..\packages\nanoFramework.Runtime.Events.1.11.32\lib\nanoFramework.Runtime.Events.dll 66 | 67 | 68 | ..\packages\nanoFramework.System.Collections.1.5.67\lib\nanoFramework.System.Collections.dll 69 | 70 | 71 | ..\packages\nanoFramework.System.Text.1.3.42\lib\nanoFramework.System.Text.dll 72 | 73 | 74 | ..\packages\nanoFramework.System.IO.Streams.1.1.96\lib\System.IO.Streams.dll 75 | 76 | 77 | ..\packages\nanoFramework.System.Net.1.11.43\lib\System.Net.dll 78 | 79 | 80 | ..\packages\nanoFramework.System.Net.Http.Server.1.5.196\lib\System.Net.Http.dll 81 | 82 | 83 | ..\packages\nanoFramework.System.Threading.1.1.52\lib\System.Threading.dll 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /nanoFramework.WebServer/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETnanoFramework,Version=v1.0": { 5 | "nanoFramework.CoreLibrary": { 6 | "type": "Direct", 7 | "requested": "[1.17.11, 1.17.11]", 8 | "resolved": "1.17.11", 9 | "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" 10 | }, 11 | "nanoFramework.Runtime.Events": { 12 | "type": "Direct", 13 | "requested": "[1.11.32, 1.11.32]", 14 | "resolved": "1.11.32", 15 | "contentHash": "NyLUIwJDlpl5VKSd+ljmdDtO2WHHBvPvruo1ccaL+hd79z+6XMYze1AccOVXKGiZenLBCwDmFHwpgIQyHkM7GA==" 16 | }, 17 | "nanoFramework.System.Collections": { 18 | "type": "Direct", 19 | "requested": "[1.5.67, 1.5.67]", 20 | "resolved": "1.5.67", 21 | "contentHash": "MjSipUB70vrxjqTm1KfKTUqqjd0wbweiNyYFXONi0XClrH6HXsuX2lhDqXM8NWuYnWyYOqx8y20sXbvsH+4brg==" 22 | }, 23 | "nanoFramework.System.IO.Streams": { 24 | "type": "Direct", 25 | "requested": "[1.1.96, 1.1.96]", 26 | "resolved": "1.1.96", 27 | "contentHash": "kJSy4EJwChO4Vq3vGWP9gNRPFDnTsDU5HxzeI7NDO+RjbDsx7B8EhKymoeTPLJCxQq8y/0P1KG2XCxGpggW+fw==" 28 | }, 29 | "nanoFramework.System.Net": { 30 | "type": "Direct", 31 | "requested": "[1.11.43, 1.11.43]", 32 | "resolved": "1.11.43", 33 | "contentHash": "USwz59gxcNUzsiXfQohWSi8ANNwGDsp+qG4zBtHZU3rKMtvTsLI3rxdfMC77VehKqsCPn7aK3PU2oCRFo+1Rgg==" 34 | }, 35 | "nanoFramework.System.Net.Http.Server": { 36 | "type": "Direct", 37 | "requested": "[1.5.196, 1.5.196]", 38 | "resolved": "1.5.196", 39 | "contentHash": "cjr5Rj39duOjGcyvo/LMFdoeTeLg0zpFgFB7wJUXw0+65EiENEnJwqqR1CfbJEvBBpBMJdH/yLkK/8DU8Jk3XQ==" 40 | }, 41 | "nanoFramework.System.Text": { 42 | "type": "Direct", 43 | "requested": "[1.3.42, 1.3.42]", 44 | "resolved": "1.3.42", 45 | "contentHash": "68HPjhersNpssbmEMUHdMw3073MHfGTfrkbRk9eILKbNPFfPFck7m4y9BlAi6DaguUJaeKxgyIojXF3SQrF8/A==" 46 | }, 47 | "nanoFramework.System.Threading": { 48 | "type": "Direct", 49 | "requested": "[1.1.52, 1.1.52]", 50 | "resolved": "1.1.52", 51 | "contentHash": "kv+US/+7QKV1iT/snxBh032vwZ+3krJ4vujlSsvmS2nNj/nK64R3bq/ST3bCFquxHDD0mog8irtCBCsFazr4kA==" 52 | }, 53 | "Nerdbank.GitVersioning": { 54 | "type": "Direct", 55 | "requested": "[3.7.115, 3.7.115]", 56 | "resolved": "3.7.115", 57 | "contentHash": "EpXamaAdRfG/BMxGgvZlTM0npRnkmXUjAj8OdNKd17t4oN+2nvjdv/KnFmzOOMDqvlwB49UCwtOHJrAQTfUBtQ==" 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /spelling_exclusion.dic: -------------------------------------------------------------------------------- 1 | nano 2 | -------------------------------------------------------------------------------- /template.vssettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 4 8 | false 9 | 4 10 | true 11 | 12 | 13 | 1 14 | 0 15 | 1 16 | 1 17 | 1 18 | 1 19 | 1 20 | 2 21 | 1 22 | 0 23 | 0 24 | 1 25 | 1 26 | 0 27 | 0 28 | 0 29 | 0 30 | 0 31 | 1 32 | 1 33 | 0 34 | 0 35 | 0 36 | 1 37 | 0 38 | 1 39 | 0 40 | 0 41 | 1 42 | 1 43 | 0 44 | 0 45 | 0 46 | 0 47 | 0 48 | 1 49 | 0 50 | 1 51 | 0 52 | 0 53 | 1 54 | 1 55 | 0 56 | 0 57 | 1 58 | 0 59 | 0 60 | 1 61 | 0 62 | 1 63 | 1 64 | 0 65 | 0 66 | 0 67 | 1 68 | 1 69 | 1 70 | 1 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/.gitignore: -------------------------------------------------------------------------------- 1 | WiFi.cs -------------------------------------------------------------------------------- /tests/WebServerE2ETests/AuthController.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System.Net; 5 | using nanoFramework.WebServer; 6 | 7 | namespace WebServerE2ETests 8 | { 9 | [Authentication("Basic")] 10 | class AuthController 11 | { 12 | [Route("authbasic")] 13 | public void Basic(WebServerEventArgs e) 14 | { 15 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 16 | } 17 | 18 | [Route("authbasicspecial")] 19 | [Authentication("Basic:user2 password")] 20 | public void Special(WebServerEventArgs e) 21 | { 22 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 23 | } 24 | 25 | [Authentication("ApiKey:superKey1234")] 26 | [Route("authapi")] 27 | public void Key(WebServerEventArgs e) 28 | { 29 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 30 | } 31 | 32 | [Route("authnone")] 33 | [Authentication("None")] 34 | public void None(WebServerEventArgs e) 35 | { 36 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 37 | } 38 | 39 | [Authentication("ApiKey")] 40 | [Route("authdefaultapi")] 41 | public void DefaultApi(WebServerEventArgs e) 42 | { 43 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/MixedController.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using nanoFramework.WebServer; 5 | 6 | namespace WebServerE2ETests 7 | { 8 | class MixedController 9 | { 10 | #region ApiKey + public 11 | [Route("authapikeyandpublic")] 12 | [Authentication("ApiKey:superKey1234")] 13 | public void ApiKeyAndPublicApiKey(WebServerEventArgs e) 14 | { 15 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Public: ApiKey"); 16 | } 17 | 18 | [Route("authapikeyandpublic")] 19 | public void ApiKeyAndPublicPublic(WebServerEventArgs e) 20 | { 21 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Public: Public"); 22 | } 23 | #endregion 24 | 25 | #region Basic + public 26 | [Route("authbasicandpublic")] 27 | [Authentication("Basic:user2 password")] 28 | public void BasicAndPublicBasic(WebServerEventArgs e) 29 | { 30 | WebServer.OutPutStream(e.Context.Response, "Basic+Public: Basic"); 31 | } 32 | 33 | [Route("authbasicandpublic")] 34 | public void BasicAndPublicPublic(WebServerEventArgs e) 35 | { 36 | WebServer.OutPutStream(e.Context.Response, "Basic+Public: Public"); 37 | } 38 | #endregion 39 | 40 | #region Basic + ApiKey + Public 41 | [Route("authapikeybasicandpublic")] 42 | public void ApiKeyBasicAndPublicPublic(WebServerEventArgs e) 43 | { 44 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Public"); 45 | } 46 | 47 | [Route("authapikeybasicandpublic")] 48 | [Authentication("Basic:user3 password")] 49 | public void ApiKeyBasicAndPublicBasic3(WebServerEventArgs e) 50 | { 51 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Basic user3"); 52 | } 53 | 54 | [Route("authapikeybasicandpublic")] 55 | [Authentication("Basic:user2 password")] 56 | public void ApiKeyBasicAndPublicBasic2(WebServerEventArgs e) 57 | { 58 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: Basic user2"); 59 | } 60 | 61 | [Authentication("ApiKey:superKey1234")] 62 | [Route("authapikeybasicandpublic")] 63 | public void ApiKeyBasicAndPublicApiKey(WebServerEventArgs e) 64 | { 65 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: ApiKey"); 66 | } 67 | 68 | [Authentication("ApiKey:superKey42")] 69 | [Route("authapikeybasicandpublic")] 70 | public void ApiKeyBasicAndPublicApiKey2(WebServerEventArgs e) 71 | { 72 | WebServer.OutPutStream(e.Context.Response, "ApiKey+Basic+Public: ApiKey 2"); 73 | } 74 | #endregion 75 | 76 | #region Multiple callbacks 77 | [Route("authmultiple")] 78 | public void MultiplePublic1(WebServerEventArgs e) 79 | { 80 | WebServer.OutPutStream(e.Context.Response, "Multiple: Public1"); 81 | } 82 | 83 | [Route("authmultiple")] 84 | [Authentication("Basic:user2 password")] 85 | public void MultipleBasic1(WebServerEventArgs e) 86 | { 87 | WebServer.OutPutStream(e.Context.Response, "Multiple: Basic1"); 88 | } 89 | 90 | [Route("authmultiple")] 91 | public void MultiplePublic2(WebServerEventArgs e) 92 | { 93 | WebServer.OutPutStream(e.Context.Response, "Multiple: Public2"); 94 | } 95 | 96 | [Authentication("ApiKey:superKey1234")] 97 | [Route("authmultiple")] 98 | public void MultipleApiKey1(WebServerEventArgs e) 99 | { 100 | WebServer.OutPutStream(e.Context.Response, "Multiple: ApiKey1"); 101 | } 102 | 103 | [Route("authmultiple")] 104 | [Authentication("Basic:user2 password")] 105 | public void MultipleBasic2(WebServerEventArgs e) 106 | { 107 | WebServer.OutPutStream(e.Context.Response, "Multiple: Basic2"); 108 | } 109 | 110 | [Authentication("ApiKey:superKey1234")] 111 | [Route("authmultiple")] 112 | public void MultipleApiKey2(WebServerEventArgs e) 113 | { 114 | WebServer.OutPutStream(e.Context.Response, "Multiple: ApiKey2"); 115 | } 116 | #endregion 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/PostPutController.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | using System.Net; 6 | using nanoFramework.WebServer; 7 | 8 | namespace WebServerE2ETests 9 | { 10 | class PostPutController 11 | { 12 | [Method("POST")] 13 | [Route("post")] 14 | public void Post(WebServerEventArgs e) 15 | { 16 | byte[] buff = new byte[e.Context.Request.InputStream.Length]; 17 | e.Context.Request.InputStream.Read(buff, 0, buff.Length); 18 | var txt = e.Context.Request.ContentType.Contains("text") ? System.Text.Encoding.UTF8.GetString(buff, 0, buff.Length) : BitConverter.ToString(buff); 19 | WebServer.OutPutStream(e.Context.Response, $"POST: {txt}"); 20 | } 21 | 22 | [Method("PUT")] 23 | [Route("put")] 24 | public void Put(WebServerEventArgs e) 25 | { 26 | byte[] buff = new byte[e.Context.Request.InputStream.Length]; 27 | e.Context.Request.InputStream.Read(buff, 0, buff.Length); 28 | var txt = e.Context.Request.ContentType.Contains("text") ? System.Text.Encoding.UTF8.GetString(buff, 0, buff.Length) : BitConverter.ToString(buff); 29 | WebServer.OutPutStream(e.Context.Response, $"PUT: {txt}"); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Net; 8 | using System.Net.NetworkInformation; 9 | using System.Text; 10 | using System.Threading; 11 | using nanoFramework.Networking; 12 | using nanoFramework.WebServer; 13 | 14 | namespace WebServerE2ETests 15 | { 16 | public partial class Program 17 | { 18 | private static WebServer _server; 19 | 20 | public static void Main() 21 | { 22 | Debug.WriteLine("Hello from nanoFramework WebServer end to end tests!"); 23 | 24 | var res = WifiNetworkHelper.ConnectDhcp(Ssid, Password, requiresDateTime: true, token: new CancellationTokenSource(60_000).Token); 25 | if (!res) 26 | { 27 | Debug.WriteLine("Impossible to connect to wifi, most likely invalid credentials"); 28 | return; 29 | } 30 | 31 | Debug.WriteLine($"Connected with wifi credentials. IP Address: {GetCurrentIPAddress()}"); 32 | _server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(SimpleRouteController), typeof(AuthController), typeof(MixedController), typeof(PostPutController) }); 33 | // To test authentication with various scenarios 34 | _server.ApiKey = "ATopSecretAPIKey1234"; 35 | _server.Credential = new NetworkCredential("topuser", "topPassword"); 36 | // Add a handler for commands that are received by the server. 37 | _server.CommandReceived += ServerCommandReceived; 38 | _server.WebServerStatusChanged += WebServerStatusChanged; 39 | 40 | // Start the server. 41 | _server.Start(); 42 | 43 | Thread.Sleep(Timeout.Infinite); 44 | 45 | // Browse our samples repository: https://github.com/nanoframework/samples 46 | // Check our documentation online: https://docs.nanoframework.net/ 47 | // Join our lively Discord community: https://discord.gg/gCyBu8T 48 | } 49 | 50 | private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs e) 51 | { 52 | Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped")}"); 53 | } 54 | 55 | private static void ServerCommandReceived(object obj, WebServerEventArgs e) 56 | { 57 | const string FileName = "I:\\Text.txt"; 58 | var url = e.Context.Request.RawUrl; 59 | Debug.WriteLine($"{nameof(ServerCommandReceived)} {e.Context.Request.HttpMethod} {url}"); 60 | 61 | if (url.ToLower().IndexOf("/param.htm") == 0) 62 | { 63 | // Test with parameters 64 | var parameters = WebServer.DecodeParam(url); 65 | string toOutput = "" + 66 | "Hi from nanoFramework ServerHere are the parameters of this URL:
"; 67 | foreach (var par in parameters) 68 | { 69 | toOutput += $"Parameter name: {par.Name}, Value: {par.Value}
"; 70 | } 71 | toOutput += ""; 72 | WebServer.OutPutStream(e.Context.Response, toOutput); 73 | return; 74 | } 75 | else if (url.IndexOf("/Text.txt") == 0) 76 | { 77 | if (File.Exists(FileName)) 78 | { 79 | WebServer.SendFileOverHTTP(e.Context.Response, FileName); 80 | return; 81 | } 82 | else 83 | { 84 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.NotFound); 85 | return; 86 | } 87 | } 88 | else if (url.IndexOf("/Text2.txt") == 0) 89 | { 90 | WebServer.SendFileOverHTTP(e.Context.Response, "Text2.txt", Encoding.UTF8.GetBytes("This is a test file for WebServer")); 91 | return; 92 | } 93 | else if (url.ToLower().IndexOf("/useinternal") == 0) 94 | { 95 | File.WriteAllText(FileName, "This is a test file for WebServer"); 96 | return; 97 | } 98 | else 99 | { 100 | WebServer.OutPutStream(e.Context.Response, "" + 101 | "Hi from nanoFramework ServerYou want me to say hello in a real HTML page!
Generate an internal text.txt file
" + 102 | "Download the Text.txt file
" + 103 | "Try this url with parameters: /param.htm?param1=42&second=24&NAme=Ellerbach"); 104 | return; 105 | } 106 | } 107 | 108 | public static string GetCurrentIPAddress() 109 | { 110 | NetworkInterface ni = NetworkInterface.GetAllNetworkInterfaces()[0]; 111 | 112 | // get first NI ( Wifi on ESP32 ) 113 | return ni.IPv4Address.ToString(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSharp.BlankApplication")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CSharp.BlankApplication")] 13 | [assembly: AssemblyCopyright("Copyright © 2024")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/SimpleRouteController.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System.Diagnostics; 5 | using System.Net; 6 | using nanoFramework.WebServer; 7 | 8 | namespace WebServerE2ETests 9 | { 10 | internal class SimpleRouteController 11 | { 12 | [Route("okcode")] 13 | public void OutputWithOKCode(WebServerEventArgs e) 14 | { 15 | Debug.WriteLine($"{nameof(OutputWithOKCode)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}"); 16 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 17 | } 18 | 19 | [Route("notfoundcode")] 20 | public void OutputWithNotFoundCode(WebServerEventArgs e) 21 | { 22 | Debug.WriteLine($"{nameof(OutputWithNotFoundCode)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}"); 23 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.NotFound); 24 | } 25 | 26 | [Route("oktext")] 27 | public void OutputWithOKText(WebServerEventArgs e) 28 | { 29 | Debug.WriteLine($"{nameof(OutputWithOKText)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}"); 30 | WebServer.OutPutStream(e.Context.Response, "OK"); 31 | } 32 | 33 | [Route("test"), Route("Test2"), Route("tEst42"), Route("TEST")] 34 | [CaseSensitive] 35 | [Method("GET")] 36 | public void RouteGetTest(WebServerEventArgs e) 37 | { 38 | string route = $"The route asked is {e.Context.Request.RawUrl.TrimStart('/').Split('/')[0]}"; 39 | e.Context.Response.ContentType = "text/plain"; 40 | WebServer.OutPutStream(e.Context.Response, route); 41 | } 42 | 43 | [Route("test/any")] 44 | public void RouteAnyTest(WebServerEventArgs e) 45 | { 46 | WebServer.OutputHttpCode(e.Context.Response, HttpStatusCode.OK); 47 | } 48 | 49 | [Route("multiplecallback")] 50 | public void FirstOfMultipleCallback(WebServerEventArgs e) 51 | { 52 | Debug.WriteLine($"{nameof(FirstOfMultipleCallback)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}"); 53 | WebServer.OutPutStream(e.Context.Response, nameof(FirstOfMultipleCallback)); 54 | } 55 | 56 | [Route("multiplecallback")] 57 | public void SecondOfMultipleCallback(WebServerEventArgs e) 58 | { 59 | Debug.WriteLine($"{nameof(SecondOfMultipleCallback)} {e.Context.Request.HttpMethod} {e.Context.Request.RawUrl}"); 60 | WebServer.OutPutStream(e.Context.Response, nameof(SecondOfMultipleCallback)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/Template/.gitignore: -------------------------------------------------------------------------------- 1 | !WiFi.cs -------------------------------------------------------------------------------- /tests/WebServerE2ETests/Template/WiFi.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | namespace WebServerE2ETests 5 | { 6 | public partial class Program 7 | { 8 | private const string Ssid = "yourSSID"; 9 | private const string Password = "YourPassword"; 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/WebServerE2ETests.nfproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildExtensionsPath)\nanoFramework\v1.0\ 5 | 6 | 7 | 8 | Debug 9 | AnyCPU 10 | {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 11 | a0611ead-fb04-44e7-bad3-459dd0a7ff46 12 | Exe 13 | Properties 14 | 512 15 | WebServerE2ETests 16 | WebServerE2ETests 17 | v1.0 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ..\..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll 35 | 36 | 37 | ..\..\packages\nanoFramework.Runtime.Events.1.11.32\lib\nanoFramework.Runtime.Events.dll 38 | 39 | 40 | ..\..\packages\nanoFramework.System.Collections.1.5.67\lib\nanoFramework.System.Collections.dll 41 | 42 | 43 | ..\..\packages\nanoFramework.System.Text.1.3.42\lib\nanoFramework.System.Text.dll 44 | 45 | 46 | ..\..\packages\nanoFramework.System.Device.Wifi.1.5.133\lib\System.Device.Wifi.dll 47 | 48 | 49 | ..\..\packages\nanoFramework.System.IO.FileSystem.1.1.87\lib\System.IO.FileSystem.dll 50 | 51 | 52 | ..\..\packages\nanoFramework.System.IO.Streams.1.1.96\lib\System.IO.Streams.dll 53 | 54 | 55 | ..\..\packages\nanoFramework.System.Net.1.11.43\lib\System.Net.dll 56 | 57 | 58 | ..\..\packages\nanoFramework.System.Net.Http.Server.1.5.196\lib\System.Net.Http.dll 59 | 60 | 61 | ..\..\packages\nanoFramework.System.Threading.1.1.52\lib\System.Threading.dll 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/WebServerE2ETests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETnanoFramework,Version=v1.0": { 5 | "nanoFramework.CoreLibrary": { 6 | "type": "Direct", 7 | "requested": "[1.17.11, 1.17.11]", 8 | "resolved": "1.17.11", 9 | "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" 10 | }, 11 | "nanoFramework.Runtime.Events": { 12 | "type": "Direct", 13 | "requested": "[1.11.32, 1.11.32]", 14 | "resolved": "1.11.32", 15 | "contentHash": "NyLUIwJDlpl5VKSd+ljmdDtO2WHHBvPvruo1ccaL+hd79z+6XMYze1AccOVXKGiZenLBCwDmFHwpgIQyHkM7GA==" 16 | }, 17 | "nanoFramework.System.Collections": { 18 | "type": "Direct", 19 | "requested": "[1.5.67, 1.5.67]", 20 | "resolved": "1.5.67", 21 | "contentHash": "MjSipUB70vrxjqTm1KfKTUqqjd0wbweiNyYFXONi0XClrH6HXsuX2lhDqXM8NWuYnWyYOqx8y20sXbvsH+4brg==" 22 | }, 23 | "nanoFramework.System.Device.Wifi": { 24 | "type": "Direct", 25 | "requested": "[1.5.133, 1.5.133]", 26 | "resolved": "1.5.133", 27 | "contentHash": "0AyJ6I7C+UWz8A2c+ChfYl/tdAroVZDxl7cVstQ9kbN0Ts8MEwD368Uoe8+pOpcJmjamTmg5iUDD9SMrW1nCuw==" 28 | }, 29 | "nanoFramework.System.IO.FileSystem": { 30 | "type": "Direct", 31 | "requested": "[1.1.87, 1.1.87]", 32 | "resolved": "1.1.87", 33 | "contentHash": "Et2CmX2P9qtOUrMfQy9ijKZ31J/87aEb+iDGziD3Sys6KZRJKkFH3DHuYMLwzrWwZxiZ2McuN1ul0UzI02QKSg==" 34 | }, 35 | "nanoFramework.System.IO.Streams": { 36 | "type": "Direct", 37 | "requested": "[1.1.96, 1.1.96]", 38 | "resolved": "1.1.96", 39 | "contentHash": "kJSy4EJwChO4Vq3vGWP9gNRPFDnTsDU5HxzeI7NDO+RjbDsx7B8EhKymoeTPLJCxQq8y/0P1KG2XCxGpggW+fw==" 40 | }, 41 | "nanoFramework.System.Net": { 42 | "type": "Direct", 43 | "requested": "[1.11.43, 1.11.43]", 44 | "resolved": "1.11.43", 45 | "contentHash": "USwz59gxcNUzsiXfQohWSi8ANNwGDsp+qG4zBtHZU3rKMtvTsLI3rxdfMC77VehKqsCPn7aK3PU2oCRFo+1Rgg==" 46 | }, 47 | "nanoFramework.System.Net.Http.Server": { 48 | "type": "Direct", 49 | "requested": "[1.5.196, 1.5.196]", 50 | "resolved": "1.5.196", 51 | "contentHash": "cjr5Rj39duOjGcyvo/LMFdoeTeLg0zpFgFB7wJUXw0+65EiENEnJwqqR1CfbJEvBBpBMJdH/yLkK/8DU8Jk3XQ==" 52 | }, 53 | "nanoFramework.System.Text": { 54 | "type": "Direct", 55 | "requested": "[1.3.42, 1.3.42]", 56 | "resolved": "1.3.42", 57 | "contentHash": "68HPjhersNpssbmEMUHdMw3073MHfGTfrkbRk9eILKbNPFfPFck7m4y9BlAi6DaguUJaeKxgyIojXF3SQrF8/A==" 58 | }, 59 | "nanoFramework.System.Threading": { 60 | "type": "Direct", 61 | "requested": "[1.1.52, 1.1.52]", 62 | "resolved": "1.1.52", 63 | "contentHash": "kv+US/+7QKV1iT/snxBh032vwZ+3krJ4vujlSsvmS2nNj/nK64R3bq/ST3bCFquxHDD0mog8irtCBCsFazr4kA==" 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /tests/WebServerE2ETests/requests.http: -------------------------------------------------------------------------------- 1 | # This file is a collection of requests that can be executed with the REST Client extension for Visual Studio Code 2 | # https://marketplace.visualstudio.com/items?itemName=humao.rest-client 3 | # adjust your host here 4 | @host=192.168.1.86:80 5 | 6 | ### 7 | 8 | POST http://{{host}}/post?someparams=1&others=2 HTTP/1.1 9 | Content-Type: text/plain 10 | 11 | This is a test with post 12 | 13 | ### 14 | 15 | PUT http://{{host}}/put HTTP/1.1 16 | Content-Type: text/plain 17 | 18 | This is another test with put 19 | 20 | ### 21 | 22 | GET http://{{host}}/get?someparams=1&others=2 HTTP/1.1 23 | 24 | ### 25 | 26 | # This request will fail with 401 Unauthorized 27 | GET http://{{host}}/authbasic HTTP/1.1 28 | 29 | ### 30 | 31 | # this one will succeed 32 | GET http://topuser:topPassword@{{host}}/authbasic HTTP/1.1 33 | 34 | ### 35 | 36 | # This request will fail with 401 Unauthorized 37 | GET http://{{host}}/authapi HTTP/1.1 38 | 39 | ### 40 | 41 | # this one will succeed 42 | GET http://{{host}}/authapi HTTP/1.1 43 | ApiKey: superKey1234 44 | 45 | ### 46 | 47 | # This request will fail with 401 Unauthorized 48 | GET http://{{host}}/authdefaultapi HTTP/1.1 49 | 50 | ### 51 | 52 | # this one will succeed 53 | GET http://{{host}}/authdefaultapi HTTP/1.1 54 | ApiKey: ATopSecretAPIKey1234 55 | 56 | 57 | ### 58 | 59 | # this one will succeed with the public route 60 | GET http://{{host}}/authapikeybasicandpublic HTTP/1.1 61 | 62 | ### 63 | 64 | # this one will succeed with user 3 65 | GET http://user3:password@{{host}}/authapikeybasicandpublic HTTP/1.1 66 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/HttpMultipartParser/FormDataProvider.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using System.IO; 5 | using System.Text; 6 | using nanoFramework.Json; 7 | 8 | namespace nanoFramework.WebServer.Tests 9 | { 10 | internal static class FormDataProvider 11 | { 12 | public static Stream CreateFormWithParameters() 13 | { 14 | string content = @"------WebKitFormBoundarySZFRSm4A2LAZPpUu 15 | Content-Disposition: form-data; name=""param1"" 16 | 17 | value1 18 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu 19 | Content-Disposition: form-data; name=""param2"" 20 | 21 | value2 22 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 23 | 24 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 25 | } 26 | 27 | public static Stream CreateFormWithFile(Person person) 28 | { 29 | string content = $@"------WebKitFormBoundarySZFRSm4A2LAZPpUu 30 | Content-Disposition: form-data; name=""file""; filename=""somefile.json"" 31 | Content-Type: application/json 32 | 33 | {JsonConvert.SerializeObject(person)} 34 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 35 | 36 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 37 | } 38 | 39 | public static Stream CreateFormWithFiles(Person[] persons) 40 | { 41 | string content = $@"------WebKitFormBoundarySZFRSm4A2LAZPpUu 42 | Content-Disposition: form-data; name=""file""; filename=""first.json"" 43 | Content-Type: application/json 44 | 45 | {JsonConvert.SerializeObject(persons[0])} 46 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu 47 | Content-Disposition: form-data; name=""file""; filename=""second.json"" 48 | Content-Type: application/json 49 | 50 | {JsonConvert.SerializeObject(persons[1])} 51 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 52 | 53 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 54 | } 55 | 56 | public static Stream CreateFormWithEverything(Person[] persons) 57 | { 58 | string content = $@"------WebKitFormBoundarySZFRSm4A2LAZPpUu 59 | Content-Disposition: form-data; name=""param1"" 60 | 61 | value1 62 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu 63 | Content-Disposition: form-data; name=""param2"" 64 | 65 | value2 66 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu 67 | Content-Disposition: form-data; name=""file""; filename=""first.json"" 68 | Content-Type: application/json 69 | 70 | {JsonConvert.SerializeObject(persons[0])} 71 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu 72 | Content-Disposition: form-data; name=""file""; filename=""second.json"" 73 | Content-Type: application/json 74 | 75 | {JsonConvert.SerializeObject(persons[1])} 76 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 77 | 78 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 79 | } 80 | 81 | public static string CreateContent(int size) 82 | { 83 | StringBuilder sb = new(size); 84 | 85 | while (sb.Length < size) 86 | sb.Append("HMLTncevuycfsoiS7cAHhiJq8CI2pTnHhJJb3MfwRB9qlK0VryH8AuJAQzhguP1Z"); 87 | 88 | return sb.ToString(); 89 | } 90 | 91 | public static Stream CreateFormWithFile(string file) 92 | { 93 | string content = @$"------WebKitFormBoundarySZFRSm4A2LAZPpUu 94 | Content-Disposition: form-data; name=""file""; filename=""somefile.json"" 95 | Content-Type: application/json 96 | 97 | {file} 98 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 99 | 100 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 101 | } 102 | 103 | public static Stream CreateEmptyForm() 104 | { 105 | string content = @"------WebKitFormBoundarySZFRSm4A2LAZPpUu 106 | 107 | 108 | 109 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 110 | 111 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 112 | } 113 | 114 | public static Stream CreateInvalidForm() 115 | { 116 | // missing the name parameter should fail 117 | string content = @"------WebKitFormBoundarySZFRSm4A2LAZPpUu 118 | Content-Disposition: form-data; invalid=""blah"" 119 | 120 | value1 121 | ------WebKitFormBoundarySZFRSm4A2LAZPpUu--"; 122 | 123 | return new MemoryStream(Encoding.UTF8.GetBytes(content)) { Position = 0 }; 124 | } 125 | 126 | public static Person[] CreatePersons() 127 | { 128 | return new Person[] 129 | { 130 | new() 131 | { 132 | Name = "Chuck Norris", 133 | Age = 999 134 | }, 135 | new() 136 | { 137 | Name = "Darth Vader", 138 | Age = 9999 139 | } 140 | }; 141 | } 142 | } 143 | 144 | internal class Person 145 | { 146 | public string Name { get; set; } 147 | public int Age { get; set; } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/HttpMultipartParser/MultipartFormDataHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using nanoFramework.TestFramework; 5 | using nanoFramework.WebServer.HttpMultipartParser; 6 | using System.Collections; 7 | 8 | namespace nanoFramework.WebServer.Tests 9 | { 10 | [TestClass] 11 | public class MultipartFormDataHeaderTests 12 | { 13 | [TestMethod] 14 | public void FormPartHeaderTest() 15 | { 16 | Hashtable headers = new(); 17 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"paramname\"", headers); 18 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 19 | ValidateHeaders(headers, "name", "paramname"); 20 | 21 | headers.Clear(); 22 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"param;name\"", headers); 23 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 24 | ValidateHeaders(headers, "name", "param;name"); 25 | 26 | headers.Clear(); 27 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"param=name\"", headers); 28 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 29 | ValidateHeaders(headers, "name", "param=name"); 30 | 31 | headers.Clear(); 32 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"param:name\"", headers); 33 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 34 | ValidateHeaders(headers, "name", "param:name"); 35 | 36 | headers.Clear(); 37 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"param name\"", headers); 38 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 39 | ValidateHeaders(headers, "name", "param name"); 40 | } 41 | 42 | [TestMethod] 43 | public void FilePartHeaderTest() 44 | { 45 | Hashtable headers = new(); 46 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"file\"; filename=\"somefile.ext\"", headers); 47 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 48 | ValidateHeaders(headers, "name", "file"); 49 | ValidateHeaders(headers, "filename", "somefile.ext"); 50 | 51 | headers.Clear(); 52 | HeaderUtility.ParseHeaders("Content-Disposition: form-data; name=\"f i;l=e\"; filename=\";some=fi-le.ext :\"", headers); 53 | ValidateHeaders(headers, "Content-Disposition", "form-data"); 54 | ValidateHeaders(headers, "name", "f i;l=e"); 55 | ValidateHeaders(headers, "filename", ";some=fi-le.ext :"); 56 | } 57 | 58 | private void ValidateHeaders(Hashtable headers, string key, string value) 59 | { 60 | Assert.IsNotNull(headers); 61 | Assert.IsTrue(headers.Contains(key.ToLower())); 62 | Assert.AreSame(headers[key.ToLower()], value); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/HttpMultipartParser/MultipartFormDataParserTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | using nanoFramework.Json; 5 | using nanoFramework.TestFramework; 6 | using nanoFramework.WebServer.HttpMultipartParser; 7 | using System; 8 | using System.IO; 9 | 10 | namespace nanoFramework.WebServer.Tests 11 | { 12 | [TestClass] 13 | public class MultipartFormDataParserTests 14 | { 15 | [TestMethod] 16 | public void FormWithParametersTest() 17 | { 18 | Stream stream = FormDataProvider.CreateFormWithParameters(); 19 | 20 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream); 21 | 22 | Assert.IsNotNull(parser); 23 | 24 | ParameterPart[] parameters = parser.Parameters; 25 | Assert.IsTrue(parameters.Length == 2); 26 | Assert.IsTrue(parameters[0].Name == "param1" && parameters[0].Data == "value1"); 27 | Assert.IsTrue(parameters[1].Name == "param2" && parameters[1].Data == "value2"); 28 | } 29 | 30 | [TestMethod] 31 | public void FormWithFileTest() 32 | { 33 | Person[] persons = FormDataProvider.CreatePersons(); 34 | 35 | Stream stream = FormDataProvider.CreateFormWithFile(persons[0]); 36 | 37 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream); 38 | 39 | Assert.IsNotNull(parser); 40 | 41 | ParameterPart[] parameters = parser.Parameters; 42 | Assert.IsTrue(parameters.Length == 0); 43 | 44 | FilePart[] files = parser.Files; 45 | Assert.IsTrue(files.Length == 1); 46 | ValidateFile(files[0], "somefile.json", persons[0].Name, persons[0].Age); 47 | } 48 | 49 | [TestMethod] 50 | public void FormWithMultipleFilesTest() 51 | { 52 | Person[] persons = FormDataProvider.CreatePersons(); 53 | 54 | Stream stream = FormDataProvider.CreateFormWithFiles(persons); 55 | 56 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream); 57 | 58 | Assert.IsNotNull(parser); 59 | 60 | ParameterPart[] parameters = parser.Parameters; 61 | Assert.IsTrue(parameters.Length == 0); 62 | 63 | FilePart[] files = parser.Files; 64 | Assert.IsTrue(files.Length == 2); 65 | ValidateFile(files[0], "first.json", persons[0].Name, persons[0].Age); 66 | ValidateFile(files[1], "second.json", persons[1].Name, persons[1].Age); 67 | } 68 | 69 | [TestMethod] 70 | public void FormWithEverythingTest() 71 | { 72 | Person[] persons = FormDataProvider.CreatePersons(); 73 | 74 | Stream stream = FormDataProvider.CreateFormWithEverything(persons); 75 | 76 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream); 77 | 78 | Assert.IsNotNull(parser); 79 | 80 | ParameterPart[] parameters = parser.Parameters; 81 | Assert.IsTrue(parameters.Length == 2); 82 | Assert.IsTrue(parameters[0].Name == "param1" && parameters[0].Data == "value1"); 83 | Assert.IsTrue(parameters[1].Name == "param2" && parameters[1].Data == "value2"); 84 | 85 | FilePart[] files = parser.Files; 86 | Assert.IsTrue(files.Length == 2); 87 | ValidateFile(files[0], "first.json", persons[0].Name, persons[0].Age); 88 | ValidateFile(files[1], "second.json", persons[1].Name, persons[1].Age); 89 | } 90 | 91 | [TestMethod] 92 | public void FormWithLargeFileTest() 93 | { 94 | string fileIn = FormDataProvider.CreateContent(4096); 95 | Stream stream = FormDataProvider.CreateFormWithFile(fileIn); 96 | 97 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream); 98 | 99 | ParameterPart[] parameters = parser.Parameters; 100 | Assert.IsTrue(parameters.Length == 0); 101 | 102 | FilePart[] files = parser.Files; 103 | Assert.IsTrue(files.Length == 1); 104 | 105 | using var sr = new StreamReader(files[0].Data); 106 | string fileOut = sr.ReadToEnd(); 107 | 108 | Assert.AreEqual(fileIn, fileOut); 109 | } 110 | 111 | [TestMethod] 112 | public void EmptyFormTest() 113 | { 114 | Stream stream = FormDataProvider.CreateEmptyForm(); 115 | 116 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream, ignoreInvalidParts: true); 117 | Assert.IsNotNull(parser); 118 | 119 | Assert.ThrowsException(typeof(MultipartFormDataParserException), () => MultipartFormDataParser.Parse(stream)); 120 | } 121 | 122 | [TestMethod] 123 | public void InvalidFormTest() 124 | { 125 | Stream stream = FormDataProvider.CreateInvalidForm(); 126 | 127 | MultipartFormDataParser parser = MultipartFormDataParser.Parse(stream, ignoreInvalidParts: true); 128 | Assert.IsNotNull(parser); 129 | 130 | Assert.ThrowsException(typeof(MultipartFormDataParserException), () => MultipartFormDataParser.Parse(stream)); 131 | } 132 | 133 | private void ValidateFile(FilePart file, string filename, string personName, int personAge) 134 | { 135 | Assert.IsTrue(file.FileName == filename); 136 | StreamReader sr = new(file.Data); 137 | string content = sr.ReadToEnd(); 138 | 139 | var person = JsonConvert.DeserializeObject(content, typeof(Person)) as Person; 140 | Assert.IsNotNull(person); 141 | Assert.IsTrue(person.Name == personName && person.Age == personAge); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyCopyright("Copyright (c) 2021 nanoFramework contributors")] 12 | [assembly: AssemblyTrademark("")] 13 | [assembly: AssemblyCulture("")] 14 | 15 | // Setting ComVisible to false makes the types in this assembly not visible 16 | // to COM components. If you need to access a type in this assembly from 17 | // COM, set the ComVisible attribute to true on that type. 18 | [assembly: ComVisible(false)] 19 | 20 | // Version information for an assembly consists of the following four values: 21 | // 22 | // Major Version 23 | // Minor Version 24 | // Build Number 25 | // Revision 26 | // 27 | // You can specify all the values or you can default the Build and Revision Numbers 28 | // by using the '*' as shown below: 29 | // [assembly: AssemblyVersion("1.0.*")] 30 | [assembly: AssemblyVersion("1.0.0.0")] 31 | [assembly: AssemblyFileVersion("1.0.0.0")] 32 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/WebServerTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 Laurent Ellerbach and the project contributors 3 | // See LICENSE file in the project root for full license information. 4 | // 5 | 6 | using nanoFramework.TestFramework; 7 | using System; 8 | 9 | namespace nanoFramework.WebServer.Tests 10 | { 11 | [TestClass] 12 | public class WebServerTests 13 | { 14 | [TestMethod] 15 | public void IsRouteMatch_Should_ReturnFalseForNotMatchingMethod() 16 | { 17 | // Arrange 18 | var route = new CallbackRoutes() 19 | { 20 | Method = "GET", 21 | Route = "/api/test" 22 | }; 23 | 24 | // Act 25 | var result = WebServer.IsRouteMatch(route, "POST", "/api/test"); 26 | 27 | // Assert 28 | Assert.IsFalse(result); 29 | } 30 | 31 | [TestMethod] 32 | [DataRow("GET", "/api/test", "GET", "/api/test")] 33 | [DataRow("", "/api/test", "GET", "/api/test")] 34 | [DataRow("POST", "/api/test", "POST", "/api/test")] 35 | [DataRow("PUT", "/api/test", "PUT", "/api/test")] 36 | [DataRow("PATCH", "/api/test", "PATCH", "/api/test")] 37 | [DataRow("DELETE", "/api/test", "DELETE", "/api/test")] 38 | [DataRow("GET", "/API/TEST", "GET", "/api/test")] 39 | [DataRow("POST", "/API/TEST", "POST", "/api/test")] 40 | [DataRow("PUT", "/API/TEST", "PUT", "/api/test")] 41 | [DataRow("PATCH", "/API/TEST", "PATCH", "/api/test")] 42 | [DataRow("DELETE", "/API/TEST", "DELETE", "/api/test")] 43 | [DataRow("GET", "/api/test", "GET", "/API/TEST")] 44 | [DataRow("POST", "/api/test", "POST", "/api/test")] 45 | [DataRow("PUT", "/api/test", "PUT", "/API/TEST")] 46 | [DataRow("PATCH", "/api/test", "PATCH", "/API/TEST")] 47 | [DataRow("DELETE", "/api/test", "DELETE", "/API/TEST")] 48 | [DataRow("GET", "/api/test", "GET", "/api/test?id=1234")] 49 | [DataRow("GET", "/api/test", "GET", "/api/test?id=")] 50 | [DataRow("GET", "/api/test/resource/name", "GET", "/api/test/resource/name")] 51 | [DataRow("GET", "/api/test/resource/name", "GET", "/api/test/resource/name?id=1234")] 52 | [DataRow("GET", "/api/test/resource/name", "GET", "/api/test/resource/name?test=")] 53 | [DataRow("GET", "/api/test/resource/name", "GET", "/api/test/resource/name?")] 54 | [DataRow("GET", "/api/test/resource/name", "GET", "/api/test/resource/name?test=&id=123&app=something")] 55 | [DataRow("", "", "GET", "/")] 56 | public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRoute(string routeMethod, string routeUrl, string invokedMethod, string invokedUrl) 57 | { 58 | Console.WriteLine($"Params: routeMethod: {routeMethod} routeUrl: {routeUrl} invokedMethod: {invokedMethod} invokedUrl: {invokedUrl}"); 59 | // Arrange 60 | var route = new CallbackRoutes() 61 | { 62 | Method = routeMethod, 63 | Route = routeUrl, 64 | CaseSensitive = false 65 | }; 66 | 67 | // Act 68 | var result = WebServer.IsRouteMatch(route, invokedMethod, invokedUrl); 69 | 70 | // Assert 71 | Assert.IsTrue(result); 72 | } 73 | 74 | [TestMethod] 75 | public void IsRouteMatch_Should_ReturnTrueForMatchingMethodAndRouteCaseSensitive() 76 | { 77 | // Arrange 78 | var routeMethod = "POST"; 79 | var routeUrl = "/api/test"; 80 | var invokedMethod = "POST"; 81 | var invokedUrlMatch = "/api/test"; 82 | var invokedUrlNotMatch = "/API/TEST"; 83 | var route = new CallbackRoutes() 84 | { 85 | Method = routeMethod, 86 | Route = routeUrl, 87 | CaseSensitive = true 88 | }; 89 | 90 | // Act 91 | var resultMatch = WebServer.IsRouteMatch(route, invokedMethod, invokedUrlMatch); 92 | var resultNotMatch = WebServer.IsRouteMatch(route, invokedMethod, invokedUrlNotMatch); 93 | 94 | // Assert 95 | Assert.IsTrue(resultMatch); 96 | Assert.IsFalse(resultNotMatch); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/nano.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | .\TestResults 6 | 120000 7 | net48 8 | x64 9 | 10 | 11 | None 12 | False 13 | COM3 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/nanoFramework.WebServer.Tests.nfproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildExtensionsPath)\nanoFramework\v1.0\ 5 | 6 | 7 | 8 | 9 | 10 | 11 | Debug 12 | AnyCPU 13 | {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 2c2b4750-2a48-4d19-9404-178aab946482 15 | Library 16 | Properties 17 | 512 18 | nanoFramework.WebServer.Tests 19 | NFUnitTest 20 | False 21 | true 22 | UnitTest 23 | v1.0 24 | 25 | 26 | 27 | $(MSBuildProjectDirectory)\nano.runsettings 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ..\..\packages\nanoFramework.CoreLibrary.1.17.11\lib\mscorlib.dll 39 | 40 | 41 | ..\..\packages\nanoFramework.Json.2.2.199\lib\nanoFramework.Json.dll 42 | 43 | 44 | ..\..\packages\nanoFramework.Runtime.Events.1.11.32\lib\nanoFramework.Runtime.Events.dll 45 | 46 | 47 | ..\..\packages\nanoFramework.System.Collections.1.5.67\lib\nanoFramework.System.Collections.dll 48 | 49 | 50 | ..\..\packages\nanoFramework.System.Text.1.3.42\lib\nanoFramework.System.Text.dll 51 | 52 | 53 | ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.TestFramework.dll 54 | 55 | 56 | ..\..\packages\nanoFramework.TestFramework.3.0.77\lib\nanoFramework.UnitTestLauncher.exe 57 | 58 | 59 | ..\..\packages\nanoFramework.System.IO.Streams.1.1.96\lib\System.IO.Streams.dll 60 | 61 | 62 | ..\..\packages\nanoFramework.System.Net.1.11.43\lib\System.Net.dll 63 | 64 | 65 | ..\..\packages\nanoFramework.System.Net.Http.Server.1.5.196\lib\System.Net.Http.dll 66 | 67 | 68 | ..\..\packages\nanoFramework.System.Threading.1.1.52\lib\System.Threading.dll 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/nanoFramework.WebServer.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETnanoFramework,Version=v1.0": { 5 | "nanoFramework.CoreLibrary": { 6 | "type": "Direct", 7 | "requested": "[1.17.11, 1.17.11]", 8 | "resolved": "1.17.11", 9 | "contentHash": "HezzAc0o2XrSGf85xSeD/6xsO6ohF9hX6/iMQ1IZS6Zw6umr4WfAN2Jv0BrPxkaYwzEegJxxZujkHoUIAqtOMw==" 10 | }, 11 | "nanoFramework.Json": { 12 | "type": "Direct", 13 | "requested": "[2.2.199, 2.2.199]", 14 | "resolved": "2.2.199", 15 | "contentHash": "XBNKcI5hiUpn19NxhSYM4cxH0FXeefrohGD4tFrTlwhZw3hL1ie5UQJ0dPsaUBb/YkypkJZzQoxEvnwOj8DI5w==" 16 | }, 17 | "nanoFramework.Runtime.Events": { 18 | "type": "Direct", 19 | "requested": "[1.11.32, 1.11.32]", 20 | "resolved": "1.11.32", 21 | "contentHash": "NyLUIwJDlpl5VKSd+ljmdDtO2WHHBvPvruo1ccaL+hd79z+6XMYze1AccOVXKGiZenLBCwDmFHwpgIQyHkM7GA==" 22 | }, 23 | "nanoFramework.System.Collections": { 24 | "type": "Direct", 25 | "requested": "[1.5.67, 1.5.67]", 26 | "resolved": "1.5.67", 27 | "contentHash": "MjSipUB70vrxjqTm1KfKTUqqjd0wbweiNyYFXONi0XClrH6HXsuX2lhDqXM8NWuYnWyYOqx8y20sXbvsH+4brg==" 28 | }, 29 | "nanoFramework.System.IO.Streams": { 30 | "type": "Direct", 31 | "requested": "[1.1.96, 1.1.96]", 32 | "resolved": "1.1.96", 33 | "contentHash": "kJSy4EJwChO4Vq3vGWP9gNRPFDnTsDU5HxzeI7NDO+RjbDsx7B8EhKymoeTPLJCxQq8y/0P1KG2XCxGpggW+fw==" 34 | }, 35 | "nanoFramework.System.Net": { 36 | "type": "Direct", 37 | "requested": "[1.11.43, 1.11.43]", 38 | "resolved": "1.11.43", 39 | "contentHash": "USwz59gxcNUzsiXfQohWSi8ANNwGDsp+qG4zBtHZU3rKMtvTsLI3rxdfMC77VehKqsCPn7aK3PU2oCRFo+1Rgg==" 40 | }, 41 | "nanoFramework.System.Net.Http.Server": { 42 | "type": "Direct", 43 | "requested": "[1.5.196, 1.5.196]", 44 | "resolved": "1.5.196", 45 | "contentHash": "cjr5Rj39duOjGcyvo/LMFdoeTeLg0zpFgFB7wJUXw0+65EiENEnJwqqR1CfbJEvBBpBMJdH/yLkK/8DU8Jk3XQ==" 46 | }, 47 | "nanoFramework.System.Text": { 48 | "type": "Direct", 49 | "requested": "[1.3.42, 1.3.42]", 50 | "resolved": "1.3.42", 51 | "contentHash": "68HPjhersNpssbmEMUHdMw3073MHfGTfrkbRk9eILKbNPFfPFck7m4y9BlAi6DaguUJaeKxgyIojXF3SQrF8/A==" 52 | }, 53 | "nanoFramework.System.Threading": { 54 | "type": "Direct", 55 | "requested": "[1.1.52, 1.1.52]", 56 | "resolved": "1.1.52", 57 | "contentHash": "kv+US/+7QKV1iT/snxBh032vwZ+3krJ4vujlSsvmS2nNj/nK64R3bq/ST3bCFquxHDD0mog8irtCBCsFazr4kA==" 58 | }, 59 | "nanoFramework.TestFramework": { 60 | "type": "Direct", 61 | "requested": "[3.0.77, 3.0.77]", 62 | "resolved": "3.0.77", 63 | "contentHash": "Py5W1oN84KMBmOOHCzdz6pyi3bZTnQu9BoqIx0KGqkhG3V8kGoem/t+BuCM0pMIWAyl2iMP1n2S9624YXmBJZw==" 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.2", 4 | "assemblyVersion": { 5 | "precision": "minor" 6 | }, 7 | "semVer1NumericIdentifierPadding": 3, 8 | "nuGetPackageVersion": { 9 | "semVer": 2.0 10 | }, 11 | "publicReleaseRefSpec": [ 12 | "^refs/heads/develop$", 13 | "^refs/heads/main$", 14 | "^refs/heads/v\\d+(?:\\.\\d+)?$" 15 | ], 16 | "cloudBuild": { 17 | "setAllVariables": true 18 | }, 19 | "release": { 20 | "branchName": "release-v{version}", 21 | "versionIncrement": "build", 22 | "firstUnstableTag": "preview" 23 | } 24 | } --------------------------------------------------------------------------------