├── .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 | [](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [](https://www.nuget.org/packages/nanoFramework.WebServer/) [](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [](https://discord.gg/gCyBu8T)
2 |
3 | 
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 | [](https://dev.azure.com/nanoframework/nanoFramework.WebServer/_build/latest?definitionId=65&repoName=nanoframework%2FnanoFramework.WebServer&branchName=main) | [](https://www.nuget.org/packages/nanoFramework.WebServer/) |
14 | | nanoFramework.WebServer.FileSystem | [](https://dev.azure.com/nanoframework/nanoFramework.WebServer/_build/latest?definitionId=65&repoName=nanoframework%2FnanoFramework.WebServer&branchName=main) | [](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 | 
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 | }
--------------------------------------------------------------------------------