diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000000..76ed32d5e2
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,27 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "altcover.global": {
+ "version": "9.0.102",
+ "commands": [
+ "altcover"
+ ],
+ "rollForward": false
+ },
+ "coveralls.net": {
+ "version": "4.0.1",
+ "commands": [
+ "csmacnz.Coveralls"
+ ],
+ "rollForward": false
+ },
+ "nunit.consolerunner.netcore": {
+ "version": "3.22.0",
+ "commands": [
+ "nunit"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..7481ade57b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,201 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+indent_style = tab
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = true
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:suggestion
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:suggestion
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = false
+csharp_new_line_before_else = false
+csharp_new_line_before_finally = false
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = methods,types
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = false
+
+# Space preferences
+csharp_space_after_cast = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = true
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..c1e6522536
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: jstedfast
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000..3ab689b096
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,61 @@
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**Platform (please complete the following information):**
+ - OS: [e.g. Windows, Linux, MacOS, iOS, Android, Windows Phone, etc.]
+ - .NET Runtime: [e.g. CoreCLR, Mono]
+ - .NET Framework: [e.g. .Net Core, .NET 4.5, UWP, etc.]
+ - MailKit Version:
+
+**Exception**
+If you got an exception, please include the exception Message *and* StackTrace.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Code Snippets**
+If applicable, add code snippets to help explain your problem.
+
+```csharp
+// Add your code snippet here.
+```
+
+**Protocol Logs**
+Please include a protocol log (scrubbed of any authentication data), especially
+if you got an exception such as `Syntax error in XYZ. Unexpected token: ...`.
+
+To get a protocol log, follow one of the following code snippets:
+
+```csharp
+// log to a file called 'imap.log'
+var client = new ImapClient (new ProtocolLogger ("imap.log"));
+```
+
+```csharp
+// log to a file called 'pop3.log'
+var client = new Pop3Client (new ProtocolLogger ("pop3.log"));
+```
+
+```csharp
+// log to a file called 'smtp.log'
+var client = new SmtpClient (new ProtocolLogger ("smtp.log"));
+```
+
+Note: if the protocol log contains sensitive information, feel free to email it to me at
+jestedfa@microsoft.com instead of including it in the GitHub issue.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000..066b2d920a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..2916857246
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "nuget"
+ directory: "/MailKit"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "08:00"
+ timezone: "America/New_York"
diff --git a/.github/issue_template.md b/.github/issue_template.md
deleted file mode 100644
index 1ee38ad36a..0000000000
--- a/.github/issue_template.md
+++ /dev/null
@@ -1,31 +0,0 @@
-To help me debug your issue, please explain:
-- What were you trying to do?
-- What happened?
-- What did you expect to happen?
-- Step-by-step reproduction instructions and/or a simple test case.
-
-If you got an exception, please include the exception Message *and* StackTrace.
-
-If you got an exception such as `Syntax error in XYZ. Unexpected token: ...`,
-INCLUDE THE PROTOCOL LOG (scrubbed of any authentication data). If you do not include
-the protocol log, you will make me VERY UNHAPPY.
-
-To get a protocol log, follow one of the following code snippets:
-
-```csharp
-// log to a file called 'imap.log'
-var client = new ImapClient (new ProtocolLogger ("imap.log"));
-```
-
-```csharp
-// log to a file called 'pop3.log'
-var client = new Pop3Client (new ProtocolLogger ("pop3.log"));
-```
-
-```csharp
-// log to a file called 'smtp.log'
-var client = new SmtpClient (new ProtocolLogger ("smtp.log"));
-```
-
-Note: if the protocol log contains sensitive information, feel free to email it to me at
-jestedfa@microsoft.com instead of including it in the GitHub issue.
diff --git a/.github/workflows/aot-compatibility.yml b/.github/workflows/aot-compatibility.yml
new file mode 100644
index 0000000000..b77574f269
--- /dev/null
+++ b/.github/workflows/aot-compatibility.yml
@@ -0,0 +1,38 @@
+name: AOT Compatibility
+
+on:
+ push:
+ branches: [ 'master' ]
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches: [ 'master' ]
+ paths-ignore:
+ - '**.md'
+
+jobs:
+ aot-test:
+ strategy:
+ fail-fast: false # ensures the entire test matrix is run, even if one permutation fails
+ matrix:
+ os: [ windows-latest ]
+ mailkitlite: [ true ]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Setup/Install the .NET SDKs
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
+
+ - name: Checkout repository
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0 # fetching all
+ submodules: true
+
+ - name: Publish AOT testApp, assert static analysis warning count, and run the app
+ shell: pwsh
+ run: .\scripts\test-aot-compatibility.ps1 ${{ matrix.mailkitlite }}
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000000..2802c824ea
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,67 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ schedule:
+ - cron: '38 12 * * 1'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: 'ubuntu-latest'
+ timeout-minutes: 360
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'csharp' ]
+
+ steps:
+ - name: Setup/Install the .NET SDKs
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
+
+ - name: Checkout repository
+ uses: actions/checkout@v6
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ submodules: recursive
+ fetch-depth: 0
+
+ - name: Run .NET restore
+ shell: pwsh
+ run: |
+ dotnet restore MailKit.sln
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ queries: security-extended,security-and-quality
+
+ - name: Build
+ continue-on-error: false
+ shell: pwsh
+ run: |
+ dotnet msbuild MailKit.sln -property:Platform="Any CPU" -property:Configuration=Release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000000..cf27696394
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,155 @@
+name: Build
+
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ ci:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ windows-latest, ubuntu-latest ]
+ build-configuration: [ Debug, Release ]
+ outputs:
+ latest-version: ${{ steps.semantic_version.outputs.version_num }}
+ environment: ci
+ env:
+ SOLUTION_PATH: MailKit.sln
+ BUILD_PLATFORM: Any CPU
+ BUILD_CONFIGURATION: ${{ matrix.build-configuration }}
+ GENERATE_CODE_COVERAGE: ${{ matrix.os == 'windows-latest' && matrix.build-configuration == 'Debug' }}
+ MONO_RUNTIME: ${{ matrix.os != 'windows-latest' }}
+ PUBLISH: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && matrix.os == 'windows-latest' && matrix.build-configuration == 'Release' }}
+
+ steps:
+ - name: Setup/Install the .NET SDKs
+ id: install-netsdk
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
+
+ - if: runner.os == 'Windows'
+ name: Setup MSBuild
+ id: setup_msbuild
+ uses: microsoft/setup-msbuild@v3
+
+ - name: Checkout repository
+ id: checkout_repo
+ uses: actions/checkout@v6
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ submodules: recursive
+ fetch-depth: 0
+
+ - name: Get semantic version from csproj
+ id: semantic_version
+ shell: pwsh
+ run: |
+ $xml = [xml](gc MailKit/MailKit.csproj)
+ $SEMANTIC_VERSION_NUMBER = $xml.Project.PropertyGroup.VersionPrefix
+ $VERSION_NUM = $SEMANTIC_VERSION_NUMBER[0].Trim()
+ Write-Host "version_num=${VERSION_NUM}"
+ [IO.File]::AppendAllText($env:GITHUB_OUTPUT, "version_num=${VERSION_NUM}$([Environment]::NewLine)")
+
+ - if: ${{ env.PUBLISH == 'true' }}
+ name: Get latest tag
+ id: get_latest_tag
+ shell: pwsh
+ run: |
+ $LATEST_TAG = git -c 'versionsort.suffix=-' ls-remote --exit-code --refs --sort='version:refname' --tags "https://github.com/$env:GIT_URL.git" '*.*.*' | tail --lines=1 | cut --delimiter='/' --fields=3
+ Write-Host "tag=$LATEST_TAG"
+ [IO.File]::AppendAllText($env:GITHUB_OUTPUT, "tag=${LATEST_TAG}$([Environment]::NewLine)")
+ env:
+ GIT_URL: ${{ github.repository }}
+
+ - if: ${{ env.PUBLISH == 'true' && steps.semantic_version.outputs.version_num != steps.get_latest_tag.outputs.tag }}
+ name: Add new tag to repo
+ id: add_new_tag_to_repo
+ continue-on-error: true
+ shell: pwsh
+ run: |
+ git config --global user.name $env:GIT_USER_NAME
+ git config --global user.email $env:GIT_USER_EMAIL
+ git tag -a -m "Tagged for $env:NEW_VERSION_NUM" $env:NEW_VERSION_NUM
+ git push --follow-tags
+ env:
+ GIT_USER_NAME: ${{ github.event.head_commit.author.username }}
+ GIT_USER_EMAIL: ${{ github.event.head_commit.author.email }}
+ NEW_VERSION_NUM: ${{ steps.semantic_version.outputs.version_num }}
+
+ - name: Run .NET restore
+ shell: pwsh
+ run: |
+ dotnet restore $env:SOLUTION_PATH
+
+ - name: Run .NET tool restore
+ shell: pwsh
+ run: |
+ dotnet tool restore
+
+ - name: Build solution
+ id: build_solution
+ continue-on-error: true
+ shell: pwsh
+ run: |
+ dotnet msbuild $env:SOLUTION_PATH -property:Platform=$env:BUILD_PLATFORM -property:Configuration=$env:BUILD_CONFIGURATION -property:MonoRuntime=$env:MONO_RUNTIME
+
+ - name: Run unit tests
+ id: run_unit_tests
+ continue-on-error: true
+ shell: pwsh
+ run: |
+ & ./scripts/test.ps1 -Configuration:$env:BUILD_CONFIGURATION -GenerateCodeCoverage:$env:GENERATE_CODE_COVERAGE
+
+ - name: Upload unit test results
+ id: upload_test_results
+ continue-on-error: true
+ uses: actions/upload-artifact@v7
+ with:
+ name: MailKit.${{ steps.semantic_version.outputs.version_num }}.${{ github.run_number }}-${{ matrix.os }}-${{ matrix.build-configuration }}-TestResults.xml
+ path: TestResult.xml
+
+ - if: ${{ env.GENERATE_CODE_COVERAGE == 'true' }}
+ name: Upload code coverage data to coveralls.io
+ id: upload_to_coveralls
+ shell: pwsh
+ run: |
+ & ./scripts/coveralls.ps1
+ env:
+ COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
+ GIT_COMMIT_SHA: ${{ github.sha }}
+ GIT_REF: ${{ github.ref }}
+ GIT_ACTOR: ${{ github.event.head_commit.author.username }}
+ GIT_ACTOR_EMAIL: ${{ github.event.head_commit.author.email }}
+ GIT_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
+ COVERALLS_JOB_ID: ${{ steps.semantic_version.outputs.version_num }}.${{ github.run_number }}
+
+ - if: ${{ env.PUBLISH == 'true' }}
+ name: Create NuGet package
+ id: create_nuget_package
+ shell: pwsh
+ run: |
+ nuget pack nuget/MailKit.nuspec -Version "$env:LATEST_VERSION.$env:GITHUB_RUN_NUMBER"
+ env:
+ LATEST_VERSION: ${{ steps.semantic_version.outputs.version_num }}
+
+ - if: ${{ env.PUBLISH == 'true' }}
+ name: Push NuGet package to MyGet
+ id: push_nuget_package
+ shell: pwsh
+ run: |
+ nuget push $env:NUGET_PKG_PATH -ApiKey $env:MYGET_API_KEY -Source https://www.myget.org/F/mimekit/api/v3/index.json
+ env:
+ NUGET_PKG_PATH: MailKit.${{ steps.semantic_version.outputs.version_num }}.${{ github.run_number }}.nupkg
+ MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }}
+
+ - if: ${{ env.PUBLISH == 'true' }}
+ name: Upload NuGet package as artifact
+ id: upload_nuget_package
+ uses: actions/upload-artifact@v7
+ with:
+ name: MailKit.${{ steps.semantic_version.outputs.version_num }}.${{ github.run_number }}.nupkg
+ path: MailKit.${{ steps.semantic_version.outputs.version_num }}.${{ github.run_number }}.nupkg
+
+# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
diff --git a/.gitignore b/.gitignore
index 0ec45ca90e..31d0a2de5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+MailKit/Resources/Resource.designer.cs
+*.csproj.nuget.dgspec.json
+*.csproj.nuget.cache
*project.lock.json
*.userprefs
*.user
@@ -8,5 +11,8 @@ packages
obj
bin
.vs
+*.patch
*.tree
*.zip
+.idea
+.DS_Store
diff --git a/.nuget/packages.config b/.nuget/packages.config
deleted file mode 100644
index eb80f3b1ce..0000000000
--- a/.nuget/packages.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
+ System.Text.Encoding.RegisterProvider (System.Text.CodePagesEncodingProvider.Instance);
+
+
+ System.Text.Encoding.RegisterProvider (System.Text.CodePagesEncodingProvider.Instance);
+
+
+ <configuration>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+ </configuration>
+
+
+
+
+
+ using (var client = new SmtpClient ()) {
+ client.ServerCertificateValidationCallback = (s,c,h,e) => true;
+
+ client.Connect (hostName, port, SecureSocketOptions.Auto);
+
+ // ...
+ }
+
+
+ bool MyServerCertificateValidationCallback (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ if (sslPolicyErrors == SslPolicyErrors.None)
+ return true;
+
+ // Note: The following code casts to an X509Certificate2 because it's easier to get the
+ // values for comparison, but it's possible to get them from an X509Certificate as well.
+ if (certificate is X509Certificate2 certificate2) {
+ var cn = certificate2.GetNameInfo (X509NameType.SimpleName, false);
+ var fingerprint = certificate2.Thumbprint;
+ var serial = certificate2.SerialNumber;
+ var issuer = certificate2.Issuer;
+
+ return cn == "imap.gmail.com" && issuer == "CN=GTS CA 1O1, O=Google Trust Services, C=US" &&
+ serial == "00BABE95B167C9ECAF08000000006065B6" &&
+ fingerprint == "E79A011EF55EEC72D2B7E391D193761372796836";
+ }
+
+ return false;
+ }
+
+
+ using (var client = new SmtpClient ()) {
+ client.CheckCertificateRevocation = false;
+
+ client.Connect (hostName, port, SecureSocketOptions.Auto);
+
+ // ...
+ }
+
+
+ using (var client = new SmtpClient ()) {
+ // Allow SSLv3.0 and all versions of TLS
+ client.SslProtocols = SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13;
+
+ client.Connect ("smtp.gmail.com", 465, true);
+
+ // ...
+ }
+
+
+
+
+
using (var client = new ImapClient ()) {
client.Connect ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
-
- // disable OAuth2 authentication unless you are actually using an access_token
- client.AuthenticationMechanisms.Remove ("XOAUTH2");
-
- client.Authenticate ("user@gmail.com", "password");
+ client.Authenticate ("user@gmail.com", "app-specific-password");
// do stuff...
@@ -153,33 +471,68 @@ using (var client = new ImapClient ()) {
-var certificate = new X509Certificate2 (@"C:\path\to\certificate.p12", "password", X509KeyStorageFlags.Exportable);
-var credential = new ServiceAccountCredential (new ServiceAccountCredential.Initializer ("your-developer-id@developer.gserviceaccount.com") {
- // Note: other scopes can be found here: https://developers.google.com/gmail/api/auth/scopes
- Scopes = new[] { "https://mail.google.com/" },
- User = "user@gmail.com"
-}.FromCertificate (certificate));
-
-// Note: result will be true if the access token was received successfully
-bool result = await credential.RequestAccessTokenAsync (cancel.Token);
-
-// use the access token as the password string
-client.Authenticate ("user@gmail.com", credential.Token.AccessToken);
+const string GMailAccount = "username@gmail.com";
+
+var clientSecrets = new ClientSecrets {
+ ClientId = "XXX.apps.googleusercontent.com",
+ ClientSecret = "XXX"
+};
+
+var codeFlow = new GoogleAuthorizationCodeFlow (new GoogleAuthorizationCodeFlow.Initializer {
+ // Cache tokens in ~/.local/share/google-filedatastore/CredentialCacheFolder on Linux/Mac
+ DataStore = new FileDataStore ("CredentialCacheFolder", false),
+ Scopes = new [] { "https://mail.google.com/" },
+ ClientSecrets = clientSecrets
+});
+
+var codeReceiver = new LocalServerCodeReceiver ();
+var authCode = new AuthorizationCodeInstalledApp (codeFlow, codeReceiver);
+var credential = await authCode.AuthorizeAsync (GMailAccount, CancellationToken.None);
+
+if (authCode.ShouldRequestAuthorizationCode (credential.Token))
+ await credential.RefreshTokenAsync (CancellationToken.None);
+
+var oauth2 = new SaslMechanismOAuth2 (credential.UserId, credential.Token.AccessToken);
+
+using (var client = new ImapClient ()) {
+ await client.ConnectAsync ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
+ await client.AuthenticateAsync (oauth2);
+ await client.DisconnectAsync (true);
+}
multipart/mixed
multipart/alternative
@@ -298,7 +651,7 @@ multipart/mixed
foreach (var mailbox in message.To.Mailboxes)
@@ -504,7 +857,7 @@ foreach (var mailbox in message.To.Mailboxes)
-
@@ -172,8 +186,12 @@
+
+
-
+
+
+
@@ -112,7 +112,7 @@
urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
{
- if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
+ if (ctx.TagId == HtmlTagId.Meta && !ctx.IsEndTag) {
+ bool isContentType = false;
+
ctx.WriteTag (htmlWriter, false);
- // replace the src attribute with a file:// URL
+ // replace charsets with "utf-8" since our output will be in utf-8 (and not whatever the original charset was)
foreach (var attribute in ctx.Attributes) {
- if (attribute.Id == HtmlAttributeId.Src) {
- MimePart image;
- string url;
+ if (attribute.Id == HtmlAttributeId.Charset) {
+ htmlWriter.WriteAttributeName (attribute.Name);
+ htmlWriter.WriteAttributeValue ("utf-8");
+ } else if (isContentType && attribute.Id == HtmlAttributeId.Content) {
+ htmlWriter.WriteAttributeName (attribute.Name);
+ htmlWriter.WriteAttributeValue ("text/html; charset=utf-8");
+ } else {
+ if (attribute.Id == HtmlAttributeId.HttpEquiv && attribute.Value != null
+ && attribute.Value.Equals ("Content-Type", StringComparison.OrdinalIgnoreCase))
+ isContentType = true;
+
+ htmlWriter.WriteAttribute (attribute);
+ }
+ }
+ } else if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
+ ctx.WriteTag (htmlWriter, false);
- if (!TryGetImage (attribute.Value, out image)) {
+ // replace the src attribute with a "data:" URL
+ foreach (var attribute in ctx.Attributes) {
+ if (attribute.Id == HtmlAttributeId.Src) {
+ if (!TryGetImage (attribute.Value, out var image)) {
htmlWriter.WriteAttribute (attribute);
continue;
}
- url = SaveImage (image, attribute.Value);
+ var dataUri = GetDataUri (image);
htmlWriter.WriteAttributeName (attribute.Name);
- htmlWriter.WriteAttributeValue (url);
+ htmlWriter.WriteAttributeValue (dataUri);
} else {
htmlWriter.WriteAttribute (attribute);
}
}
- } else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag) {
- ctx.WriteTag (htmlWriter, false);
-
- // add and/or replace oncontextmenu="return false;"
- foreach (var attribute in ctx.Attributes) {
- if (attribute.Name.ToLowerInvariant () == "oncontextmenu")
- continue;
-
- htmlWriter.WriteAttribute (attribute);
- }
-
- htmlWriter.WriteAttribute ("oncontextmenu", "return false;");
} else {
// pass the tag through to the output
ctx.WriteTag (htmlWriter, true);
@@ -206,7 +212,7 @@ protected override void VisitTextPart (TextPart entity)
string delsp;
if (entity.ContentType.Parameters.TryGetValue ("delsp", out delsp))
- flowed.DeleteSpace = delsp.ToLowerInvariant () == "yes";
+ flowed.DeleteSpace = delsp.Equals ("yes", StringComparison.OrdinalIgnoreCase);
converter = flowed;
} else {
diff --git a/Documentation/Examples/MultipartFormDataExample.cs b/Documentation/Examples/MultipartFormDataExample.cs
deleted file mode 100644
index 722f490e73..0000000000
--- a/Documentation/Examples/MultipartFormDataExample.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Net;
-
-using MimeKit;
-
-namespace Examples {
- class MultipartFormDataExample
- {
- #region ParseMultipartFormDataSimple
- MimeEntity ParseMultipartFormData (HttpWebResponse response)
- {
- var contentType = ContentType.Parse (response.ContentType);
-
- return MimeEntity.Parse (contentType, response.GetResponseStream ());
- }
- #endregion
-
- #region ParseMultipartFormDataComplex
- MimeEntity ParseMultipartFormData (HttpWebResponse response)
- {
- // create a temporary file to store our large HTTP data stream
- var tmp = Path.GetTempFileName ();
-
- using (var stream = File.Open (tmp, FileMode.Open, FileAccess.ReadWrite)) {
- // create a header for the multipart/form-data MIME entity based on the Content-Type value of the HTTP
- // response
- var header = Encoding.UTF8.GetBytes (string.Format ("Content-Type: {0}\r\n\r\n", response.ContentType));
-
- // write the header to the stream
- stream.Write (header, 0, header.Length);
-
- // copy the content of the HTTP response to our temporary stream
- response.GetResponseStream ().CopyTo (stream);
-
- // reset the stream back to the beginning
- stream.Position = 0;
-
- // parse the MIME entity with persistent = true, telling the parser not to load the content into memory
- return MimeEntity.Load (stream, persistent: true);
- }
- }
- #endregion
- }
-}
diff --git a/Documentation/Examples/MultipartFormDataExamples.cs b/Documentation/Examples/MultipartFormDataExamples.cs
index 722f490e73..157af96124 100644
--- a/Documentation/Examples/MultipartFormDataExamples.cs
+++ b/Documentation/Examples/MultipartFormDataExamples.cs
@@ -11,7 +11,7 @@ MimeEntity ParseMultipartFormData (HttpWebResponse response)
{
var contentType = ContentType.Parse (response.ContentType);
- return MimeEntity.Parse (contentType, response.GetResponseStream ());
+ return MimeEntity.Load (contentType, response.GetResponseStream ());
}
#endregion
diff --git a/Documentation/Examples/OAuth2ExchangeExample.cs b/Documentation/Examples/OAuth2ExchangeExample.cs
new file mode 100644
index 0000000000..8e3be0b6c4
--- /dev/null
+++ b/Documentation/Examples/OAuth2ExchangeExample.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using MailKit;
+using MailKit.Net.Imap;
+using MailKit.Security;
+
+using Microsoft.Identity.Client;
+
+namespace OAuth2ExchangeExample {
+ class Program
+ {
+ const string ExchangeAccount = "username@office365.com";
+
+ public static void Main (string[] args)
+ {
+ using (var client = new ImapClient ()) {
+ client.Connect ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
+ if (client.AuthenticationMechanisms.Contains ("OAUTHBEARER") || client.AuthenticationMechanisms.Contains ("XOAUTH2"))
+ AuthenticateAsync (client).GetAwaiter ().GetResult ();
+ client.Disconnect (true);
+ }
+ }
+
+ static async Task AuthenticateAsync (ImapClient client)
+ {
+ var options = new PublicClientApplicationOptions {
+ ClientId = "Application (client) ID",
+ TenantId = "Directory (tenant) ID",
+ RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
+ };
+
+ var publicClientApplication = PublicClientApplicationBuilder
+ .CreateWithApplicationOptions (options)
+ .Build ();
+
+ var scopes = new string[] {
+ "email",
+ "offline_access",
+ "https://outlook.office.com/IMAP.AccessAsUser.All", // Only needed for IMAP
+ //"https://outlook.office.com/POP.AccessAsUser.All", // Only needed for POP
+ //"https://outlook.office.com/SMTP.AccessAsUser.All", // Only needed for SMTP
+ };
+
+ AuthenticationResult? result;
+
+ try {
+ // First, check the cache for an auth token.
+ result = await publicClientApplication.AcquireTokenSilent (scopes, username).ExecuteAsync ();
+ } catch (MsalUiRequiredException) {
+ // If that fails, then try getting an auth token interactively.
+ result = await publicClientApplication.AcquireTokenInteractive (scopes).WithLoginHint (username).ExecuteAsync ();
+ }
+
+ // Note: We use result.Account.Username here instead of ExchangeAccount because the user *may* have chosen a
+ // different Microsoft Exchange account when presented with the browser window during the authentication process.
+ SaslMechanism oauth2;
+
+ if (client.AuthenticationMechanisms.Contains ("OAUTHBEARER"))
+ oauth2 = new SaslMechanismOAuthBearer (result.Account.Username, result.AccessToken);
+ else
+ oauth2 = new SaslMechanismOAuth2 (result.Account.Username, result.AccessToken);
+
+ await client.AuthenticateAsync (oauth2);
+ }
+ }
+}
diff --git a/Documentation/Examples/OAuth2GMailExample.cs b/Documentation/Examples/OAuth2GMailExample.cs
new file mode 100644
index 0000000000..95ad1dc03a
--- /dev/null
+++ b/Documentation/Examples/OAuth2GMailExample.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Google.Apis.Util;
+using Google.Apis.Util.Store;
+using Google.Apis.Auth.OAuth2;
+using Google.Apis.Auth.OAuth2.Flows;
+
+using MailKit;
+using MailKit.Net.Imap;
+using MailKit.Security;
+
+namespace OAuth2GMailExample {
+ class Program
+ {
+ const string GMailAccount = "username@gmail.com";
+
+ public static void Main (string[] args)
+ {
+ using (var client = new ImapClient ()) {
+ client.Connect ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
+ if (client.AuthenticationMechanisms.Contains ("OAUTHBEARER") || client.AuthenticationMechanisms.Contains ("XOAUTH2"))
+ AuthenticateAsync (client).GetAwaiter ().GetResult ();
+ client.Disconnect (true);
+ }
+ }
+
+ static async Task AuthenticateAsync (ImapClient client)
+ {
+ var clientSecrets = new ClientSecrets {
+ ClientId = "XXX.apps.googleusercontent.com",
+ ClientSecret = "XXX"
+ };
+
+ var codeFlow = new GoogleAuthorizationCodeFlow (new GoogleAuthorizationCodeFlow.Initializer {
+ DataStore = new FileDataStore ("CredentialCacheFolder", false),
+ Scopes = new [] { "https://mail.google.com/" },
+ ClientSecrets = clientSecrets
+ });
+
+ // Note: For a web app, you'll want to use AuthorizationCodeWebApp instead.
+ var codeReceiver = new LocalServerCodeReceiver ();
+ var authCode = new AuthorizationCodeInstalledApp (codeFlow, codeReceiver);
+
+ var credential = await authCode.AuthorizeAsync (GMailAccount, CancellationToken.None);
+
+ if (credential.Token.IsStale)
+ await credential.RefreshTokenAsync (CancellationToken.None);
+
+ // Note: We use credential.UserId here instead of GMailAccount because the user *may* have chosen a
+ // different GMail account when presented with the browser window during the authentication process.
+ SaslMechanism oauth2;
+
+ if (client.AuthenticationMechanisms.Contains ("OAUTHBEARER"))
+ oauth2 = new SaslMechanismOAuthBearer (credential.UserId, credential.Token.AccessToken);
+ else
+ oauth2 = new SaslMechanismOAuth2 (credential.UserId, credential.Token.AccessToken);
+
+ await client.AuthenticateAsync (oauth2);
+ }
+ }
+}
diff --git a/Documentation/Examples/OpenPGPExamples.cs b/Documentation/Examples/OpenPGPExamples.cs
index 6007cce257..b22d054aaf 100644
--- a/Documentation/Examples/OpenPGPExamples.cs
+++ b/Documentation/Examples/OpenPGPExamples.cs
@@ -126,9 +126,14 @@ static Stream Decrypt (MimeMessage message)
{
var text = message.TextBody;
- using (var memory = new MemoryStream (Encoding.ASCII.GetBytes (text), false)) {
+ using (var encrypted = new MemoryStream (Encoding.ASCII.GetBytes (text), false)) {
using (var ctx = new MyGnuPGContext ()) {
- return ctx.GetDecryptedStream (memory);
+ var decrypted = new MemoryStream ();
+
+ ctx.DecryptTo (encrypted, decrypted);
+ decrypted.Position = 0;
+
+ return decrypted;
}
}
}
diff --git a/Documentation/Examples/OpeningContent.cs b/Documentation/Examples/OpeningContent.cs
index a52334c927..18e9dde1b5 100644
--- a/Documentation/Examples/OpeningContent.cs
+++ b/Documentation/Examples/OpeningContent.cs
@@ -1,4 +1,4 @@
-using (var stream = part.ContentObject.Open ()) {
+using (var stream = part.Content.Open ()) {
// At this point, you can now read from the stream as if it were the original,
// raw content. Assuming you have an image UI control that could load from a
// stream, you could do something like this:
diff --git a/Documentation/Examples/ParameterExamples.cs b/Documentation/Examples/ParameterExamples.cs
new file mode 100644
index 0000000000..fa35bc8cd0
--- /dev/null
+++ b/Documentation/Examples/ParameterExamples.cs
@@ -0,0 +1,27 @@
+using System;
+
+using MimeKit;
+
+namespace MimeKit.Examples
+{
+ public static class ParameterExamples
+ {
+ public void OverrideAllParameterEncodings (MimePart part)
+ {
+ #region OverrideAllParameterEncodings
+ // Some versions of Outlook expect the rfc2047 style of encoding of parameter values.
+ foreach (var parameter in part.ContentDisposition.Parameters)
+ parameter.EncodingMethod = ParameterEncodingMethod.Rfc2047;
+ #endregion OverrideAllParameterEncodings
+ }
+
+ public void OverrideFileNameParameterEncodings (MimePart part)
+ {
+ #region OverrideFileNameParameterEncoding
+ // Some versions of Outlook expect the rfc2047 style of encoding for the filename parameter value.
+ if (part.ContentDisposition.Parameters.TryGetValue ("filename", out var parameter))
+ parameter.EncodingMethod = ParameterEncodingMethod.Rfc2047;
+ #endregion OverrideFileNameParameterEncoding
+ }
+ }
+}
diff --git a/Documentation/Examples/Pop3Examples.cs b/Documentation/Examples/Pop3Examples.cs
index 7594e8fb68..9acb31a703 100644
--- a/Documentation/Examples/Pop3Examples.cs
+++ b/Documentation/Examples/Pop3Examples.cs
@@ -1,9 +1,9 @@
//
// Pop3Examples.cs
//
-// Author: Jeffrey Stedfast
urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
{
- if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
+ if (ctx.TagId == HtmlTagId.Meta && !ctx.IsEndTag) {
+ bool isContentType = false;
+
+ ctx.WriteTag (htmlWriter, false);
+
+ // replace charsets with "utf-8" since our output will be in utf-8 (and not whatever the original charset was)
+ foreach (var attribute in ctx.Attributes) {
+ if (attribute.Id == HtmlAttributeId.Charset) {
+ htmlWriter.WriteAttributeName (attribute.Name);
+ htmlWriter.WriteAttributeValue ("utf-8");
+ } else if (isContentType && attribute.Id == HtmlAttributeId.Content) {
+ htmlWriter.WriteAttributeName (attribute.Name);
+ htmlWriter.WriteAttributeValue ("text/html; charset=utf-8");
+ } else {
+ if (attribute.Id == HtmlAttributeId.HttpEquiv && attribute.Value != null
+ && attribute.Value.Equals ("Content-Type", StringComparison.OrdinalIgnoreCase))
+ isContentType = true;
+
+ htmlWriter.WriteAttribute (attribute);
+ }
+ }
+ } else if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
ctx.WriteTag (htmlWriter, false);
// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes) {
if (attribute.Id == HtmlAttributeId.Src) {
- MimePart image;
- string url;
-
- if (!TryGetImage (attribute.Value, out image)) {
+ if (!TryGetImage (attribute.Value, out var image)) {
htmlWriter.WriteAttribute (attribute);
continue;
}
- url = SaveImage (image, attribute.Value);
+ // Note: you can either use a "file://" URI or you can use a
+ // "data:" URI, the choice is yours.
+ var uri = GetFileUri (image, attribute.Value);
+ //var uri = GetDataUri (image);
htmlWriter.WriteAttributeName (attribute.Name);
- htmlWriter.WriteAttributeValue (url);
+ htmlWriter.WriteAttributeValue (uri);
} else {
htmlWriter.WriteAttribute (attribute);
}
@@ -496,8 +770,8 @@ class HtmlPreviewVisitor : MimeVisitor
// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes) {
- if (attribute.Name.ToLowerInvariant () == "oncontextmenu")
- continue;
+ if (attribute.Name.Equals ("oncontextmenu", StringComparison.OrdinalIgnoreCase))
+ continue;
htmlWriter.WriteAttribute (attribute);
}
@@ -528,7 +802,7 @@ class HtmlPreviewVisitor : MimeVisitor
string delsp;
if (entity.ContentType.Parameters.TryGetValue ("delsp", out delsp))
- flowed.DeleteSpace = delsp.ToLowerInvariant () == "yes";
+ flowed.DeleteSpace = delsp.Equals ("yes", StringComparison.OrdinalIgnoreCase);
converter = flowed;
} else {
@@ -580,25 +854,25 @@ Once you've rendered the message using the above technique, you'll have a list o
were not used, even if they did not match the simplistic criteria used by the `MimeMessage.Attachments`
property.
-### Q: Why doesn't the `MimeMessage` class implement `ISerializable` so that I can serialize a message to disk and read it back later?
+### Q: Why doesn't the `MimeMessage` class implement `ISerializable` so that I can serialize a message to disk and read it back later?
The MimeKit API was designed to use the existing MIME format for serialization. In light of this, the ability
to use the .NET serialization API and format did not make much sense to support.
-You can easily serialize a [MimeMessage](http://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm) to a stream using the
-[WriteTo](http://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_WriteTo.htm) methods.
+You can easily serialize a [MimeMessage](https://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm) to a stream using the
+[WriteTo](https://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_WriteTo.htm) methods.
For more information on this topic, see the following other two topics:
-* How can I parse messages?
-* How can I save messages?
+* [How can I parse messages?](#load-messages)
+* [How can I save messages?](#save-messages)
-### Q: How can I parse messages?
+### Q: How can I parse messages?
One of the more common operations that MimeKit is meant for is parsing email messages from arbitrary streams.
There are two ways of accomplishing this task.
-The first way is to use one of the [Load](http://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_Load.htm) methods
+The first way is to use one of the [Load](https://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_Load.htm) methods
on `MimeMessage`:
```csharp
@@ -613,7 +887,7 @@ Or you can load a message from a file path:
var message = MimeMessage.Load ("message.eml");
```
-The second way is to use the [MimeParser](http://www.mimekit.net/docs/html/T_MimeKit_MimeParser.htm) class. For the most
+The second way is to use the [MimeParser](https://www.mimekit.net/docs/html/T_MimeKit_MimeParser.htm) class. For the most
part, using the `MimeParser` directly is not necessary unless you wish to parse a Unix mbox file stream. However, this is
how you would do it:
@@ -635,10 +909,10 @@ while (!parser.IsEndOfStream) {
}
```
-### Q: How can I save messages?
+### Q: How can I save messages?
-One you've got a [MimeMessage](http://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm), you can save
-it to a file using the [WriteTo](http://mimekit.net/docs/html/Overload_MimeKit_MimeMessage_WriteTo.htm) method:
+One you've got a [MimeMessage](https://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm), you can save
+it to a file using the [WriteTo](https://mimekit.net/docs/html/Overload_MimeKit_MimeMessage_WriteTo.htm) method:
```csharp
message.WriteTo ("message.eml");
@@ -648,7 +922,7 @@ The `WriteTo` method also has overloads that allow you to write the message to a
By default, the `WriteTo` method will save the message using DOS line-endings on Windows and Unix
line-endings on Unix-based systems such as macOS and Linux. You can override this behavior by
-passing a [FormatOptions](http://mimekit.net/docs/html/T_MimeKit_FormatOptions.htm) argument to
+passing a [FormatOptions](https://mimekit.net/docs/html/T_MimeKit_FormatOptions.htm) argument to
the method:
```csharp
@@ -667,19 +941,19 @@ strings due to the fact that each MIME part of the message *may* be encoded in a
character set, thus making it impossible to convert the message into a unicode string using a
single charset to do the conversion (which is *exactly* what `ToString` does).
-### Q: How can I save attachments?
+### Q: How can I save attachments?
-If you've already got a [MimePart](http://www.mimekit.net/docs/html/T_MimeKit_MimePart.htm) that represents
+If you've already got a [MimePart](https://www.mimekit.net/docs/html/T_MimeKit_MimePart.htm) that represents
the attachment that you'd like to save, here's how you might save it:
```csharp
using (var stream = File.Create (fileName))
- attachment.ContentObject.DecodeTo (stream);
+ attachment.Content.DecodeTo (stream);
```
Pretty simple, right?
-But what if your attachment is actually a [MessagePart](http://www.mimekit.net/docs/html/T_MimeKit_MessagePart.htm)?
+But what if your attachment is actually a [MessagePart](https://www.mimekit.net/docs/html/T_MimeKit_MessagePart.htm)?
To save the content of a `message/rfc822` part, you'd use the following code snippet:
@@ -693,43 +967,49 @@ If you are iterating over all of the attachments in a message, you might do some
```csharp
foreach (var attachment in message.Attachments) {
var fileName = attachment.ContentDisposition?.FileName ?? attachment.ContentType.Name;
-
+
+ if (string.IsNullOrEmpty (fileName))
+ fileName = "untitled.dat";
+
+ // make sure that the filename value does not contain a full path or invalid path characters
+ fileName = Path.GetFileName (fileName);
+
using (var stream = File.Create (fileName)) {
if (attachment is MessagePart) {
var rfc822 = (MessagePart) attachment;
-
+
rfc822.Message.WriteTo (stream);
} else {
var part = (MimePart) attachment;
-
- part.ContentObject.DecodeTo (stream);
+
+ part.Content.DecodeTo (stream);
}
}
}
```
-### Q: How can I get the email addresses in the From, To, and Cc headers?
+### Q: How can I get the email addresses in the From, To, and Cc headers?
-The [From](http://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_From.htm),
-[To](http://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_To.htm), and
-[Cc](http://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_Cc.htm) properties of a
-[MimeMessage](http://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm) are all of type
-[InternetAddressList](http://www.mimekit.net/docs/html/T_MimeKit_InternetAddressList.htm). An
+The [From](https://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_From.htm),
+[To](https://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_To.htm), and
+[Cc](https://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_Cc.htm) properties of a
+[MimeMessage](https://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm) are all of type
+[InternetAddressList](https://www.mimekit.net/docs/html/T_MimeKit_InternetAddressList.htm). An
`InternetAddressList` is a list of
-[InternetAddress](http://www.mimekit.net/docs/html/T_MimeKit_InternetAddress.htm) items. This is
+[InternetAddress](https://www.mimekit.net/docs/html/T_MimeKit_InternetAddress.htm) items. This is
where most people start to get lost because an `InternetAddress` is an abstract class that only
-really has a [Name](http://www.mimekit.net/docs/html/P_MimeKit_InternetAddress_Name.htm) property.
+really has a [Name](https://www.mimekit.net/docs/html/P_MimeKit_InternetAddress_Name.htm) property.
As you've probably already discovered, the `Name` property contains the name of the person
(if available), but what you want is his or her email address, not their name.
To get the email address, you'll need to figure out what subclass of address each `InternetAddress`
really is. There are 2 subclasses of `InternetAddress`:
-[GroupAddress](http://www.mimekit.net/docs/html/T_MimeKit_GroupAddress.htm) and
-[MailboxAddress](http://www.mimekit.net/docs/html/T_MimeKit_MailboxAddress.htm).
+[GroupAddress](https://www.mimekit.net/docs/html/T_MimeKit_GroupAddress.htm) and
+[MailboxAddress](https://www.mimekit.net/docs/html/T_MimeKit_MailboxAddress.htm).
A `GroupAddress` is a named group of more `InternetAddress` items that are contained within the
-[Members](http://www.mimekit.net/docs/html/P_MimeKit_GroupAddress_Members.htm) property. To get
+[Members](https://www.mimekit.net/docs/html/P_MimeKit_GroupAddress_Members.htm) property. To get
an idea of what a group address represents, consider the following examples:
```
@@ -750,7 +1030,7 @@ To: undisclosed-recipients:;
Most of the time, the `From`, `To`, and `Cc` headers will only contain mailbox addresses. As you will
notice, a `MailboxAddress` has an
-[Address](http://www.mimekit.net/docs/html/P_MimeKit_MailboxAddress_Address.htm) property which will
+[Address](https://www.mimekit.net/docs/html/P_MimeKit_MailboxAddress_Address.htm) property which will
contain the email address of the mailbox. In the following example, the `Address` property will
contain the value `john@smith.com`:
@@ -759,14 +1039,14 @@ To: John Smith
- htmlWriter.WriteEndTag (HtmlTagId.BlockQuote);
-
- // pass the
- htmlWriter.WriteStartTag (HtmlTagId.BlockQuote); - htmlWriter.WriteAttribute (HtmlAttributeId.Style, "border-left: 1px #ccc solid; margin: 0 0 0 .8ex; padding-left: 1ex;"); - - ctx.InvokeCallbackForEndTag = true; - } - } else { - // pass the tag through to the output - ctx.WriteTag (htmlWriter, true); - } - } - - string QuoteText (string text) - { - using (var quoted = new StringWriter ()) { - quoted.WriteLine (GetOnDateSenderWrote (original)); - - using (var reader = new StringReader (text)) { - string line; - - while ((line = reader.ReadLine ()) != null) { - quoted.Write ("> "); - quoted.WriteLine (line); - } - } - - return quoted.ToString (); - } - } - - protected override void VisitTextPart (TextPart entity) - { - string text; - - if (entity.IsHtml) { - var converter = new HtmlToHtml { - HtmlTagCallback = HtmlTagCallback - }; - - text = converter.Convert (entity.Text); - } else if (entity.IsFlowed) { - var converter = new FlowedToText (); - - text = converter.Convert (entity.Text); - text = QuoteText (text); - } else { - // quote the original message text - text = QuoteText (entity.Text); - } - - var part = new TextPart (entity.ContentType.MediaSubtype.ToLowerInvariant ()) { - Text = text - }; - - Push (part); - } - - protected override void VisitMessagePart (MessagePart entity) - { - // don't descend into message/rfc822 parts - } + readonly Stacktag through to the output + ctx.WriteTag (htmlWriter, true); + + // prepend the HTML reply with "On {DATE}, {SENDER} wrote:" + htmlWriter.WriteStartTag (HtmlTagId.P); + htmlWriter.WriteText (GetOnDateSenderWrote (original)); + htmlWriter.WriteEndTag (HtmlTagId.P); + + // Wrap the original content in astack = new Stack (); + MimeMessage original, reply; + MailboxAddress from; + bool replyToAll; + int isRelated; + + /// + /// Creates a new ReplyVisitor. + /// + public ReplyVisitor (MailboxAddress from, bool replyToAll) + { + this.replyToAll = replyToAll; + this.from = from; + } + + ///+ /// Gets the reply. + /// + ///The reply. + public MimeMessage Reply { + get { return reply; } + } + + void Push (MimeEntity entity) + { + var multipart = entity as Multipart; + + if (reply.Body == null) { + reply.Body = entity; + } else { + var parent = stack.Peek (); + parent.Add (entity); + } + + if (multipart != null) + stack.Push (multipart); + } + + void Pop () + { + stack.Pop (); + } + + static string GetOnDateSenderWrote (MimeMessage message) + { + var sender = message.Sender != null ? message.Sender : message.From.Mailboxes.FirstOrDefault (); + var name = sender != null ? (!string.IsNullOrEmpty (sender.Name) ? sender.Name : sender.Address) : "an unknown sender"; + + return string.Format ("On {0}, {1} wrote:", message.Date.ToString ("f"), name); + } + + ///+ /// Visit the specified message. + /// + /// The message. + public override void Visit (MimeMessage message) + { + reply = new MimeMessage (); + original = message; + + stack.Clear (); + + reply.From.Add (from.Clone ()); + + // reply to the sender of the message + if (message.ReplyTo.Count > 0) { + reply.To.AddRange (message.ReplyTo); + } else if (message.From.Count > 0) { + reply.To.AddRange (message.From); + } else if (message.Sender != null) { + reply.To.Add (message.Sender); + } + + if (replyToAll) { + // include all of the other original recipients - TODO: remove ourselves from these lists + reply.To.AddRange (message.To); + reply.Cc.AddRange (message.Cc); + } + + // set the reply subject + if (!message.Subject?.StartsWith ("Re:", StringComparison.OrdinalIgnoreCase)) + reply.Subject = "Re: " + (message.Subject ?? string.Empty); + else + reply.Subject = message.Subject; + + // construct the In-Reply-To and References headers + if (!string.IsNullOrEmpty (message.MessageId)) { + reply.InReplyTo = message.MessageId; + foreach (var id in message.References) + reply.References.Add (id); + reply.References.Add (message.MessageId); + } + + base.Visit (message); + } + + ///+ /// Visit the specified entity. + /// + /// The MIME entity. + ///+ /// Only Visit(MimeMessage) is supported. + /// + public override void Visit (MimeEntity entity) + { + throw new NotSupportedException (); + } + + protected override void VisitMultipartAlternative (MultipartAlternative alternative) + { + var multipart = new MultipartAlternative (); + + Push (multipart); + + for (int i = 0; i < alternative.Count; i++) + alternative[i].Accept (this); + + Pop (); + } + + protected override void VisitMultipartRelated (MultipartRelated related) + { + var multipart = new MultipartRelated (); + var root = related.Root; + + Push (multipart); + + root.Accept (this); + + isRelated++; + for (int i = 0; i < related.Count; i++) { + if (related[i] != root) + related[i].Accept (this); + } + isRelated--; + + Pop (); + } + + protected override void VisitMultipart (Multipart multipart) + { + foreach (var part in multipart) { + if (part is MultipartAlternative) + part.Accept (this); + else if (part is MultipartRelated) + part.Accept (this); + else if (part is TextPart) + part.Accept (this); + } + } + + void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter) + { + if (ctx.TagId == HtmlTagId.Body && !ctx.IsEmptyElementTag) { + if (ctx.IsEndTag) { + // end our opening+ htmlWriter.WriteEndTag (HtmlTagId.BlockQuote); + + // pass the
+ htmlWriter.WriteStartTag (HtmlTagId.BlockQuote); + htmlWriter.WriteAttribute (HtmlAttributeId.Style, "border-left: 1px #ccc solid; margin: 0 0 0 .8ex; padding-left: 1ex;"); + + ctx.InvokeCallbackForEndTag = true; + } + } else { + // pass the tag through to the output + ctx.WriteTag (htmlWriter, true); + } + } + + string QuoteText (string text) + { + using (var quoted = new StringWriter ()) { + quoted.WriteLine (GetOnDateSenderWrote (original)); + + using (var reader = new StringReader (text)) { + string line; + + while ((line = reader.ReadLine ()) != null) { + quoted.Write ("> "); + quoted.WriteLine (line); + } + } + + return quoted.ToString (); + } + } + + protected override void VisitTextPart (TextPart entity) + { + string text; + + if (entity.IsHtml) { + var converter = new HtmlToHtml { + HtmlTagCallback = HtmlTagCallback + }; + + text = converter.Convert (entity.Text); + } else if (entity.IsFlowed) { + var converter = new FlowedToText (); + + text = converter.Convert (entity.Text); + text = QuoteText (text); + } else { + // quote the original message text + text = QuoteText (entity.Text); + } + + var part = new TextPart (entity.ContentType.MediaSubtype.ToLowerInvariant ()) { + Text = text + }; + + Push (part); + } + + protected override void VisitMessagePart (MessagePart entity) + { + // don't descend into message/rfc822 parts + } + + protected override void VisitMimePart (MimePart entity) + { + if (isRelated > 0 || !entity.IsAttachment) { + var parent = stack.Peek (); + parent.Add (entity); + } + } } ``` ```csharp public static MimeMessage Reply (MimeMessage message, MailboxAddress from, bool replyToAll) { - var visitor = new ReplyVisitor (from, replyToAll); + var visitor = new ReplyVisitor (from, replyToAll); - visitor.Visit (message); + visitor.Visit (message); - return visitor.Reply; + return visitor.Reply; } ``` -### Q: How can I forward a message? +### Q: How can I forward a message? There are 2 common ways of forwarding a message: attaching the original message as an attachment and inlining the message body much like replying typically does. Which method you choose is up to you. -To forward a message by attaching it as an attachment, you would do do something like this: +To forward a message by attaching it as an attachment, you would do something like this: ```csharp public static MimeMessage Forward (MimeMessage original, MailboxAddress from, IEnumerableto) { - var message = new MimeMessage (); - message.From.Add (from); - message.To.AddRange (to); + var message = new MimeMessage (); + message.From.Add (from); + message.To.AddRange (to); - // set the forwarded subject - if (!original.Subject.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase)) - message.Subject = "FW: " + original.Subject; - else - message.Subject = original.Subject; + // set the forwarded subject + if (!original.Subject?.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase)) + message.Subject = "FW: " + (original.Subject ?? string.Empty); + else + message.Subject = original.Subject; - // create the main textual body of the message - var text = new TextPart ("plain") { Text = "Here's the forwarded message:" }; + // create the main textual body of the message + var text = new TextPart ("plain") { Text = "Here's the forwarded message:" }; - // create the message/rfc822 attachment for the original message - var rfc822 = new MessagePart { Message = original }; + // create the message/rfc822 attachment for the original message + var rfc822 = new MessagePart { Message = original }; - // create a multipart/mixed container for the text body and the forwarded message - var multipart = new Multipart ("mixed"); - multipart.Add (text); - multipart.Add (rfc822); + // create a multipart/mixed container for the text body and the forwarded message + var multipart = new Multipart ("mixed"); + multipart.Add (text); + multipart.Add (rfc822); - // set the multipart as the body of the message - message.Body = multipart; + // set the multipart as the body of the message + message.Body = multipart; - return message; + return message; } ``` -To forward a message by simply inlining the original message's text content, you can do something like this: +To forward a message by inlining the original message's text content, you can do something like this: ```csharp public static MimeMessage Forward (MimeMessage original, MailboxAddress from, IEnumerable to) { - var message = new MimeMessage (); - message.From.Add (from); - message.To.AddRange (to); - - // set the forwarded subject - if (!original.Subject.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase)) - message.Subject = "FW: " + original.Subject; - else - message.Subject = original.Subject; - - // quote the original message text - using (var text = new StringWriter ()) { - text.WriteLine (); - text.WriteLine ("-------- Original Message --------"); - text.WriteLine ("Subject: {0}", original.Subject); - text.WriteLine ("Date: {0}", DateUtils.FormatDate (original.Date)); - text.WriteLine ("From: {0}", original.From); - text.WriteLine ("To: {0}", original.To); - text.WriteLine (); - - text.Write (original.TextBody); - - message.Body = new TextPart ("plain") { - Text = text.ToString () - }; - } - - return message; + var message = new MimeMessage (); + message.From.Add (from); + message.To.AddRange (to); + + // set the forwarded subject + if (!original.Subject?.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase)) + message.Subject = "FW: " + (original.Subject ?? string.Empty); + else + message.Subject = original.Subject; + + // quote the original message text + using (var text = new StringWriter ()) { + text.WriteLine (); + text.WriteLine ("-------- Original Message --------"); + text.WriteLine ("Subject: {0}", original.Subject ?? string.Empty); + text.WriteLine ("Date: {0}", DateUtils.FormatDate (original.Date)); + text.WriteLine ("From: {0}", original.From); + text.WriteLine ("To: {0}", original.To); + text.WriteLine (); + + text.Write (original.TextBody); + + message.Body = new TextPart ("plain") { + Text = text.ToString () + }; + } + + return message; } ``` @@ -1287,15 +1581,15 @@ Keep in mind that not all messages will have a `TextBody` available, so you'll h ## ImapClient -### Q: How can I get the number of unread messages in a folder? +### Q: How can I get the number of unread messages in a folder? -If the folder is open (via [Open](http://www.mimekit.net/docs/html/Overload_MailKit_Net_Imap_ImapFolder_Open.htm)), -then the [ImapFolder.Unread](http://www.mimekit.net/docs/html/P_MailKit_MailFolder_Unread.htm) property will be kept +If the folder is open (via [Open](https://www.mimekit.net/docs/html/Overload_MailKit_Net_Imap_ImapFolder_Open.htm)), +then the [ImapFolder.Unread](https://www.mimekit.net/docs/html/P_MailKit_MailFolder_Unread.htm) property will be kept up to date (at least as-of the latest command issued to the server). If the folder *isn't* open, then you will need to query the unread state of the folder using the -[Status](http://www.mimekit.net/docs/html/M_MailKit_Net_Imap_ImapFolder_Status.htm) method with the -appropriate [StatusItems](http://www.mimekit.net/docs/html/T_MailKit_StatusItems.htm) flag(s). +[Status](https://www.mimekit.net/docs/html/M_MailKit_Net_Imap_ImapFolder_Status.htm) method with the +appropriate [StatusItems](https://www.mimekit.net/docs/html/T_MailKit_StatusItems.htm) flag(s). For example, to get the total *and* unread counts, you can do this: @@ -1306,7 +1600,7 @@ int total = folder.Count; int unread = folder.Unread; ``` -### Q: How can I search for messages delivered between two dates? +### Q: How can I search for messages delivered between two dates? The obvious solution is: @@ -1327,12 +1621,12 @@ var query = SearchQuery.Not (SearchQuery.DeliveredBefore (dateRange.BeginDate) var results = folder.Search (query); ``` -### Q: What does "The ImapClient is currently busy processing a command." mean? +### Q: What does "The ImapClient is currently busy processing a command." mean? If you get an InvalidOperationException with the message, "The ImapClient is currently busy processing a command.", it means that you are trying to use the -[ImapClient](http://www.mimekit.net/docs/html/T_MailKit_Net_Imap_ImapClient.htm) and/or one of its -[ImapFolder](http://www.mimekit.net/docs/html/T_MailKit_Net_Imap_ImapFolder.htm)s from multiple +[ImapClient](https://www.mimekit.net/docs/html/T_MailKit_Net_Imap_ImapClient.htm) and/or one of its +[ImapFolder](https://www.mimekit.net/docs/html/T_MailKit_Net_Imap_ImapFolder.htm)s from multiple threads. To avoid this situation, you'll need to lock the `SyncRoot` property of the `ImapClient` and `ImapFolder` @@ -1349,13 +1643,13 @@ lock (client.SyncRoot) { Note: Locking the `SyncRoot` is only necessary when using the synchronous API's. All `Async()` method variants already do this locking for you. -### Q: Why do I get InvalidOperationException: "The folder is not currently open."? +### Q: Why do I get InvalidOperationException: "The folder is not currently open."? If you get this exception, it's probably because you thought you had to open the destination folder that you passed as an argument to one of the -[CopyTo](http://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_CopyTo.htm) or -[MoveTo](http://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_MoveTo.htm) methods. When you opened -that destination folder, you also inadvertantly closed the source folder which is why you are getting this +[CopyTo](https://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_CopyTo.htm) or +[MoveTo](https://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_MoveTo.htm) methods. When you opened +that destination folder, you also inadvertently closed the source folder which is why you are getting this exception. The IMAP server can only have a single folder open at a time. Whenever you open a folder, you automatically @@ -1363,7 +1657,7 @@ close the previously opened folder. When copying or moving messages from one folder to another, you only need to have the source folder open. -### Q: Why doesn't ImapFolder.MoveTo() move the message out of the source folder? +### Q: Why doesn't ImapFolder.MoveTo() move the message out of the source folder? If you look at the source code for the `ImapFolder.MoveTo()` method, what you'll notice is that there are several code paths depending on the features that the IMAP server supports. @@ -1379,7 +1673,7 @@ messages. If the server supports the `UIDPLUS` extension, then MailKit will attempt to `EXPUNGE` the subset of messages that it just marked for deletion, however, if the `UIDPLUS` extension is not supported by the -IMAP server, then it cannot safely expunge just that subset of messages and so it simply stops there. +IMAP server, then it cannot safely expunge just that subset of messages and so it stops there. My guess is that your server supports neither `MOVE` nor `UIDPLUS` and that is why clients like Outlook continue to see the messages in your folder. I believe, however, that Outlook has a setting to show @@ -1388,14 +1682,14 @@ deleted messages with a strikeout (which you probably have disabled). So to answer your question more succinctly: After calling `folder.MoveTo (...);`, if you are confident that the messages marked for deletion should be expunged, call `folder.Expunge ();` -### Q: How can I mark messages as read for IMAP? +### Q: How can I mark messages as read for IMAP? The way to mark messages as read using the IMAP protocol is to set the `\Seen` flag on the message(s). To do this using MailKit, you will first need to know either the index(es) or the UID(s) of the messages that you would like to set the `\Seen` flag on. Once you have that information, you will want to call one of the -[AddFlags](http://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_AddFlags.htm) methods on the +[AddFlags](https://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_AddFlags.htm) methods on the `ImapFolder`. For example: ```csharp @@ -1408,9 +1702,184 @@ To mark messages as unread, you would *remove* the `\Seen` flag, like so: folder.RemoveFlags (uids, MessageFlags.Seen, true); ``` +### Q: How can I re-synchronize the cache for an IMAP folder? + +Assuming your IMAP server does not support the `QRESYNC` extension (which simplifies this procedure a ton), +here is some simple code to illustrate how to go about re-synchronizing your cache with the remote IMAP +server. + +```csharp +/// +/// Just a simple class to represent the cached information about a message. +/// +class CachedMessageInfo +{ + public UniqueId UniqueId; + public MessageFlags Flags; + public HashSetKeywords; + public Envelope Envelope; + public BodyPart Body; +} + +/// +/// Resynchronize the cache with the remote IMAP folder. +/// +/// The IMAP folder. +/// The local cache of message metadata. +/// The cached UIDVALIDITY value of the IMAP folder from a previous session. +static void ResyncFolder (ImapFolder folder, Listcache, ref uint cachedUidValidity) +{ + IList summaries; + + // Step 1: Open the folder. + + // Note: we only need read-only access to update our cache, but depending on + // what you plan to do with the folder after resynchronizing, you may want + // top open the folder in read-write mode instead. + folder.Open (FolderAccess.ReadOnly); + + if (cache.Count > 0) { + if (folder.UidValidity == cachedUidValidity) { + // Step 2: Remove messages from our cache that no longer exist on the server. + + // get the full list of UIDs on the server... + var all = folder.Search (SearchQuery.All); + + // remove any messages from our cache that no longer exist... + for (int i = 0; i < cache.Count; i++) { + if (!all.Contains (cache[i].UniqueId)) { + cache.RemoveAt (i); + i--; + } + } + + // Step 3: Sync any flag changes for our cached messages. + + // get a list of known uids... astute observers will note that an easy + // optimization to make here would be to merge this loop with the above + // loop. + var known = new UniqueIdSet (SortOrder.Ascending); + for (int i = 0; i < cache.Count; i++) + known.Add (cache[i].UniqueId); + + // fetch the flags for our known messages... + summaries = folder.Fetch (known, MessageSummaryItems.Flags); + for (int i = 0; i < summaries.Count; i++) { + // Note: the indexes should match up with our cache, but it wouldn't + // hurt to add error checking to make sure. I'm not bothering to here + // for simplicity reasons. + cache[i].Flags = summaries[i].Flags.Value; + cache[i].Keywords = summaries[i].Keywords; + } + } else { + // The UIDVALIDITY of the folder has changed. This means that our entire + // cache is obsolete. We need to clear our cache and start from scratch. + cachedUidValidity = folder.UidValidity; + cache.Clear (); + } + } else { + // We have nothing cached, so just start from scratch. + cachedUidValidity = folder.UidValidity; + } + + // Step 4: Fetch the messages we don't already know about and add them to our cache. + + summaries = folder.Fetch (cache.Count, -1, MessageSummaryItems.UniqueId | MessageSummaryItems.Flags | MessageSummaryItems.Envelope | MessageSummaryItems.BodyStructure); + for (int i = 0; i < summaries.Count; i++) { + cache.Add (new CachedMessageInfo { + UniqueId = summaries[i].UniqueId, + Flags = summaries[i].Flags.Value, + Keywords = summaries[i].Keywords, + Envelope = summaries[i].Envelope, + Body = summaries[i].Body + }); + } + + // Tada! Now we are resynchronized with the server! +} +``` + +### Q: How can I login using a shared mailbox in Office365? + +```csharp +var result = await GetPublicClientOAuth2CredentialsAsync ("IMAP", "sharedMailboxName@custom-domain.com"); + +// Note: We always use result.Account.Username instead of `Username` because the user may have selected an alternative account. +var oauth2 = new SaslMechanismOAuth2 (result.Account.Username, result.AccessToken); + +using (var client = new ImapClient ()) { + await client.ConnectAsync ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect); + await client.AuthenticateAsync (oauth2); + + // ... + + await client.DisconnectAsync (true); +} +``` + +Notes: + +1. The `GetPublicClientOAuth2CredentialsAsync()` method used in this example code snippet can be found in the +[ExchangeOAuth2.md](ExchangeOAuth2.md#desktop-and-mobile-applications) documentation. +2. Some users have reported that they need to use `"username@custom-domain.com\\sharedMailboxName"` as their +username instead of `"sharedMailboxName@custom-domain.com"`. + ## SmtpClient -### Q: How can I send email to a SpecifiedPickupDirectory? +### Q: Why doesn't the message show up in the "Sent Mail" folder after sending it? + +It seems to be a common misunderstanding that messages sent via SMTP will magically show up in the account's "Sent Mail" folder. + +In order for the message to show up in the "Sent Mail" folder, you will need to append the message to the "Sent Mail" folder +yourself because the SMTP protocol does not support doing this automatically. + +If the "Sent Mail" folder is a local mbox folder, you'll need to append it like this: + +```csharp +using (var mbox = File.Open ("C:\\path\\to\\Sent Mail.mbox", FileMode.Append, FileAccess.Write)) { + var marker = string.Format ("From MAILER-DAEMON {0}{1}", DateTime.Now.ToString (CultureInfo.InvariantCulture, "ddd MMM d HH:mm:ss yyyy"), Environment.NewLine); + var bytes = Encoding.ASCII.GetBytes (marker); + + // Write the mbox marker bytes. + mbox.Write (bytes, 0, bytes.Length); + + // Write the message, making sure to escape any line that looks like an mbox From-marker. + using (var filtered = new FilteredStream (stream)) { + filtered.Add (new MboxFromMarker ()); + message.WriteTo (filtered); + filtered.Flush (); + } + + mbox.Flush (); +} +``` + +If the "Sent Mail" folder exists on an IMAP server, you would need to do something more like this: + +```csharp +using (var client = new ImapClient ()) { + client.Connect ("imap.server.com", 993, SecureSocketOptions.SslOnConnect); + client.Authenticate ("username", "password"); + + IMailFolder sentMail; + + if (client.Capabilities.HasFlag (ImapCapabilities.SpecialUse)) { + sentMail = client.GetFolder (SpecialFolder.Sent); + } else { + var personal = client.GetFolder (client.PersonalNamespaces[0]); + + // Note: This assumes that the "Sent Mail" folder lives at the root of the folder hierarchy + // and is named "Sent Mail" as opposed to "Sent" or "Sent Items" or any other variation. + sentMail = personal.GetSubfolder ("Sent Mail"); + } + + sentMail.Append (message, MessageFlags.Seen); + + client.Disconnect (true); +} +``` + +### Q: How can I send email to a SpecifiedPickupDirectory? Based on Microsoft's [referencesource](https://github.com/Microsoft/referencesource/blob/master/System/net/System/Net/mail/SmtpClient.cs#L401), when `SmtpDeliveryMethod.SpecifiedPickupDirectory` is used, the `SmtpClient` saves the message to the @@ -1419,27 +1888,60 @@ specified pickup directory location using a randomly generated filename based on like this: ```csharp -void SendToPickupDirectory (MimeMessage message, string pickupDirectory) +public static void SaveToPickupDirectory (MimeMessage message, string pickupDirectory) { do { + // Generate a random file name to save the message to. var path = Path.Combine (pickupDirectory, Guid.NewGuid ().ToString () + ".eml"); + Stream stream; - if (File.Exists (path)) - continue; + try { + // Attempt to create the new file. + stream = File.Open (path, FileMode.CreateNew); + } catch (IOException) { + // If the file already exists, try again with a new Guid. + if (File.Exists (path)) + continue; + + // Otherwise, fail immediately since it probably means that there is + // no graceful way to recover from this error. + throw; + } try { - using (var stream = new FileStream (path, FileMode.CreateNew)) { - message.WriteTo (stream); - return; + using (stream) { + // IIS pickup directories expect the message to be "byte-stuffed" + // which means that lines beginning with "." need to be escaped + // by adding an extra "." to the beginning of the line. + // + // Use an SmtpDataFilter "byte-stuff" the message as it is written + // to the file stream. This is the same process that an SmtpClient + // would use when sending the message in a `DATA` command. + using (var filtered = new FilteredStream (stream)) { + filtered.Add (new SmtpDataFilter ()); + + // Make sure to write the message in DOS ( ) format. + var options = FormatOptions.Default.Clone (); + options.NewLineFormat = NewLineFormat.Dos; + + message.WriteTo (options, filtered); + filtered.Flush (); + return; + } } - } catch (IOException) { - // The file may have been created between our File.Exists() check and - // our attempt to create the stream. + } catch { + // An exception here probably means that the disk is full. + // + // Delete the file that was created above so that incomplete files are not + // left behind for IIS to send accidentally. + File.Delete (path); + throw; } } while (true); } ``` -### Q: How can I request a notification when the message is read by the user? + +### Q: How can I request a notification when the message is read by the user? The first thing I need to make clear is that requesting a notification does not guarantee that you'll actually get one. In order for you to receive a notification that the message was read by its recipient, the recipient's @@ -1455,7 +1957,7 @@ message.Headers[HeaderId.DispositionNotificationTo] = new MailboxAddress ("My Na For more information on this topic, read [rfc3798](https://tools.ietf.org/html/rfc3798). -### Q: How can I process a read receipt notification? +### Q: How can I process a read receipt notification? A read receipt notification comes in the form of a MIME message with a top-level MIME part with a MIME-type of `multipart/report` that has a `report-type` parameter with a value of `disposition-notification`. @@ -1472,7 +1974,7 @@ if (report != null && report.ReportType.Equals ("disposition-notification", Stri The first part of the `multipart/report` will be a human-readable explanation of the notification. The second part will have a MIME-type of `message/disposition-notification` and be represented by -a [MessageDispositionNotification](http://www.mimekit.net/docs/html/T_MimeKit_MessageDispositionNotification.htm). +a [MessageDispositionNotification](https://www.mimekit.net/docs/html/T_MimeKit_MessageDispositionNotification.htm). This notification part will contain a list of header-like fields containing information about the message that this notification is for such as the `Original-Message-Id`, `Original-Recipient`, etc. @@ -1484,4 +1986,5 @@ if (notification != null) { var messageId = notification.Fields["Original-Message-Id"]; } ``` + For more information on this topic, read [rfc3798](https://tools.ietf.org/html/rfc3798). diff --git a/GMailOAuth2.md b/GMailOAuth2.md new file mode 100644 index 0000000000..fb8f857ad2 --- /dev/null +++ b/GMailOAuth2.md @@ -0,0 +1,169 @@ +# Using OAuth2 With GMail (IMAP, POP3 or SMTP) + +## Quick Index + +* [Setting up OAuth2 for use with Google Mail](#setting-up-oauth2-for-use-with-google-mail) + * [Register Your Application with Google](#register-your-application-with-google) + * [Obtaining an OAuth2 Client ID and Secret](#obtaining-an-oauth2-client-id-and-secret) +* [Authenticating a Desktop App with the OAuth2 Client ID and Secret](#authenticating-a-desktop-app-with-the-oauth2-client-id-and-secret) +* [Authenticating an ASP.NET Web App with the OAuth2 Client ID and Secret](#authenticating-an-aspnet-web-app-with-the-oauth2-client-id-and-secret) + +## Setting up OAuth2 for use with Google Mail + +### Register Your Application with Google + +Go to [Google's Developer Console](https://cloud.google.com/console). + +Click the **Select A Project** button in the **Navigation Bar** at the top of the screen. + + + +Click the **New Project** button. + + + +Fill in the name **Project Name**, and if appropriate, select the **Organization** that your program +should be associated with. Then click *Create*. + + + +### Obtaining an OAuth2 Client ID and Secret + +Click the **☰** symbol, move down to **APIs & Services** and then select **OAuth consent screen**. + + + +Select the **External** radio item and then click **Create**. + + + +Fill in the **Application name** and any other fields that are appropriate for your application and then click +**Create**. + + + +Click **+ Create Credentials** and then select **OAuth client ID**. + + + +Select the **Other** radio item in the **Application type** section and then type in a name to use for the OAuth +client ID. Once completed, click **Create**. + + + +At this point, you will be presented with a web dialog that will allow you to copy the **Client ID** and +**Client Secret** strings into your clipboard to paste them into your program. + + + +## Authenticating a Desktop App with the OAuth2 Client ID and Secret + +Now that you have the **Client ID** and **Client Secret** strings, you'll need to plug those values into +your application. + +The following sample code uses the [Google.Apis.Auth](https://www.nuget.org/packages/Google.Apis.Auth/) +nuget package for obtaining the access token which will be needed by MailKit to pass on to the GMail +server. + +```csharp +const string GMailAccount = "username@gmail.com"; + +var clientSecrets = new ClientSecrets { + ClientId = "XXX.apps.googleusercontent.com", + ClientSecret = "XXX" +}; + +var codeFlow = new GoogleAuthorizationCodeFlow (new GoogleAuthorizationCodeFlow.Initializer { + // Cache tokens in ~/.local/share/google-filedatastore/CredentialCacheFolder on Linux/Mac + DataStore = new FileDataStore ("CredentialCacheFolder", false), + Scopes = new [] { "https://mail.google.com/" }, + ClientSecrets = clientSecrets, + LoginHint = GMailAccount +}); + +// Note: For a web app, you'll want to use AuthorizationCodeWebApp instead. +var codeReceiver = new LocalServerCodeReceiver (); +var authCode = new AuthorizationCodeInstalledApp (codeFlow, codeReceiver); + +var credential = await authCode.AuthorizeAsync (GMailAccount, CancellationToken.None); + +if (credential.Token.IsStale) + await credential.RefreshTokenAsync (CancellationToken.None); + +var oauth2 = new SaslMechanismOAuthBearer (credential.UserId, credential.Token.AccessToken); + +using (var client = new ImapClient ()) { + await client.ConnectAsync ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect); + await client.AuthenticateAsync (oauth2); + await client.DisconnectAsync (true); +} +``` + +## Authenticating an ASP.NET Web App with the OAuth2 Client ID and Secret + +Now that you have the **Client ID** and **Client Secret** strings, you'll need to plug those values into +your application. + +The following sample code uses the [Google.Apis.Auth](https://www.nuget.org/packages/Google.Apis.Auth/) +nuget package for obtaining the access token which will be needed by MailKit to pass on to the GMail +server. + +Add Google Authentication processor to your **Program.cs**. + +```csharp +builder.Services.AddAuthentication (options => { + // This forces challenge results to be handled by Google OpenID Handler, so there's no + // need to add an AccountController that emits challenges for Login. + options.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme; + + // This forces forbid results to be handled by Google OpenID Handler, which checks if + // extra scopes are required and does automatic incremental auth. + options.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme; + + // Default scheme that will handle everything else. + // Once a user is authenticated, the OAuth2 token info is stored in cookies. + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; +}) +.AddCookie (options => { + options.ExpireTimeSpan = TimeSpan.FromMinutes (5); +}) +.AddGoogleOpenIdConnect (options => { + var secrets = GoogleClientSecrets.FromFile ("client_secret.json").Secrets; + options.ClientId = secrets.ClientId; + options.ClientSecret = secrets.ClientSecret; +}); +``` + +Ensure that you are using Authorization and HttpsRedirection in your **Program.cs**: + +```csharp +app.UseHttpsRedirection (); +app.UseStaticFiles (); + +app.UseRouting (); + +app.UseAuthentication (); +app.UseAuthorization (); +``` + +Now, using the **GoogleScopedAuthorizeAttribute**, you can request scopes saved in a library as constants and request tokens for these scopes. + +```csharp +[GoogleScopedAuthorize(DriveService.ScopeConstants.DriveReadonly)] +public async Task AuthenticateAsync ([FromServices] IGoogleAuthProvider auth) +{ + GoogleCredential? googleCred = await auth.GetCredentialAsync (); + string token = await googleCred.UnderlyingCredential.GetAccessTokenForRequestAsync (); + + var oauth2 = new SaslMechanismOAuthBearer ("UserEmail", token); + + using var emailClient = new ImapClient (); + await emailClient.ConnectAsync ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect); + await emailClient.AuthenticateAsync (oauth2); + await emailClient.DisconnectAsync (true); +} +``` + +All of that and more has been described in Google's [OAuth 2.0](https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc) +documentation. However, be careful since [Asp.Net MVC](https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-asp.net-mvc) +does not work for Asp.Net Core. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..b316677fc6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2013-2026 .NET Foundation and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/License.md b/License.md deleted file mode 100644 index 2f6dc6c859..0000000000 --- a/License.md +++ /dev/null @@ -1,21 +0,0 @@ -## License Information - -MailKit is Copyright (C) 2013-2016 Xamarin Inc. and is licensed under the MIT license: - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. diff --git a/MailKit.Coverity.sln b/MailKit.Coverity.sln index 66444b7a40..e22ac56651 100644 --- a/MailKit.Coverity.sln +++ b/MailKit.Coverity.sln @@ -1,36 +1,37 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Net45", "MailKit\MailKit.Net45.csproj", "{7264D469-A390-4C10-9C87-DAA37EDD3C1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Net45", "submodules\MimeKit\MimeKit\MimeKit.Net45.csproj", "{D5F54A4F-D84B-430F-9271-F7861E285B3E}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{BB3237C7-E19C-4232-B875-6658ABDD184A}" ProjectSection(SolutionItems) = preProject .nuget\packages.config = .nuget\packages.config EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MimeKit", "submodules\MimeKit\MimeKit\MimeKit.csproj", "{4453C1EF-9C6A-4305-B70B-9154AE48B63C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MailKit", "MailKit\MailKit.csproj", "{67EBBC81-9334-49CE-BF7B-17DA659E9736}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.Build.0 = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.Build.0 = Release|Any CPU + {4453C1EF-9C6A-4305-B70B-9154AE48B63C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4453C1EF-9C6A-4305-B70B-9154AE48B63C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4453C1EF-9C6A-4305-B70B-9154AE48B63C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4453C1EF-9C6A-4305-B70B-9154AE48B63C}.Release|Any CPU.Build.0 = Release|Any CPU + {67EBBC81-9334-49CE-BF7B-17DA659E9736}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67EBBC81-9334-49CE-BF7B-17DA659E9736}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67EBBC81-9334-49CE-BF7B-17DA659E9736}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67EBBC81-9334-49CE-BF7B-17DA659E9736}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AE4FD452-949D-40F5-B83B-B439EBA814AA} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = MailKit\MailKit.Net45.csproj diff --git a/MailKit.Documentation.sln b/MailKit.Documentation.sln index 35ba8c6eca..82ae58e147 100644 --- a/MailKit.Documentation.sln +++ b/MailKit.Documentation.sln @@ -1,20 +1,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29926.136 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Net45", "MailKit\MailKit.Net45.csproj", "{7264D469-A390-4C10-9C87-DAA37EDD3C1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Net45", "submodules\MimeKit\MimeKit\MimeKit.Net45.csproj", "{D5F54A4F-D84B-430F-9271-F7861E285B3E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BouncyCastle", "submodules\MimeKit\submodules\bc-csharp\crypto\BouncyCastle.csproj", "{4C235092-820C-4DEB-9074-D356FB797D8B}" -EndProject Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "Documentation", "Documentation\Documentation.shfbproj", "{59115814-A1E3-46AE-AE30-4065AE8F4CAF}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{2299B3E8-95E4-4766-9AA2-5553EAD7F375}" - ProjectSection(SolutionItems) = preProject - .nuget\packages.config = .nuget\packages.config - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MimeKit", "submodules\MimeKit\MimeKit\MimeKit.csproj", "{FAEC8A91-6983-4ED9-A414-09C6B65B13BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MailKit", "MailKit\MailKit.csproj", "{E543A427-93DE-4E65-ADF2-44412E440FB1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -22,25 +15,24 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.Build.0 = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.Build.0 = Release|Any CPU - {4C235092-820C-4DEB-9074-D356FB797D8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C235092-820C-4DEB-9074-D356FB797D8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C235092-820C-4DEB-9074-D356FB797D8B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C235092-820C-4DEB-9074-D356FB797D8B}.Release|Any CPU.Build.0 = Release|Any CPU {59115814-A1E3-46AE-AE30-4065AE8F4CAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59115814-A1E3-46AE-AE30-4065AE8F4CAF}.Release|Any CPU.ActiveCfg = Release|Any CPU {59115814-A1E3-46AE-AE30-4065AE8F4CAF}.Release|Any CPU.Build.0 = Release|Any CPU + {FAEC8A91-6983-4ED9-A414-09C6B65B13BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAEC8A91-6983-4ED9-A414-09C6B65B13BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAEC8A91-6983-4ED9-A414-09C6B65B13BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAEC8A91-6983-4ED9-A414-09C6B65B13BB}.Release|Any CPU.Build.0 = Release|Any CPU + {E543A427-93DE-4E65-ADF2-44412E440FB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E543A427-93DE-4E65-ADF2-44412E440FB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E543A427-93DE-4E65-ADF2-44412E440FB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E543A427-93DE-4E65-ADF2-44412E440FB1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EB22C82D-37B9-4F33-9228-B5FD1B590B1F} + EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 $0.StandardHeader = $1 diff --git a/MailKit.Mobile.sln b/MailKit.Mobile.sln deleted file mode 100644 index 9a1e97cca9..0000000000 --- a/MailKit.Mobile.sln +++ /dev/null @@ -1,85 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BouncyCastle.Android", "submodules\MimeKit\submodules\bc-csharp\crypto\BouncyCastle.Android.csproj", "{A0D302CB-8866-4AB1-98B9-F0772EABF5DF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BouncyCastle.iOS", "submodules\MimeKit\submodules\bc-csharp\crypto\BouncyCastle.iOS.csproj", "{0249241C-205E-4AC0-828B-90F822359B9E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Android", "submodules\MimeKit\MimeKit\MimeKit.Android.csproj", "{004B4019-62B7-4A15-AF2C-C20968845C46}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.iOS", "submodules\MimeKit\MimeKit\MimeKit.iOS.csproj", "{4C1288AD-12C8-4BF7-AED7-6C4DC539C856}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Android", "MailKit\MailKit.Android.csproj", "{9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.iOS", "MailKit\MailKit.iOS.csproj", "{60B5D72B-8219-48B6-B688-AD0FE284A96A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - AppStore|Any CPU = AppStore|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {004B4019-62B7-4A15-AF2C-C20968845C46}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Release|Any CPU.Build.0 = Release|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Release|Any CPU.Build.0 = Release|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Release|Any CPU.Build.0 = Release|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Release|Any CPU.Build.0 = Release|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Release|Any CPU.Build.0 = Release|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = MailKit\MailKit.iOS..csproj - Policies = $0 - $0.TextStylePolicy = $1 - $1.inheritsSet = null - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.AlignEmbeddedUsingStatements = False - $2.AlignEmbeddedIfStatements = False - $2.NamespaceBraceStyle = EndOfLine - $2.StructBraceStyle = EndOfLine - $2.EnumBraceStyle = EndOfLine - $2.BeforeIndexerDeclarationBracket = False - $2.AfterDelegateDeclarationParameterComma = True - $2.BeforeSizeOfParentheses = True - $2.BeforeTypeOfParentheses = True - $2.SpacesAfterTypecast = True - $2.BlankLinesBeforeUsings = 1 - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - $0.StandardHeader = $3 - $3.Text = @\r\n${FileName}\n \nAuthor: ${AuthorName} <${AuthorEmail}>\n\nCopyright (c) ${Year} ${CopyrightHolder}\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n - $3.IncludeInNewFiles = True - EndGlobalSection -EndGlobal diff --git a/MailKit.Net40.sln b/MailKit.Net40.sln deleted file mode 100644 index 24382ab3d6..0000000000 --- a/MailKit.Net40.sln +++ /dev/null @@ -1,52 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Net40", "MailKit\MailKit.Net40.csproj", "{DB3A2478-4742-452B-80C1-F672B64285AD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Net40", "submodules\MimeKit\MimeKit\MimeKit.Net40.csproj", "{C909FC86-6084-41E5-B99C-DCDF2A5B7F82}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Release|Any CPU.Build.0 = Release|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = MailKit\MailKit.Net40.csproj - Policies = $0 - $0.TextStylePolicy = $1 - $1.inheritsSet = null - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.AlignEmbeddedUsingStatements = False - $2.AlignEmbeddedIfStatements = False - $2.NamespaceBraceStyle = EndOfLine - $2.StructBraceStyle = EndOfLine - $2.EnumBraceStyle = EndOfLine - $2.BeforeIndexerDeclarationBracket = False - $2.AfterDelegateDeclarationParameterComma = True - $2.BeforeSizeOfParentheses = True - $2.BeforeTypeOfParentheses = True - $2.SpacesAfterTypecast = True - $2.BlankLinesBeforeUsings = 1 - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - $0.TextStylePolicy = $3 - $3.FileWidth = 120 - $3.TabsToSpaces = False - $3.EolMarker = Unix - $3.inheritsSet = VisualStudio - $3.inheritsScope = text/plain - $3.scope = text/plain - EndGlobalSection -EndGlobal diff --git a/MailKit.Net45.sln b/MailKit.Net45.sln deleted file mode 100644 index ed17f92591..0000000000 --- a/MailKit.Net45.sln +++ /dev/null @@ -1,66 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -VisualStudioVersion = 12.0.31101.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Net45", "MailKit\MailKit.Net45.csproj", "{7264D469-A390-4C10-9C87-DAA37EDD3C1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Net45", "submodules\MimeKit\MimeKit\MimeKit.Net45.csproj", "{D5F54A4F-D84B-430F-9271-F7861E285B3E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{637EC535-3921-4A7A-8CB4-00A5AB18FAA2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{BB3237C7-E19C-4232-B875-6658ABDD184A}" - ProjectSection(SolutionItems) = preProject - .nuget\packages.config = .nuget\packages.config - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.Build.0 = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = MailKit\MailKit.Net45.csproj - Policies = $0 - $0.TextStylePolicy = $1 - $1.inheritsSet = null - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.AlignEmbeddedUsingStatements = False - $2.AlignEmbeddedIfStatements = False - $2.NamespaceBraceStyle = EndOfLine - $2.StructBraceStyle = EndOfLine - $2.EnumBraceStyle = EndOfLine - $2.BeforeIndexerDeclarationBracket = False - $2.AfterDelegateDeclarationParameterComma = True - $2.BeforeSizeOfParentheses = True - $2.BeforeTypeOfParentheses = True - $2.SpacesAfterTypecast = True - $2.BlankLinesBeforeUsings = 1 - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - $0.StandardHeader = $3 - $3.Text = @\r\n${FileName}\n \nAuthor: ${AuthorName} <${AuthorEmail}>\n\nCopyright (c) ${Year} ${CopyrightHolder}\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n - $3.IncludeInNewFiles = True - EndGlobalSection -EndGlobal diff --git a/MailKit.sln b/MailKit.sln index 87192bec59..2270597fad 100644 --- a/MailKit.sln +++ b/MailKit.sln @@ -1,41 +1,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.9 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32516.85 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Net45", "MailKit\MailKit.Net45.csproj", "{7264D469-A390-4C10-9C87-DAA37EDD3C1D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D5001AA9-4C61-475F-8EA3-4C15949D849F}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Net45", "submodules\MimeKit\MimeKit\MimeKit.Net45.csproj", "{D5F54A4F-D84B-430F-9271-F7861E285B3E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MimeKit", "submodules\MimeKit\MimeKit\MimeKit.csproj", "{B0E5B7C4-710E-4DDE-9C00-1234844ADA76}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Net40", "MailKit\MailKit.Net40.csproj", "{DB3A2478-4742-452B-80C1-F672B64285AD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MailKit", "MailKit\MailKit.csproj", "{12F096E4-8CDC-4D5B-87B8-8AD71A3B5BED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Net40", "submodules\MimeKit\MimeKit\MimeKit.Net40.csproj", "{C909FC86-6084-41E5-B99C-DCDF2A5B7F82}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Data.Sqlite", "submodules\MimeKit\Mono.Data.Sqlite\Mono.Data.Sqlite.csproj", "{F26434C1-BA3D-41FB-B560-C009CB72B1B6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BouncyCastle.Android", "submodules\MimeKit\submodules\bc-csharp\crypto\BouncyCastle.Android.csproj", "{A0D302CB-8866-4AB1-98B9-F0772EABF5DF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.Android", "MailKit\MailKit.Android.csproj", "{9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.Android", "submodules\MimeKit\MimeKit\MimeKit.Android.csproj", "{004B4019-62B7-4A15-AF2C-C20968845C46}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.iOS", "MailKit\MailKit.iOS.csproj", "{60B5D72B-8219-48B6-B688-AD0FE284A96A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.iOS", "submodules\MimeKit\MimeKit\MimeKit.iOS.csproj", "{4C1288AD-12C8-4BF7-AED7-6C4DC539C856}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BouncyCastle.iOS", "submodules\MimeKit\submodules\bc-csharp\crypto\BouncyCastle.iOS.csproj", "{0249241C-205E-4AC0-828B-90F822359B9E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{637EC535-3921-4A7A-8CB4-00A5AB18FAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MimeKit.NetStandard", "submodules\MimeKit\MimeKit\MimeKit.NetStandard.csproj", "{E8667DCE-A5BB-4D30-9815-FC8959E447F5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MailKit.NetStandard", "MailKit\MailKit.NetStandard.csproj", "{507D2CF2-55FF-463F-8513-778442EBD251}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portable.Text.Encoding.WindowsUniversal81", "submodules\MimeKit\submodules\Portable.Text.Encoding\Portable.Text.Encoding\Portable.Text.Encoding.WindowsUniversal81.csproj", "{B76A64F9-B00E-4243-AE89-5D024CA3B436}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimeKit.WindowsUniversal81", "submodules\MimeKit\MimeKit\MimeKit.WindowsUniversal81.csproj", "{D9906B8C-7BBD-4CCE-AC7C-E9BCA020D20C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailKit.WindowsUniversal81", "MailKit\MailKit.WindowsUniversal81.csproj", "{5C20EB98-8084-41E7-952A-F297C0AAC916}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{1B670279-AEA7-4D9B-A854-CB4CC177B277}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,101 +20,23 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7264D469-A390-4C10-9C87-DAA37EDD3C1D}.Release|Any CPU.Build.0 = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5F54A4F-D84B-430F-9271-F7861E285B3E}.Release|Any CPU.Build.0 = Release|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB3A2478-4742-452B-80C1-F672B64285AD}.Release|Any CPU.Build.0 = Release|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C909FC86-6084-41E5-B99C-DCDF2A5B7F82}.Release|Any CPU.Build.0 = Release|Any CPU - {F26434C1-BA3D-41FB-B560-C009CB72B1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F26434C1-BA3D-41FB-B560-C009CB72B1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F26434C1-BA3D-41FB-B560-C009CB72B1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F26434C1-BA3D-41FB-B560-C009CB72B1B6}.Release|Any CPU.Build.0 = Release|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0D302CB-8866-4AB1-98B9-F0772EABF5DF}.Release|Any CPU.Build.0 = Release|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BF818C3-C20E-4EFB-9426-F0A6C2EDC627}.Release|Any CPU.Build.0 = Release|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {004B4019-62B7-4A15-AF2C-C20968845C46}.Release|Any CPU.Build.0 = Release|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60B5D72B-8219-48B6-B688-AD0FE284A96A}.Release|Any CPU.Build.0 = Release|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C1288AD-12C8-4BF7-AED7-6C4DC539C856}.Release|Any CPU.Build.0 = Release|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0249241C-205E-4AC0-828B-90F822359B9E}.Release|Any CPU.Build.0 = Release|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {637EC535-3921-4A7A-8CB4-00A5AB18FAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {E8667DCE-A5BB-4D30-9815-FC8959E447F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8667DCE-A5BB-4D30-9815-FC8959E447F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8667DCE-A5BB-4D30-9815-FC8959E447F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8667DCE-A5BB-4D30-9815-FC8959E447F5}.Release|Any CPU.Build.0 = Release|Any CPU - {B76A64F9-B00E-4243-AE89-5D024CA3B436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B76A64F9-B00E-4243-AE89-5D024CA3B436}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B76A64F9-B00E-4243-AE89-5D024CA3B436}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B76A64F9-B00E-4243-AE89-5D024CA3B436}.Release|Any CPU.Build.0 = Release|Any CPU - {D9906B8C-7BBD-4CCE-AC7C-E9BCA020D20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9906B8C-7BBD-4CCE-AC7C-E9BCA020D20C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9906B8C-7BBD-4CCE-AC7C-E9BCA020D20C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9906B8C-7BBD-4CCE-AC7C-E9BCA020D20C}.Release|Any CPU.Build.0 = Release|Any CPU - {5C20EB98-8084-41E7-952A-F297C0AAC916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C20EB98-8084-41E7-952A-F297C0AAC916}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C20EB98-8084-41E7-952A-F297C0AAC916}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C20EB98-8084-41E7-952A-F297C0AAC916}.Release|Any CPU.Build.0 = Release|Any CPU - {507D2CF2-55FF-463F-8513-778442EBD251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {507D2CF2-55FF-463F-8513-778442EBD251}.Debug|Any CPU.Build.0 = Debug|Any CPU - {507D2CF2-55FF-463F-8513-778442EBD251}.Release|Any CPU.ActiveCfg = Release|Any CPU - {507D2CF2-55FF-463F-8513-778442EBD251}.Release|Any CPU.Build.0 = Release|Any CPU + {B0E5B7C4-710E-4DDE-9C00-1234844ADA76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0E5B7C4-710E-4DDE-9C00-1234844ADA76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0E5B7C4-710E-4DDE-9C00-1234844ADA76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0E5B7C4-710E-4DDE-9C00-1234844ADA76}.Release|Any CPU.Build.0 = Release|Any CPU + {12F096E4-8CDC-4D5B-87B8-8AD71A3B5BED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12F096E4-8CDC-4D5B-87B8-8AD71A3B5BED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12F096E4-8CDC-4D5B-87B8-8AD71A3B5BED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12F096E4-8CDC-4D5B-87B8-8AD71A3B5BED}.Release|Any CPU.Build.0 = Release|Any CPU + {1B670279-AEA7-4D9B-A854-CB4CC177B277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B670279-AEA7-4D9B-A854-CB4CC177B277}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B670279-AEA7-4D9B-A854-CB4CC177B277}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B670279-AEA7-4D9B-A854-CB4CC177B277}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = MailKit\MailKit.csproj - Policies = $0 - $0.TextStylePolicy = $1 - $1.inheritsSet = null - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.AlignEmbeddedUsingStatements = False - $2.AlignEmbeddedIfStatements = False - $2.NamespaceBraceStyle = EndOfLine - $2.StructBraceStyle = EndOfLine - $2.EnumBraceStyle = EndOfLine - $2.BeforeIndexerDeclarationBracket = False - $2.AfterDelegateDeclarationParameterComma = True - $2.BeforeSizeOfParentheses = True - $2.BeforeTypeOfParentheses = True - $2.SpacesAfterTypecast = True - $2.BlankLinesBeforeUsings = 1 - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - $0.StandardHeader = $3 - $3.Text = @\r\n${FileName}\n \nAuthor: ${AuthorName} <${AuthorEmail}>\n\nCopyright (c) ${Year} ${CopyrightHolder}\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n - $3.IncludeInNewFiles = True + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {80311676-045A-4523-8BD6-AEAD1F21474C} EndGlobalSection EndGlobal diff --git a/MailKit/AccessControl.cs b/MailKit/AccessControl.cs index 3e07bae13a..c0d0efa8c1 100644 --- a/MailKit/AccessControl.cs +++ b/MailKit/AccessControl.cs @@ -1,9 +1,9 @@ -// +// // AccessControl.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -50,9 +50,9 @@ public class AccessControl /// The identifier name. /// The access rights. /// - /// public AccessControl (string name, IEnumerable+ /// is null ./// is . -or- - ///+ /// is null ./// is . rights) { @@ -73,9 +73,9 @@ public AccessControl (string name, IEnumerable rights) /// The identifier name. /// The access rights. /// - /// public AccessControl (string name, string rights) { @@ -95,7 +95,7 @@ public AccessControl (string name, string rights) /// /// The identifier name. ///+ /// is null ./// is . -or- - ///+ /// is null ./// is . - /// public AccessControl (string name) { diff --git a/MailKit/AccessControlList.cs b/MailKit/AccessControlList.cs index 409c10c619..cf8f7fc21a 100644 --- a/MailKit/AccessControlList.cs +++ b/MailKit/AccessControlList.cs @@ -1,9 +1,9 @@ -// +// // AccessControlList.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -47,7 +47,7 @@ public class AccessControlList : List /// /// The list of access controls. /// - /// public AccessControlList (IEnumerableis null . + ///is . /// controls) : base (controls) { diff --git a/MailKit/AccessRight.cs b/MailKit/AccessRight.cs index 17d153ef3e..be3efbf71e 100644 --- a/MailKit/AccessRight.cs +++ b/MailKit/AccessRight.cs @@ -1,9 +1,9 @@ -// +// // AccessRight.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -37,7 +37,7 @@ namespace MailKit { /// see https://tools.ietf.org/html/rfc4314#section-2.1 /// /// - public struct AccessRight : IEquatable + public readonly struct AccessRight : IEquatable { /// /// The access right for folder lookups. @@ -154,8 +154,8 @@ public AccessRight (char right) /// Determines whether the specified is equal to the current . /// /// The to compare with the current . - /// + /// true if the specifiedis equal to the current - /// ; otherwise, false .public bool Equals (AccessRight other) { return other.Right == Right; @@ -169,7 +169,7 @@ public bool Equals (AccessRight other) /// if the specified is equal to the current + /// ; otherwise, . /// Determines whether two access rights are equal. /// - ///+ /// true ifand are equal; otherwise, false ./// The first access right to compare. /// The second access right to compare. public static bool operator == (AccessRight right1, AccessRight right2) @@ -183,7 +183,7 @@ public bool Equals (AccessRight other) /// if and are equal; otherwise, . /// Determines whether two access rights are not equal. /// - ///+ /// true ifand are not equal; otherwise, false ./// The first access right to compare. /// The second access right to compare. public static bool operator != (AccessRight right1, AccessRight right2) @@ -198,11 +198,11 @@ public bool Equals (AccessRight other) /// Determines whether the specified if and are not equal; otherwise, . is equal to the current . /// /// The to compare with the current . - /// - public override bool Equals (object obj) + /// true if the specifiedis equal to the current ; - /// otherwise, false .+ public override bool Equals (object? obj) { - return obj is AccessRight && ((AccessRight) obj).Right == Right; + return obj is AccessRight right && right.Right == Right; } /// if the specified is equal to the current ; + /// otherwise, . diff --git a/MailKit/AccessRights.cs b/MailKit/AccessRights.cs index ea50eaed80..1ef3619f9f 100644 --- a/MailKit/AccessRights.cs +++ b/MailKit/AccessRights.cs @@ -1,9 +1,9 @@ -// +// // AccessRights.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -47,7 +47,7 @@ public class AccessRights : ICollection /// /// The access rights. /// - /// public AccessRights (IEnumerableis null . + ///is . /// rights) { @@ -62,7 +62,7 @@ public AccessRights (IEnumerable rights) /// /// The access rights. /// - /// public AccessRights (string rights) { @@ -96,7 +96,7 @@ public int Count { ///is null . + ///is . /// /// Gets whether or not this set of access rights is read only. /// - ///+ /// true if this collection is read only; otherwise,false .public bool IsReadOnly { get { return false; } } @@ -119,7 +119,7 @@ void ICollection if this collection is read only; otherwise, . .Add (AccessRight right) /// /// Adds the specified access right if it is not already included. /// - ///+ /// true if the right was added; otherwise,false ./// The access right. public bool Add (AccessRight right) { @@ -137,7 +137,7 @@ public bool Add (AccessRight right) /// if the right was added; otherwise, . /// Adds the right specified by the given character. /// - ///+ /// true if the right was added; otherwise,false ./// The right. public bool Add (char right) { @@ -152,7 +152,7 @@ public bool Add (char right) /// /// The rights. /// if the right was added; otherwise, . - /// public void AddRange (string rights) { @@ -171,7 +171,7 @@ public void AddRange (string rights) /// /// The rights. ///is null . + ///is . /// - /// public void AddRange (IEnumerableis null . + ///is . /// rights) { @@ -199,7 +199,7 @@ public void Clear () /// /// Determines whether or not the set of access rights already contains the specified right /// - ///+ /// true if the specified right exists; otherwisefalse ./// The access right. public bool Contains (AccessRight right) { @@ -216,7 +216,7 @@ public bool Contains (AccessRight right) /// The array. /// The array index. /// if the specified right exists; otherwise, . - /// ///is null . + ///is . /// /// is out of range. @@ -238,7 +238,7 @@ public void CopyTo (AccessRight[] array, int arrayIndex) /// /// Removes the specified access right. /// - ///+ /// true if the access right was removed; otherwisefalse ./// The access right. public bool Remove (AccessRight right) { diff --git a/MailKit/AlertEventArgs.cs b/MailKit/AlertEventArgs.cs index b9b40e61ab..0d7e986045 100644 --- a/MailKit/AlertEventArgs.cs +++ b/MailKit/AlertEventArgs.cs @@ -1,9 +1,9 @@ -// +// // AlertEventArgs.cs // // Author: Jeffrey Stedfast if the access right was removed; otherwise, . // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -45,7 +45,7 @@ public class AlertEventArgs : EventArgs /// /// The alert message. /// - /// public AlertEventArgs (string message) { diff --git a/MailKit/Annotation.cs b/MailKit/Annotation.cs new file mode 100644 index 0000000000..2bc3029eff --- /dev/null +++ b/MailKit/Annotation.cs @@ -0,0 +1,82 @@ +// +// Annotation.cs +// +// Author: Jeffrey Stedfastis null . + ///is . /// +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +namespace MailKit { + /// + /// An annotation. + /// + ///+ /// + public class Annotation + { + ///An annotation. + ///For more information about annotations, see + /// rfc5257. + ///+ /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The annotation entry. + ///. + /// + /// + public Annotation (AnnotationEntry entry) + { + if (entry is null) + throw new ArgumentNullException (nameof (entry)); + + Properties = new Dictionaryis . + /// (); + Entry = entry; + } + + /// + /// Get the annotation tag. + /// + ///+ /// Gets the annotation tag. + /// + ///The annotation tag. + public AnnotationEntry Entry { + get; private set; + } + + ///+ /// Get the annotation properties. + /// + ///+ /// Gets the annotation properties. + /// + ///The annotation properties. + public DictionaryProperties { + get; private set; + } + } +} diff --git a/MailKit/Security/Ntlm/NtlmAuthLevel.cs b/MailKit/AnnotationAccess.cs similarity index 55% rename from MailKit/Security/Ntlm/NtlmAuthLevel.cs rename to MailKit/AnnotationAccess.cs index c80cae4664..0c4b6a08ba 100644 --- a/MailKit/Security/Ntlm/NtlmAuthLevel.cs +++ b/MailKit/AnnotationAccess.cs @@ -1,10 +1,9 @@ +// +// AnnotationAccess.cs // -// NtlmAuthLevel.cs +// Author: Jeffrey Stedfast // -// Author: -// Martin Baulig -// -// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,29 +22,32 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +// + +namespace MailKit { + /// + /// An annotation access level. + /// + ///+ /// + public enum AnnotationAccess + { + ///An annotation access level. + ///For more information about annotations, see + /// rfc5257. + ///+ /// Annotations are not supported. + /// + None, + + ///+ /// Annotations are read-only. + /// + ReadOnly, -namespace MailKit.Security.Ntlm { - /* - * On Windows, this is controlled by a registry setting - * (http://msdn.microsoft.com/en-us/library/ms814176.aspx) - * - * This can be configured by setting the static - * Type3Message.DefaultAuthLevel property, the default value - * is LM_and_NTLM_and_try_NTLMv2_Session. - */ - enum NtlmAuthLevel { - /* Use LM and NTLM, never use NTLMv2 session security. */ - LM_and_NTLM, - - /* Use NTLMv2 session security if the server supports it, - * otherwise fall back to LM and NTLM. */ - LM_and_NTLM_and_try_NTLMv2_Session, - - /* Use NTLMv2 session security if the server supports it, - * otherwise fall back to NTLM. Never use LM. */ - NTLM_only, - - /* Use NTLMv2 only. */ - NTLMv2_only, + ///+ /// Annotations are read-write. + /// + ReadWrite } } diff --git a/MailKit/AnnotationAttribute.cs b/MailKit/AnnotationAttribute.cs new file mode 100644 index 0000000000..a2654f243e --- /dev/null +++ b/MailKit/AnnotationAttribute.cs @@ -0,0 +1,257 @@ +// +// AnnotationAttribute.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; + +namespace MailKit { + /// + /// An annotation attribute. + /// + ///+ /// + public class AnnotationAttribute : IEquatableAn annotation attribute. + ///For more information about annotations, see + /// rfc5257. + ///+ { + static readonly char[] Wildcards = { '*', '%' }; + + /// + /// The annotation value. + /// + ///+ /// Used to get or set both the private and shared values of an annotation. + /// + public static readonly AnnotationAttribute Value = new AnnotationAttribute ("value", AnnotationScope.Both); + + ///+ /// The shared annotation value. + /// + ///+ /// Used to get or set the shared value of an annotation. + /// + public static readonly AnnotationAttribute SharedValue = new AnnotationAttribute ("value", AnnotationScope.Shared); + + ///+ /// The private annotation value. + /// + ///+ /// Used to get or set the private value of an annotation. + /// + public static readonly AnnotationAttribute PrivateValue = new AnnotationAttribute ("value", AnnotationScope.Private); + + ///+ /// The size of an annotation value. + /// + ///+ /// Used to get the size of the both the private and shared annotation values. + /// + public static readonly AnnotationAttribute Size = new AnnotationAttribute ("size", AnnotationScope.Both); + + ///+ /// The size of a shared annotation value. + /// + ///+ /// Used to get the size of a shared annotation value. + /// + public static readonly AnnotationAttribute SharedSize = new AnnotationAttribute ("size", AnnotationScope.Shared); + + ///+ /// The size of a private annotation value. + /// + ///+ /// Used to get the size of a private annotation value. + /// + public static readonly AnnotationAttribute PrivateSize = new AnnotationAttribute ("size", AnnotationScope.Private); + + AnnotationAttribute (string name, AnnotationScope scope) + { + switch (scope) { + case AnnotationScope.Shared: Specifier = string.Format ("{0}.shared", name); break; + case AnnotationScope.Private: Specifier = string.Format ("{0}.priv", name); break; + default: Specifier = name; break; + } + Scope = scope; + Name = name; + } + + ///+ /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The annotation attribute specifier. + ///. + /// + /// + ///is . + /// + /// + public AnnotationAttribute (string specifier) + { + if (specifier == null) + throw new ArgumentNullException (nameof (specifier)); + + if (specifier.Length == 0) + throw new ArgumentException ("Annotation attribute specifiers cannot be empty.", nameof (specifier)); + + // TODO: improve validation + if (specifier.IndexOfAny (Wildcards) != -1) + throw new ArgumentException ("Annotation attribute specifiers cannot contain '*' or '%'.", nameof (specifier)); + + Specifier = specifier; + + if (specifier.EndsWith (".shared", StringComparison.Ordinal)) { + Name = specifier.Substring (0, specifier.Length - ".shared".Length); + Scope = AnnotationScope.Shared; + } else if (specifier.EndsWith (".priv", StringComparison.Ordinal)) { + Name = specifier.Substring (0, specifier.Length - ".priv".Length); + Scope = AnnotationScope.Private; + } else { + Scope = AnnotationScope.Both; + Name = specifier; + } + } + + ///contains illegal characters. + /// + /// Get the name of the annotation attribute. + /// + ///+ /// Gets the name of the annotation attribute. + /// + ///The name of the annotation attribute. + public string Name { + get; private set; + } + + ///+ /// Get the scope of the annotation attribute. + /// + ///+ /// Gets the scope of the annotation attribute. + /// + ///The scope of the annotation attribute. + public AnnotationScope Scope { + get; private set; + } + + ///+ /// Get the annotation attribute specifier. + /// + ///+ /// Gets the annotation attribute specifier. + /// + ///The annotation attribute specifier. + public string Specifier { + get; private set; + } + + #region IEquatable implementation + + ///+ /// Determines whether the specified + ///is equal to the current . + /// + /// Determines whether the specified + /// Theis equal to the current . + /// to compare with the current . + /// + public bool Equals (AnnotationAttribute? other) + { + return other?.Specifier == Specifier; + } + + #endregion + + /// if the specified is equal to the current + /// ; otherwise, . + /// Determines whether two annotation attributes are equal. + /// + ///+ /// Determines whether two annotation attributes are equal. + /// + ///+ /// The first annotation attribute to compare. + /// The second annotation attribute to compare. + public static bool operator == (AnnotationAttribute attr1, AnnotationAttribute attr2) + { + return attr1?.Specifier == attr2?.Specifier; + } + + /// if and are equal; otherwise, . + /// Determines whether two annotation attributes are not equal. + /// + ///+ /// Determines whether two annotation attributes are not equal. + /// + ///+ /// The first annotation attribute to compare. + /// The second annotation attribute to compare. + public static bool operator != (AnnotationAttribute attr1, AnnotationAttribute attr2) + { + return attr1?.Specifier != attr2?.Specifier; + } + + /// if and are not equal; otherwise, . + /// Determine whether the specified + ///is equal to the current . + /// + /// Determines whether the specified + /// Theis equal to the current . + /// to compare with the current . + /// + public override bool Equals (object? obj) + { + return obj is AnnotationAttribute attribute && attribute.Specifier == Specifier; + } + + /// if the specified is equal to the current + /// ; otherwise, . + /// Serves as a hash function for a + ///object. + /// + /// Serves as a hash function for a + ///object. + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + return Specifier.GetHashCode (); + } + + ///+ /// Returns a + ///that represents the current . + /// + /// Returns a + ///that represents the current . + /// A + public override string ToString () + { + return Specifier; + } + } +} diff --git a/MailKit/AnnotationEntry.cs b/MailKit/AnnotationEntry.cs new file mode 100644 index 0000000000..d1f2a14f23 --- /dev/null +++ b/MailKit/AnnotationEntry.cs @@ -0,0 +1,526 @@ +// +// AnnotationEntry.cs +// +// Author: Jeffrey Stedfastthat represents the current . +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; + +namespace MailKit { + /// + /// An annotation entry. + /// + ///+ /// + public class AnnotationEntry : IEquatableAn annotation entry. + ///For more information about annotations, see + /// rfc5257. + ///+ { + /// + /// An annotation entry for a comment on a message. + /// + ///+ /// Used to get or set a comment on a message. + /// + public static readonly AnnotationEntry Comment = new AnnotationEntry ("/comment", AnnotationScope.Both); + + ///+ /// An annotation entry for a private comment on a message. + /// + ///+ /// Used to get or set a private comment on a message. + /// + public static readonly AnnotationEntry PrivateComment = new AnnotationEntry ("/comment", AnnotationScope.Private); + + ///+ /// An annotation entry for a shared comment on a message. + /// + ///+ /// Used to get or set a shared comment on a message. + /// + public static readonly AnnotationEntry SharedComment = new AnnotationEntry ("/comment", AnnotationScope.Shared); + + ///+ /// An annotation entry for flags on a message. + /// + ///+ /// Used to get or set flags on a message. + /// + public static readonly AnnotationEntry Flags = new AnnotationEntry ("/flags", AnnotationScope.Both); + + ///+ /// An annotation entry for private flags on a message. + /// + ///+ /// Used to get or set private flags on a message. + /// + public static readonly AnnotationEntry PrivateFlags = new AnnotationEntry ("/flags", AnnotationScope.Private); + + ///+ /// Aa annotation entry for shared flags on a message. + /// + ///+ /// Used to get or set shared flags on a message. + /// + public static readonly AnnotationEntry SharedFlags = new AnnotationEntry ("/flags", AnnotationScope.Shared); + + ///+ /// An annotation entry for an alternate subject on a message. + /// + ///+ /// Used to get or set an alternate subject on a message. + /// + public static readonly AnnotationEntry AltSubject = new AnnotationEntry ("/altsubject", AnnotationScope.Both); + + ///+ /// An annotation entry for a private alternate subject on a message. + /// + ///+ /// Used to get or set a private alternate subject on a message. + /// + public static readonly AnnotationEntry PrivateAltSubject = new AnnotationEntry ("/altsubject", AnnotationScope.Private); + + ///+ /// An annotation entry for a shared alternate subject on a message. + /// + ///+ /// Used to get or set a shared alternate subject on a message. + /// + public static readonly AnnotationEntry SharedAltSubject = new AnnotationEntry ("/altsubject", AnnotationScope.Shared); + + static void ValidatePath (string path) + { + if (path == null) + throw new ArgumentNullException (nameof (path)); + + if (path.Length == 0) + throw new ArgumentException ("Annotation entry paths cannot be empty.", nameof (path)); + + if (path[0] != '/' && path[0] != '*' && path[0] != '%') + throw new ArgumentException ("Annotation entry paths must begin with '/'.", nameof (path)); + + if (path.Length > 1 && path[1] >= '0' && path[1] <= '9') + throw new ArgumentException ("Annotation entry paths must not include a part-specifier.", nameof (path)); + + if (path == "*" || path == "%") + return; + + char pc = path[0]; + + for (int i = 1; i < path.Length; i++) { + char c = path[i]; + + if (c > 127) + throw new ArgumentException ($"Invalid character in annotation entry path: '{c}'.", nameof (path)); + + if (c >= '0' && c <= '9' && pc == '/') + throw new ArgumentException ("Invalid annotation entry path.", nameof (path)); + + if ((pc == '/' || pc == '.') && (c == '/' || c == '.')) + throw new ArgumentException ("Invalid annotation entry path.", nameof (path)); + + pc = c; + } + + int endIndex = path.Length - 1; + + if (path[endIndex] == '/') + throw new ArgumentException ("Annotation entry paths must not end with '/'.", nameof (path)); + + if (path[endIndex] == '.') + throw new ArgumentException ("Annotation entry paths must not end with '.'.", nameof (path)); + } + + static void ValidatePartSpecifier (string partSpecifier) + { + if (partSpecifier == null) + throw new ArgumentNullException (nameof (partSpecifier)); + + char pc = '\0'; + + for (int i = 0; i < partSpecifier.Length; i++) { + char c = partSpecifier[i]; + + if (!((c >= '0' && c <= '9') || c == '.') || (c == '.' && (pc == '.' || pc == '\0'))) + throw new ArgumentException ("Invalid part-specifier.", nameof (partSpecifier)); + + pc = c; + } + + if (pc == '.') + throw new ArgumentException ("Invalid part-specifier.", nameof (partSpecifier)); + } + + AnnotationEntry (string? partSpecifier, string entry, string path, AnnotationScope scope) + { + PartSpecifier = partSpecifier; + Entry = entry; + Path = path; + Scope = scope; + } + + ///+ /// Initializes a new instance of the + ///struct. + /// + /// Creates a new + /// The annotation entry path. + /// The scope of the annotation. + ///. + /// + /// + ///is . + /// + /// + public AnnotationEntry (string path, AnnotationScope scope = AnnotationScope.Both) + { + ValidatePath (path); + + switch (scope) { + case AnnotationScope.Private: Entry = path + ".priv"; break; + case AnnotationScope.Shared: Entry = path + ".shared"; break; + default: Entry = path; break; + } + + PartSpecifier = null; + Path = path; + Scope = scope; + } + + ///is invalid. + /// + /// Initializes a new instance of the + ///struct. + /// + /// Creates a new + /// The part-specifier of the body part of the message. + /// The annotation entry path. + /// The scope of the annotation. + ///for an individual body part of a message. + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// + public AnnotationEntry (string partSpecifier, string path, AnnotationScope scope = AnnotationScope.Both) + { + ValidatePartSpecifier (partSpecifier); + ValidatePath (path); + + switch (scope) { + case AnnotationScope.Private: Entry = string.Format ("/{0}{1}.priv", partSpecifier, path); break; + case AnnotationScope.Shared: Entry = string.Format ("/{0}{1}.shared", partSpecifier, path); break; + default: Entry = string.Format ("/{0}{1}", partSpecifier, path); break; + } + + PartSpecifier = partSpecifier; + Path = path; + Scope = scope; + } + + ///+ /// is invalid. -or- + ///+ /// is invalid. + /// Initializes a new instance of the + ///struct. + /// + /// Creates a new + /// The body part of the message. + /// The annotation entry path. + /// The scope of the annotation. + ///for an individual body part of a message. + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// + public AnnotationEntry (BodyPart part, string path, AnnotationScope scope = AnnotationScope.Both) + { + if (part == null) + throw new ArgumentNullException (nameof (part)); + + ValidatePath (path); + + switch (scope) { + case AnnotationScope.Private: Entry = string.Format ("/{0}{1}.priv", part.PartSpecifier, path); break; + case AnnotationScope.Shared: Entry = string.Format ("/{0}{1}.shared", part.PartSpecifier, path); break; + default: Entry = string.Format ("/{0}{1}", part.PartSpecifier, path); break; + } + + PartSpecifier = part.PartSpecifier; + Path = path; + Scope = scope; + } + + ///is invalid. + /// + /// Get the annotation entry specifier. + /// + ///+ /// Gets the annotation entry specifier. + /// + ///The annotation entry specifier. + public string Entry { + get; private set; + } + + ///+ /// Get the part-specifier component of the annotation entry. + /// + ///+ /// Gets the part-specifier component of the annotation entry. + /// + ///The part-specifier. + public string? PartSpecifier { + get; private set; + } + + ///+ /// Get the path component of the annotation entry. + /// + ///+ /// Gets the path component of the annotation entry. + /// + ///The path. + public string Path { + get; private set; + } + + ///+ /// Get the scope of the annotation. + /// + ///+ /// Gets the scope of the annotation. + /// + ///The scope. + public AnnotationScope Scope { + get; private set; + } + + #region IEquatable implementation + + ///+ /// Determines whether the specified + ///is equal to the current . + /// + /// Determines whether the specified + /// Theis equal to the current . + /// to compare with the current . + /// + public bool Equals (AnnotationEntry? other) + { + return other?.Entry == Entry; + } + + #endregion + + /// if the specified is equal to the current + /// ; otherwise, . + /// Determines whether two annotation entries are equal. + /// + ///+ /// Determines whether two annotation entries are equal. + /// + ///+ /// The first annotation entry to compare. + /// The second annotation entry to compare. + public static bool operator == (AnnotationEntry entry1, AnnotationEntry entry2) + { + return entry1?.Entry == entry2?.Entry; + } + + /// if and are equal; otherwise, . + /// Determines whether two annotation entries are not equal. + /// + ///+ /// Determines whether two annotation entries are not equal. + /// + ///+ /// The first annotation entry to compare. + /// The second annotation entry to compare. + public static bool operator != (AnnotationEntry entry1, AnnotationEntry entry2) + { + return entry1?.Entry != entry2?.Entry; + } + + /// if and are not equal; otherwise, . + /// Determine whether the specified + ///is equal to the current . + /// + /// Determines whether the specified + /// Theis equal to the current . + /// to compare with the current . + /// + public override bool Equals (object? obj) + { + return obj is AnnotationEntry entry && entry.Entry == Entry; + } + + /// if the specified is equal to the current + /// ; otherwise, . + /// Serves as a hash function for a + ///object. + /// + /// Serves as a hash function for a + ///object. + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. + public override int GetHashCode () + { + return Entry.GetHashCode (); + } + + ///+ /// Returns a + ///that represents the current . + /// + /// Returns a + ///that represents the current . + /// A + public override string ToString () + { + return Entry; + } + + ///that represents the current . + /// Parse an annotation entry. + /// + ///+ /// Parses an annotation entry. + /// + /// The annotation entry. + ///The parsed annotation entry. + ///+ /// + ///is . + /// + /// + public static AnnotationEntry Parse (string entry) + { + if (entry == null) + throw new ArgumentNullException (nameof (entry)); + + if (entry.Length == 0) + throw new FormatException ("An annotation entry cannot be empty."); + + if (entry[0] != '/' && entry[0] != '*' && entry[0] != '%') + throw new FormatException ("An annotation entry must begin with a '/' character."); + + var scope = AnnotationScope.Both; + int startIndex = 0, endIndex; + string? partSpecifier = null; + var component = 0; + var pc = entry[0]; + string path; + + for (int i = 1; i < entry.Length; i++) { + char c = entry[i]; + + if (c >= '0' && c <= '9' && pc == '/') { + if (component > 0) + throw new FormatException ("Invalid annotation entry."); + + startIndex = i; + endIndex = i + 1; + pc = c; + + while (endIndex < entry.Length) { + c = entry[endIndex]; + + if (c == '/') { + if (pc == '.') + throw new FormatException ("Invalid part-specifier in annotation entry."); + + break; + } + + if (!(c >= '0' && c <= '9') && c != '.') + throw new FormatException ($"Invalid character in part-specifier: '{c}'."); + + if (c == '.' && pc == '.') + throw new FormatException ("Invalid part-specifier in annotation entry."); + + endIndex++; + pc = c; + } + + if (endIndex >= entry.Length) + throw new FormatException ("Incomplete part-specifier in annotation entry."); + + partSpecifier = entry.Substring (startIndex, endIndex - startIndex); + i = startIndex = endIndex; + component++; + } else if (c == '/' || c == '.') { + if (pc == '/' || pc == '.') + throw new FormatException ("Invalid annotation entry path."); + + if (c == '/') + component++; + } else if (c > 127) { + throw new FormatException ($"Invalid character in annotation entry path: '{c}'."); + } + + pc = c; + } + + if (pc == '/' || pc == '.') + throw new FormatException ("Invalid annotation entry path."); + + if (entry.EndsWith (".shared", StringComparison.Ordinal)) { + endIndex = entry.Length - ".shared".Length; + scope = AnnotationScope.Shared; + } else if (entry.EndsWith (".priv", StringComparison.Ordinal)) { + endIndex = entry.Length - ".priv".Length; + scope = AnnotationScope.Private; + } else { + endIndex = entry.Length; + } + + path = entry.Substring (startIndex, endIndex - startIndex); + + return new AnnotationEntry (partSpecifier, entry, path, scope); + } + + internal static AnnotationEntry Create (string entry) + { + switch (entry) { + case "/comment": return Comment; + case "/comment.priv": return PrivateComment; + case "/comment.shared": return SharedComment; + case "/flags": return Flags; + case "/flags.priv": return PrivateFlags; + case "/flags.shared": return SharedFlags; + case "/altsubject": return AltSubject; + case "/altsubject.priv": return PrivateAltSubject; + case "/altsubject.shared": return SharedAltSubject; + default: return Parse (entry); + } + } + } +} diff --git a/MailKit/MessagesArrivedEventArgs.cs b/MailKit/AnnotationScope.cs similarity index 61% rename from MailKit/MessagesArrivedEventArgs.cs rename to MailKit/AnnotationScope.cs index 82fafbeee8..d2c84fbb27 100644 --- a/MailKit/MessagesArrivedEventArgs.cs +++ b/MailKit/AnnotationScope.cs @@ -1,9 +1,9 @@ -// -// MessagesArrivedEventArgs.cs +// +// AnnotationScope.cs // // Author: Jeffrey Stedfastdoes not conform to the annotation entry syntax. + /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -28,34 +28,34 @@ namespace MailKit { /// - /// Event args used when messages arrive in a folder. + /// The scope of an annotation. /// ///- /// Event args used when messages arrive in a folder. + /// - public class MessagesArrivedEventArgs : EventArgs + [Flags] + public enum AnnotationScope { ///Represents the scope of an annotation. + ///For more information about annotations, see + /// rfc5257. ///- /// Initializes a new instance of the + None, + + ///class. + /// No scopes. + /// + /// The private annotation scope. + /// + Private, + + ///+ /// The shared annotation scope. /// - ///- /// Creates a new - /// The number of messages that just arrived. - public MessagesArrivedEventArgs (int count) - { - Count = count; - } + Shared, ///. - /// - /// Get the number of messages that just arrived in the folder. + /// Both private and shared scopes. /// - ///- /// Gets the number of messages that just arrived in the folder. - /// - ///The count. - public int Count { - get; private set; - } + Both = Private | Shared } } diff --git a/MailKit/AnnotationsChangedEventArgs.cs b/MailKit/AnnotationsChangedEventArgs.cs new file mode 100644 index 0000000000..25144ad860 --- /dev/null +++ b/MailKit/AnnotationsChangedEventArgs.cs @@ -0,0 +1,82 @@ +// +// AnnotationsChangedEventArgs.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace MailKit { + /// + /// Event args used when an annotation changes. + /// + ///+ /// Event args used when an annotation changes. + /// + public class AnnotationsChangedEventArgs : MessageEventArgs + { + ///+ /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The message index. + /// The annotations that changed. + ///. + /// + /// + public AnnotationsChangedEventArgs (int index, IEnumerableis . + /// annotations) : base (index) + { + if (annotations == null) + throw new ArgumentNullException (nameof (annotations)); + + Annotations = new ReadOnlyCollection (annotations.ToArray ()); + } + + /// + /// Get the annotations that changed. + /// + ///+ /// Gets the annotations that changed. + /// + ///The annotation. + public IListAnnotations { + get; internal set; + } + + /// + /// Gets the updated mod-sequence value of the message, if available. + /// + ///+ /// Gets the updated mod-sequence value of the message, if available. + /// + ///The mod-sequence value. + public ulong? ModSeq { + get; internal set; + } + } +} diff --git a/MailKit/AppendRequest.cs b/MailKit/AppendRequest.cs new file mode 100644 index 0000000000..af3d9457a4 --- /dev/null +++ b/MailKit/AppendRequest.cs @@ -0,0 +1,210 @@ +// +// AppendRequest.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +using MimeKit; + +namespace MailKit { + /// + /// A request for appending a message to a folder. + /// + ///+ /// A request for appending a message to a folder. + /// + public class AppendRequest : IAppendRequest + { + ///+ /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The message. + /// The message flags. + ///. + /// + /// + public AppendRequest (MimeMessage message, MessageFlags flags = MessageFlags.None) + { + if (message == null) + throw new ArgumentNullException (nameof (message)); + + Message = message; + Flags = flags; + } + + ///is . + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The message. + /// The message flags. + /// The message keywords. + ///. + /// + /// + public AppendRequest (MimeMessage message, MessageFlags flags, IEnumerable+ /// is . -or- + ///+ /// is . keywords) + { + if (message == null) + throw new ArgumentNullException (nameof (message)); + + if (keywords == null) + throw new ArgumentNullException (nameof (keywords)); + + Keywords = keywords as ISet ?? new HashSet (keywords); + Message = message; + Flags = flags; + } + + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The message. + /// The message flags. + /// The internal date of the message. + ///. + /// + /// + public AppendRequest (MimeMessage message, MessageFlags flags, DateTimeOffset internalDate) + { + if (message == null) + throw new ArgumentNullException (nameof (message)); + + Message = message; + Flags = flags; + InternalDate = internalDate; + } + + ///is . + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The message. + /// The message flags. + /// The message keywords. + /// The internal date of the message. + ///. + /// + /// + public AppendRequest (MimeMessage message, MessageFlags flags, IEnumerable+ /// is . -or- + ///+ /// is . keywords, DateTimeOffset internalDate) + { + if (message == null) + throw new ArgumentNullException (nameof (message)); + + if (keywords == null) + throw new ArgumentNullException (nameof (keywords)); + + Keywords = keywords as ISet ?? new HashSet (keywords); + Message = message; + Flags = flags; + InternalDate = internalDate; + } + + /// + /// Get the message that should be appended to the folder. + /// + ///+ /// Gets the message that should be appended to the folder. + /// + ///The message. + public MimeMessage Message { + get; private set; + } + + ///+ /// Get or set the message flags that should be set on the message. + /// + ///+ /// Gets or sets the message flags that should be set on the message. + /// + ///The message flags. + public MessageFlags Flags { + get; set; + } + + ///+ /// Get or set the keywords that should be set on the message. + /// + ///+ /// Gets or sets the keywords that should be set on the message. + /// + ///The keywords. + public ISet? Keywords { + get; set; + } + + /// + /// Get or set the timestamp that should be used by folder as the + ///. + /// + /// Gets or sets the timestamp that should be used by folder as the + ///. + /// The date and time to use for the INTERNALDATE or + public DateTimeOffset? InternalDate { + get; set; + } + + ///if it should be left up to the folder to decide. + /// Get or set the list of annotations that should be set on the message. + /// + ///+ /// + ///Gets or sets the list of annotations that should be set on the message. + ///+ /// + ///This feature is not supported by all folders. + ///Use + ///with the enum value + /// to determine if this feature is supported. The list of annotations. + public IList? Annotations { + get; set; + } + + /// + /// Get or set the transfer progress reporting mechanism. + /// + ///+ /// Gets or sets the transfer progress reporting mechanism. + /// + ///The transfer progress mechanism. + public ITransferProgress? TransferProgress { + get; set; + } + } +} diff --git a/MailKit/AuthenticatedEventArgs.cs b/MailKit/AuthenticatedEventArgs.cs index ee91f04624..e458464474 100644 --- a/MailKit/AuthenticatedEventArgs.cs +++ b/MailKit/AuthenticatedEventArgs.cs @@ -1,9 +1,9 @@ -// +// // AuthenticatedEventArgs.cs // // Author: Jeffrey Stedfast// -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -43,8 +43,14 @@ public class AuthenticatedEventArgs : EventArgs /// Creates a new . /// /// The free-form text. + /// + /// public AuthenticatedEventArgs (string message) { + if (message == null) + throw new ArgumentNullException (nameof (message)); + Message = message; } diff --git a/MailKit/BodyPart.cs b/MailKit/BodyPart.cs index 3b3ae0e02e..f66785c04b 100644 --- a/MailKit/BodyPart.cs +++ b/MailKit/BodyPart.cs @@ -1,9 +1,9 @@ -// +// // BodyPart.cs // // Author: Jeffrey Stedfastis . + /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,9 @@ using System; using System.Text; +using System.Globalization; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using MimeKit; using MimeKit.Utils; @@ -41,7 +43,7 @@ namespace MailKit { /// . /// /// - /// public abstract class BodyPart { @@ -51,8 +53,36 @@ public abstract class BodyPart ///+ ///////// Creates a new + [Obsolete ("Use BodyPart (ContentType, string) instead.")] protected BodyPart () { + ContentType = new ContentType ("application", "octet-stream"); + PartSpecifier = string.Empty; + } + + ///. /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The content type. + /// The part specifier. + ///. + /// + /// + protected BodyPart (ContentType contentType, string partSpecifier) + { + if (contentType == null) + throw new ArgumentNullException (nameof (contentType)); + + if (partSpecifier == null) + throw new ArgumentNullException (nameof (partSpecifier)); + + ContentType = contentType; + PartSpecifier = partSpecifier; } ///+ /// is . -or- + ///+ /// is . @@ -73,7 +103,7 @@ public ContentType ContentType { /// Gets the part specifier. /// /// ///- /// ///+ //////The part specifier. public string PartSpecifier { @@ -93,38 +123,32 @@ public string PartSpecifier { /// /// The visitor. ///- /// - public virtual void Accept (BodyPartVisitor visitor) - { - if (visitor == null) - throw new ArgumentNullException (nameof (visitor)); - - visitor.VisitBodyPart (this); - } + public abstract void Accept (BodyPartVisitor visitor); internal static void Encode (StringBuilder builder, uint value) { - builder.Append (value.ToString ()); + builder.Append (value.ToString (CultureInfo.InvariantCulture)); } - internal static void Encode (StringBuilder builder, string value) + internal static void Encode (StringBuilder builder, string? value) { if (value != null) - builder.Append (MimeUtils.Quote (value)); + MimeUtils.AppendQuoted (builder, value); else builder.Append ("NIL"); } - internal static void Encode (StringBuilder builder, Uri location) + internal static void Encode (StringBuilder builder, Uri? location) { if (location != null) - builder.Append (MimeUtils.Quote (location.ToString ())); + MimeUtils.AppendQuoted (builder, location.ToString ()); else builder.Append ("NIL"); } - internal static void Encode (StringBuilder builder, string[] values) + internal static void Encode (StringBuilder builder, string[]? values) { if (values == null || values.Length == 0) { builder.Append ("NIL"); @@ -164,7 +188,7 @@ internal static void Encode (StringBuilder builder, IListis null . + ///is . /// parameters) builder.Append (')'); } - internal static void Encode (StringBuilder builder, ContentDisposition disposition) + internal static void Encode (StringBuilder builder, ContentDisposition? disposition) { if (disposition == null) { builder.Append ("NIL"); @@ -202,7 +226,7 @@ internal static void Encode (StringBuilder builder, BodyPartCollection parts) } } - internal static void Encode (StringBuilder builder, Envelope envelope) + internal static void Encode (StringBuilder builder, Envelope? envelope) { if (envelope == null) { builder.Append ("NIL"); @@ -212,7 +236,7 @@ internal static void Encode (StringBuilder builder, Envelope envelope) envelope.Encode (builder); } - internal static void Encode (StringBuilder builder, BodyPart body) + internal static void Encode (StringBuilder builder, BodyPart? body) { if (body == null) { builder.Append ("NIL"); @@ -253,6 +277,11 @@ public override string ToString () return builder.ToString (); } + static bool IsNIL (string text, int index) + { + return string.Compare (text, index, "NIL", 0, 3, StringComparison.Ordinal) == 0; + } + static bool TryParse (string text, ref int index, out uint value) { while (index < text.Length && text[index] == ' ') @@ -268,7 +297,7 @@ static bool TryParse (string text, ref int index, out uint value) return index > startIndex; } - static bool TryParse (string text, ref int index, out string nstring) + static bool TryParse (string text, ref int index, out string? nstring) { nstring = null; @@ -279,7 +308,7 @@ static bool TryParse (string text, ref int index, out string nstring) return false; if (text[index] != '"') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { index += 3; return true; } @@ -316,7 +345,7 @@ static bool TryParse (string text, ref int index, out string nstring) return true; } - static bool TryParse (string text, ref int index, out string[] values) + static bool TryParse (string text, ref int index, out string[]? values) { values = null; @@ -327,7 +356,7 @@ static bool TryParse (string text, ref int index, out string[] values) return false; if (text[index] != '(') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { index += 3; return true; } @@ -341,33 +370,32 @@ static bool TryParse (string text, ref int index, out string[] values) return false; var list = new List (); - string value; do { if (text[index] == ')') break; - if (!TryParse (text, ref index, out value)) + if (!TryParse (text, ref index, out string? value)) return false; - list.Add (value); + if (value != null) + list.Add (value); } while (index < text.Length); if (index >= text.Length || text[index] != ')') return false; + values = list.ToArray (); index++; return true; } - static bool TryParse (string text, ref int index, out Uri uri) + static bool TryParse (string text, ref int index, out Uri? uri) { - string nstring; - uri = null; - if (!TryParse (text, ref index, out nstring)) + if (!TryParse (text, ref index, out string? nstring)) return false; if (!string.IsNullOrEmpty (nstring)) { @@ -380,10 +408,8 @@ static bool TryParse (string text, ref int index, out Uri uri) return true; } - static bool TryParse (string text, ref int index, out IList parameters) + static bool TryParse (string text, ref int index, [NotNullWhen (true)] out IList ? parameters) { - string name, value; - parameters = null; while (index < text.Length && text[index] == ' ') @@ -393,7 +419,7 @@ static bool TryParse (string text, ref int index, out IList parameter return false; if (text[index] != '(') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { parameters = new List (); index += 3; return true; @@ -413,10 +439,10 @@ static bool TryParse (string text, ref int index, out IList parameter if (text[index] == ')') break; - if (!TryParse (text, ref index, out name)) + if (!TryParse (text, ref index, out string? name) || name == null) return false; - if (!TryParse (text, ref index, out value)) + if (!TryParse (text, ref index, out string? value) || value == null) return false; parameters.Add (new Parameter (name, value)); @@ -430,11 +456,8 @@ static bool TryParse (string text, ref int index, out IList parameter return true; } - static bool TryParse (string text, ref int index, out ContentDisposition disposition) + static bool TryParse (string text, ref int index, out ContentDisposition? disposition) { - IList parameters; - string value; - disposition = null; while (index < text.Length && text[index] == ' ') @@ -444,7 +467,7 @@ static bool TryParse (string text, ref int index, out ContentDisposition disposi return false; if (text[index] != '(') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { index += 3; return true; } @@ -454,10 +477,10 @@ static bool TryParse (string text, ref int index, out ContentDisposition disposi index++; - if (!TryParse (text, ref index, out value)) + if (!TryParse (text, ref index, out string? value) || value == null) return false; - if (!TryParse (text, ref index, out parameters)) + if (!TryParse (text, ref index, out IList ? parameters)) return false; if (index >= text.Length || text[index] != ')') @@ -473,10 +496,9 @@ static bool TryParse (string text, ref int index, out ContentDisposition disposi return true; } - static bool TryParse (string text, ref int index, bool multipart, out ContentType contentType) + static bool TryParse (string text, ref int index, bool multipart, [NotNullWhen (true)] out ContentType? contentType) { - IList parameters; - string type, subtype; + string? type, subtype; contentType = null; @@ -496,7 +518,7 @@ static bool TryParse (string text, ref int index, bool multipart, out ContentTyp if (!TryParse (text, ref index, out subtype)) return false; - if (!TryParse (text, ref index, out parameters)) + if (!TryParse (text, ref index, out IList ? parameters)) return false; contentType = new ContentType (type ?? "application", subtype ?? "octet-stream"); @@ -507,18 +529,17 @@ static bool TryParse (string text, ref int index, bool multipart, out ContentTyp return true; } - static bool TryParse (string text, ref int index, string prefix, out IList children) + static bool TryParse (string text, ref int index, string prefix, [NotNullWhen (true)] out BodyPartCollection? bodyParts) { - BodyPart part; string path; int id = 1; - children = null; + bodyParts = null; if (index >= text.Length) return false; - children = new List (); + bodyParts = new BodyPartCollection (); do { if (text[index] != '(') @@ -526,27 +547,27 @@ static bool TryParse (string text, ref int index, string prefix, out IList = text.Length || text[index] != '(') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { index += 3; return true; } @@ -567,21 +588,21 @@ static bool TryParse (string text, ref int index, string path, out BodyPart part if (index >= text.Length) return false; - if (text[index] == '(') { + if (text[index] == '(' || IsNIL (text, index)) { var prefix = path.Length > 0 ? path + "." : string.Empty; - var multipart = new BodyPartMultipart (); - IList children; + BodyPartCollection? bodyParts = null; - if (!TryParse (text, ref index, prefix, out children)) - return false; - - foreach (var child in children) - multipart.BodyParts.Add (child); + if (text[index] == '(') { + if (!TryParse (text, ref index, prefix, out bodyParts)) + return false; + } else { + index += "NIL".Length; + } if (!TryParse (text, ref index, true, out contentType)) return false; - multipart.ContentType = contentType; + var multipart = new BodyPartMultipart (contentType, path, bodyParts ?? new BodyPartCollection ()); if (!TryParse (text, ref index, out disposition)) return false; @@ -600,21 +621,20 @@ static bool TryParse (string text, ref int index, string path, out BodyPart part part = multipart; } else { - BodyPartMessage message = null; - BodyPartText txt = null; - BodyPartBasic basic; + BodyPartMessage? message = null; + BodyPartText? txt = null; + BodyPartBasic? basic; + string? nstring; if (!TryParse (text, ref index, false, out contentType)) return false; if (contentType.IsMimeType ("message", "rfc822")) - basic = message = new BodyPartMessage (); + basic = message = new BodyPartMessage (contentType, path); else if (contentType.IsMimeType ("text", "*")) - basic = txt = new BodyPartText (); + basic = txt = new BodyPartText (contentType, path); else - basic = new BodyPartBasic (); - - basic.ContentType = contentType; + basic = new BodyPartBasic (contentType, path); if (!TryParse (text, ref index, out nstring)) return false; @@ -631,7 +651,7 @@ static bool TryParse (string text, ref int index, string path, out BodyPart part basic.ContentTransferEncoding = nstring; - if (!TryParse (text, ref index, out number)) + if (!TryParse (text, ref index, out uint number)) return false; basic.Octets = number; @@ -657,15 +677,12 @@ static bool TryParse (string text, ref int index, string path, out BodyPart part basic.ContentLocation = location; if (message != null) { - Envelope envelope; - BodyPart body; - - if (!Envelope.TryParse (text, ref index, out envelope)) + if (!Envelope.TryParse (text, ref index, out Envelope? envelope)) return false; message.Envelope = envelope; - if (!TryParse (text, ref index, path, out body)) + if (!TryParse (text, ref index, path, out BodyPart? body)) return false; message.Body = body; @@ -684,8 +701,6 @@ static bool TryParse (string text, ref int index, string path, out BodyPart part part = basic; } - part.PartSpecifier = path; - if (index >= text.Length || text[index] != ')') return false; @@ -702,13 +717,13 @@ static bool TryParse (string text, ref int index, string path, out BodyPart part /// This syntax, while similar to IMAP's BODYSTRUCTURE syntax, is not completely /// compatible. /// - ///+ /// true , if the body part was successfully parsed,false otherwise./// The text to parse. /// The parsed body part. /// if the body part was successfully parsed; otherwise, . - /// - public static bool TryParse (string text, out BodyPart part) + public static bool TryParse (string text, out BodyPart? part) { if (text == null) throw new ArgumentNullException (nameof (text)); diff --git a/MailKit/BodyPartBasic.cs b/MailKit/BodyPartBasic.cs index 454f4da96c..50c1ced90a 100644 --- a/MailKit/BodyPartBasic.cs +++ b/MailKit/BodyPartBasic.cs @@ -1,9 +1,9 @@ -// +// // BodyPartBasic.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -38,7 +38,7 @@ namespace MailKit { /// message/rfc822 part, or a text part. /// /// - /// public class BodyPartBasic : BodyPart { @@ -48,7 +48,25 @@ public class BodyPartBasic : BodyPart ///+ ///////// Creates a new - public BodyPartBasic () + [Obsolete ("Use BodyPartBasic (ContentType, string) instead.")] + public BodyPartBasic () : base () + { + } + + ///. /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The content type. + /// The part specifier. + ///. + /// + /// + public BodyPartBasic (ContentType contentType, string partSpecifier) : base (contentType, partSpecifier) { } @@ -59,7 +77,7 @@ public BodyPartBasic () /// Gets the Content-Id of the body part, if available. /// ///+ /// is . -or- + ///+ /// is . The content identifier. - public string ContentId { + public string? ContentId { get; set; } @@ -70,7 +88,7 @@ public string ContentId { /// Gets the Content-Description of the body part, if available. /// ///The content description. - public string ContentDescription { + public string? ContentDescription { get; set; } @@ -83,7 +101,7 @@ public string ContentDescription { /// method to parse this value into a usable. /// /// The content transfer encoding. - public string ContentTransferEncoding { + public string? ContentTransferEncoding { get; set; } @@ -91,7 +109,9 @@ public string ContentTransferEncoding { /// Gets the size of the body part, in bytes. ///- /// Gets the size of the body part, in bytes. + /// ///Gets the size of the body part, in bytes. + ///Note that this size is the size in its transfer encoding + /// and not the resulting size after any decoding. ///The number of octets. public uint Octets { @@ -105,7 +125,7 @@ public uint Octets { /// Gets the MD5 hash of the content, if available. /// ///The content md5. - public string ContentMd5 { + public string? ContentMd5 { get; set; } @@ -119,7 +139,7 @@ public string ContentMd5 { /// summary information from an. /// /// The content disposition. - public ContentDisposition ContentDisposition { + public ContentDisposition? ContentDisposition { get; set; } @@ -133,7 +153,7 @@ public ContentDisposition ContentDisposition { /// summary information from an. /// /// The content language. - public string[] ContentLanguage { + public string[]? ContentLanguage { get; set; } @@ -147,7 +167,7 @@ public string[] ContentLanguage { /// summary information from an. /// /// The content location. - public Uri ContentLocation { + public Uri? ContentLocation { get; set; } @@ -161,7 +181,7 @@ public Uri ContentLocation { /// is necessary to include theflag when /// fetching summary information from an . /// - /// + /// true if this part is an attachment; otherwise,false .public bool IsAttachment { get { return ContentDisposition != null && ContentDisposition.IsAttachment; } } @@ -177,17 +197,16 @@ public bool IsAttachment { /// fetching summary information from an if this part is an attachment; otherwise, . . /// /// The name of the file. - public string FileName { + public string? FileName { get { - string filename = null; + string? filename = null; if (ContentDisposition != null) filename = ContentDisposition.FileName; - if (filename == null) - filename = ContentType.Name; + filename ??= ContentType.Name; - return filename != null ? filename.Trim () : null; + return filename?.Trim (); } } @@ -204,7 +223,7 @@ public string FileName { /// /// The visitor. ///- /// public override void Accept (BodyPartVisitor visitor) { diff --git a/MailKit/BodyPartCollection.cs b/MailKit/BodyPartCollection.cs index d98852d663..2ac68f6e16 100644 --- a/MailKit/BodyPartCollection.cs +++ b/MailKit/BodyPartCollection.cs @@ -1,9 +1,9 @@ -// +// // BodyPartCollection.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -69,7 +69,7 @@ public int Count { /// /// Gets whether or not this body part collection is read only. /// - ///+ /// true if this collection is read only; otherwise,false .public bool IsReadOnly { get { return false; } } @@ -82,7 +82,7 @@ public bool IsReadOnly { /// /// The body part. /// if this collection is read only; otherwise, . - /// public void Add (BodyPart part) { @@ -109,10 +109,10 @@ public void Clear () ///is null . + ///is . /// /// Determines whether or not the collection contains the specified body part. /// - ///+ /// true if the specified body part exists; otherwisefalse ./// The body part. /// if the specified body part exists; otherwise, . - /// public bool Contains (BodyPart part) { @@ -132,7 +132,7 @@ public bool Contains (BodyPart part) /// The array. /// The array index. ///is null . + ///is . /// - /// ///is null . + ///is . /// /// is out of range. @@ -154,10 +154,10 @@ public void CopyTo (BodyPart[] array, int arrayIndex) /// /// Removes the specified body part. /// - ///+ /// true if the body part was removed; otherwisefalse ./// The body part. /// if the body part was removed; otherwise, . - /// public bool Remove (BodyPart part) { @@ -202,38 +202,33 @@ public BodyPart this [int index] { ///is null . + ///is . /// The index of the part matching the specified URI if found; otherwise /// The URI of the body part. ///-1 .- /// public int IndexOf (Uri uri) { if (uri == null) throw new ArgumentNullException (nameof (uri)); - bool cid = uri.IsAbsoluteUri && uri.Scheme.ToLowerInvariant () == "cid"; + bool cid = uri.IsAbsoluteUri && uri.Scheme.Equals ("cid", StringComparison.OrdinalIgnoreCase); for (int index = 0; index < Count; index++) { - var bodyPart = this[index] as BodyPartBasic; - - if (bodyPart == null) + if (this[index] is not BodyPartBasic bodyPart) continue; if (uri.IsAbsoluteUri) { if (cid) { if (!string.IsNullOrEmpty (bodyPart.ContentId)) { - var id = MimeUtils.EnumerateReferences (bodyPart.ContentId).FirstOrDefault (); + // Note: we might have a Content-Id in the form "is null . + ///is . /// ", so attempt to decode it + var id = MimeUtils.EnumerateReferences (bodyPart.ContentId!).FirstOrDefault () ?? bodyPart.ContentId; if (id == uri.AbsolutePath) return index; } } else if (bodyPart.ContentLocation != null) { - Uri absolute; - if (!bodyPart.ContentLocation.IsAbsoluteUri) continue; - absolute = bodyPart.ContentLocation; - - if (absolute == uri) + if (bodyPart.ContentLocation == uri) return index; } } else if (bodyPart.ContentLocation == uri) { diff --git a/MailKit/BodyPartMessage.cs b/MailKit/BodyPartMessage.cs index 094eecaaa2..3517812b6a 100644 --- a/MailKit/BodyPartMessage.cs +++ b/MailKit/BodyPartMessage.cs @@ -1,9 +1,9 @@ -// +// // BodyPartMessage.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,8 @@ using System; using System.Text; +using MimeKit; + namespace MailKit { /// /// A message/rfc822 body part. @@ -42,7 +44,25 @@ public class BodyPartMessage : BodyPartBasic /// /// Creates a new - public BodyPartMessage () + [Obsolete ("Use BodyPartMessage (ContentType, string) instead.")] + public BodyPartMessage () : this (new ContentType ("message", "rfc822"), string.Empty) + { + } + + ///. /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The content type. + /// The part specifier. + ///. + /// + /// + public BodyPartMessage (ContentType contentType, string partSpecifier) : base (contentType, partSpecifier) { } @@ -53,7 +73,7 @@ public BodyPartMessage () /// Gets the envelope of the message, if available. /// ///+ /// is . -or- + ///+ /// is . The envelope. - public Envelope Envelope { + public Envelope? Envelope { get; set; } @@ -64,7 +84,7 @@ public Envelope Envelope { /// Gets the body structure of the message. /// ///The body structure. - public BodyPart Body { + public BodyPart? Body { get; set; } @@ -92,7 +112,7 @@ public uint Lines { /// /// The visitor. ///- /// public override void Accept (BodyPartVisitor visitor) { diff --git a/MailKit/BodyPartMultipart.cs b/MailKit/BodyPartMultipart.cs index 8395b4e5b5..7e1a8df9ba 100644 --- a/MailKit/BodyPartMultipart.cs +++ b/MailKit/BodyPartMultipart.cs @@ -1,9 +1,9 @@ -// +// // BodyPartMultipart.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -44,11 +44,53 @@ public class BodyPartMultipart : BodyPart /// /// Creates a new - public BodyPartMultipart () + [Obsolete ("Use BodyPartMultipart (ContentType, string) instead.")] + public BodyPartMultipart () : this (new ContentType ("multipart", "mixed"), string.Empty) + { + } + + ///. /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The content type. + /// The part specifier. + ///. + /// + /// + public BodyPartMultipart (ContentType contentType, string partSpecifier) : base (contentType, partSpecifier) { BodyParts = new BodyPartCollection (); } + ///+ /// is . -or- + ///+ /// is . + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The content type. + /// The part specifier. + /// The child body parts of the multipart. + ///. + /// + /// + public BodyPartMultipart (ContentType contentType, string partSpecifier, BodyPartCollection bodyParts) : base (contentType, partSpecifier) + { + if (bodyParts is null) + throw new ArgumentNullException (nameof (bodyParts)); + + BodyParts = bodyParts; + } + ///+ /// is . -or- + ///+ /// is . -or- + ///+ /// is . /// Gets the child body parts. /// @@ -67,7 +109,7 @@ public BodyPartCollection BodyParts { /// Gets the Content-Disposition of the body part, if available. /// ///The content disposition. - public ContentDisposition ContentDisposition { + public ContentDisposition? ContentDisposition { get; set; } @@ -78,7 +120,7 @@ public ContentDisposition ContentDisposition { /// Gets the Content-Language of the body part, if available. /// ///The content language. - public string[] ContentLanguage { + public string[]? ContentLanguage { get; set; } @@ -89,7 +131,7 @@ public string[] ContentLanguage { /// Gets the Content-Location of the body part, if available. /// ///The content location. - public Uri ContentLocation { + public Uri? ContentLocation { get; set; } @@ -106,7 +148,7 @@ public Uri ContentLocation { /// /// The visitor. ///- /// public override void Accept (BodyPartVisitor visitor) { diff --git a/MailKit/BodyPartText.cs b/MailKit/BodyPartText.cs index 5dc1e88d2e..1924b94be9 100644 --- a/MailKit/BodyPartText.cs +++ b/MailKit/BodyPartText.cs @@ -1,9 +1,9 @@ -// +// // BodyPartText.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,8 @@ using System; using System.Text; +using MimeKit; + namespace MailKit { /// /// A textual body part. @@ -35,7 +37,7 @@ namespace MailKit { /// Represents any body part with a media type of "text". /// /// - /// public class BodyPartText : BodyPartBasic { @@ -45,7 +47,25 @@ public class BodyPartText : BodyPartBasic ///+ ///////// Creates a new - public BodyPartText () + [Obsolete ("Use BodyPartText (ContentType, string) instead.")] + public BodyPartText () : this (new ContentType ("text", "plain"), string.Empty) + { + } + + ///. /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The content type. + /// The part specifier. + ///. + /// + /// + public BodyPartText (ContentType contentType, string partSpecifier) : base (contentType, partSpecifier) { } @@ -55,7 +75,7 @@ public BodyPartText () ///+ /// is . -or- + ///+ /// is . /// Checks whether or not the text part's Content-Type is text/plain. /// - ///+ /// true if the text is html; otherwise,false .public bool IsPlain { get { return ContentType.IsMimeType ("text", "plain"); } } @@ -66,7 +86,7 @@ public bool IsPlain { /// if the text is html; otherwise, . /// Checks whether or not the text part's Content-Type is text/html. /// - ///+ /// true if the text is html; otherwise,false .public bool IsHtml { get { return ContentType.IsMimeType ("text", "html"); } } @@ -95,7 +115,7 @@ public uint Lines { /// /// The visitor. /// if the text is html; otherwise, . - /// public override void Accept (BodyPartVisitor visitor) { diff --git a/MailKit/BodyPartVisitor.cs b/MailKit/BodyPartVisitor.cs index 8d896c5d98..78160968b2 100644 --- a/MailKit/BodyPartVisitor.cs +++ b/MailKit/BodyPartVisitor.cs @@ -1,9 +1,9 @@ -// +// // BodyPartVisitor.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -34,6 +34,16 @@ namespace MailKit { /// public abstract class BodyPartVisitor { + /// + /// Initialize a new instance of the + ///class. + /// + /// Creates a new + protected BodyPartVisitor () + { + } + ///. + /// /// Dispatches the entity to one of the more specialized visit methods in this class. /// @@ -43,8 +53,7 @@ public abstract class BodyPartVisitor /// The MIME body part. public virtual void Visit (BodyPart body) { - if (body != null) - body.Accept (this); + body?.Accept (this); } ///@@ -79,8 +88,7 @@ protected internal virtual void VisitBodyPartBasic (BodyPartBasic entity) /// The body part representing the message/rfc822 message. protected virtual void VisitMessage (BodyPart message) { - if (message != null) - message.Accept (this); + message.Accept (this); } /// @@ -93,7 +101,9 @@ protected virtual void VisitMessage (BodyPart message) protected internal virtual void VisitBodyPartMessage (BodyPartMessage entity) { VisitBodyPartBasic (entity); - VisitMessage (entity.Body); + + if (entity.Body != null) + VisitMessage (entity.Body); } /// diff --git a/MailKit/ByteArrayBuilder.cs b/MailKit/ByteArrayBuilder.cs new file mode 100644 index 0000000000..a22b7a3994 --- /dev/null +++ b/MailKit/ByteArrayBuilder.cs @@ -0,0 +1,182 @@ +// +// ByteArrayBuilder.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Text; +using System.Buffers; + +namespace MailKit +{ + class ByteArrayBuilder : IDisposable + { + byte[] buffer; + int length; + + public ByteArrayBuilder (int initialCapacity) + { + buffer = ArrayPool .Shared.Rent (initialCapacity); + length = 0; + } + + public int Length { + get { return length; } + } + + void EnsureCapacity (int capacity) + { + if (capacity > buffer.Length) { + var resized = ArrayPool .Shared.Rent (capacity); + Buffer.BlockCopy (buffer, 0, resized, 0, length); + ArrayPool .Shared.Return (buffer); + buffer = resized; + } + } + + public void Append (byte c) + { + EnsureCapacity (length + 1); + buffer[length++] = c; + } + + public void Append (byte[] text, int startIndex, int count) + { + EnsureCapacity (length + count); + Buffer.BlockCopy (text, startIndex, buffer, length, count); + length += count; + } + + public void Clear () + { + length = 0; + } + + public byte[] ToArray () + { + var array = new byte[length]; + + Buffer.BlockCopy (buffer, 0, array, 0, length); + + return array; + } + + public string ToString (Encoding encoding, Encoding fallback) + { + try { + return encoding.GetString (buffer, 0, length); + } catch (DecoderFallbackException) { + return fallback.GetString (buffer, 0, length); + } + } + + public override string ToString () + { + return ToString (TextEncodings.UTF8, TextEncodings.Latin1); + } + + public bool Equals (string value, bool ignoreCase = false) + { + if (length == value.Length) { + if (ignoreCase) { + for (int i = 0; i < length; i++) { + uint a = (uint) buffer[i]; + uint b = (uint) value[i]; + + if ((a - 'a') <= 'z' - 'a') + a -= 0x20; + if ((b - 'a') <= 'z' - 'a') + b -= 0x20; + + if (a != b) + return false; + } + } else { + for (int i = 0; i < length; i++) { + if (value[i] != (char) buffer[i]) + return false; + } + } + + return true; + } + + return false; + } + + public void TrimNewLine () + { + // Trim the sequence from the end of the line. + if (length > 0 && buffer[length - 1] == (byte) '\n') { + length--; + + if (length > 0 && buffer[length - 1] == (byte) '\r') + length--; + } + } + + // FIXME: This should be moved somewhere else... + internal static bool TryParse (byte[] text, ref int index, int endIndex, out int value) + { + int startIndex = index; + + value = 0; + + while (index < endIndex && text[index] >= (byte) '0' && text[index] <= (byte) '9') { + int digit = text[index] - (byte) '0'; + + if (value > int.MaxValue / 10) { + // integer overflow + return false; + } + + if (value == int.MaxValue / 10 && digit > int.MaxValue % 10) { + // integer overflow + return false; + } + + value = (value * 10) + digit; + index++; + } + + return index > startIndex; + } + + // FIXME: Does this make sense to have here? Or should I have an extensions class for byte[] that has this? + public bool TryParse (int startIndex, int endIndex, out int value) + { + int index = startIndex; + + return TryParse (buffer, ref index, endIndex, out value); + } + + public void Dispose () + { + if (length != -1) { + ArrayPool .Shared.Return (buffer); + length = -1; + } + } + } +} diff --git a/MailKit/CommandException.cs b/MailKit/CommandException.cs index 8e3cfb4464..e3b830a642 100644 --- a/MailKit/CommandException.cs +++ b/MailKit/CommandException.cs @@ -1,9 +1,9 @@ -// +// // CommandException.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -55,9 +55,10 @@ public abstract class CommandException : Exception /// The serialization info. /// The streaming context. /// - /// [SecuritySafeCritical] + [Obsolete ("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] protected CommandException (SerializationInfo info, StreamingContext context) : base (info, context) { } diff --git a/MailKit/CompressedStream.cs b/MailKit/CompressedStream.cs index 180eae7199..a8cfc0e700 100644 --- a/MailKit/CompressedStream.cs +++ b/MailKit/CompressedStream.cs @@ -1,9 +1,9 @@ -// +// // CompressedStream.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,8 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Org.BouncyCastle.Utilities.Zlib; @@ -38,9 +40,9 @@ class CompressedStream : Stream readonly ZStream zIn, zOut; bool eos, disposed; - public CompressedStream (Stream baseStream) + public CompressedStream (Stream innerStream) { - BaseStream = baseStream; + InnerStream = innerStream; zOut = new ZStream (); zOut.deflateInit (5, true); @@ -52,33 +54,33 @@ public CompressedStream (Stream baseStream) } /// - /// Gets the base stream. + /// Gets the inner stream. /// - ///The base stream. - public Stream BaseStream { + ///The inner stream. + public Stream InnerStream { get; private set; } ////// Gets whether the stream supports reading. /// - ///+ /// true if the stream supports reading; otherwise,false .public override bool CanRead { - get { return BaseStream.CanRead; } + get { return InnerStream.CanRead; } } /// if the stream supports reading; otherwise, . /// Gets whether the stream supports writing. /// - ///+ /// true if the stream supports writing; otherwise,false .public override bool CanWrite { - get { return BaseStream.CanWrite; } + get { return InnerStream.CanWrite; } } /// if the stream supports writing; otherwise, . /// Gets whether the stream supports seeking. /// - ///+ /// true if the stream supports seeking; otherwise,false .public override bool CanSeek { get { return false; } } @@ -86,29 +88,29 @@ public override bool CanSeek { /// if the stream supports seeking; otherwise, . /// Gets whether the stream supports I/O timeouts. /// - ///+ /// true if the stream supports I/O timeouts; otherwise,false .public override bool CanTimeout { - get { return BaseStream.CanTimeout; } + get { return InnerStream.CanTimeout; } } /// if the stream supports I/O timeouts; otherwise, . - /// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to read before timing out. + /// Gets or sets a value, in milliseconds, that determines how long the stream will attempt to read before timing out. /// - ///A value, in miliseconds, that determines how long the stream will attempt to read before timing out. + ///A value, in milliseconds, that determines how long the stream will attempt to read before timing out. ///The read timeout. public override int ReadTimeout { - get { return BaseStream.ReadTimeout; } - set { BaseStream.ReadTimeout = value; } + get { return InnerStream.ReadTimeout; } + set { InnerStream.ReadTimeout = value; } } ///- /// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to write before timing out. + /// Gets or sets a value, in milliseconds, that determines how long the stream will attempt to write before timing out. /// - ///A value, in miliseconds, that determines how long the stream will attempt to write before timing out. + ///A value, in milliseconds, that determines how long the stream will attempt to write before timing out. ///The write timeout. public override int WriteTimeout { - get { return BaseStream.WriteTimeout; } - set { BaseStream.WriteTimeout = value; } + get { return InnerStream.WriteTimeout; } + set { InnerStream.WriteTimeout = value; } } ///@@ -164,12 +166,12 @@ void CheckDisposed () /// The buffer offset. /// The number of bytes to read. /// - /// The buffer to write. - /// The offset of the first byte to write. - /// The number of bytes to write. + /// The buffer to write. + /// The offset of the first byte to write. + /// The number of bytes to write. ///- /// ///is null . + ///is . /// /// ////// is less than zero or greater than the length of . -or- - ///The is not large enough to contain bytes strting + /// The ///is not large enough to contain bytes starting /// at the specified . @@ -193,7 +195,71 @@ public override int Read (byte[] buffer, int offset, int count) do { if (zIn.avail_in == 0 && !eos) { - zIn.avail_in = BaseStream.Read (zIn.next_in, 0, zIn.next_in.Length); + zIn.avail_in = InnerStream.Read (zIn.next_in, 0, zIn.next_in.Length); + + eos = zIn.avail_in == 0; + zIn.next_in_index = 0; + } + + int retval = zIn.inflate (JZlib.Z_FULL_FLUSH); + + if (retval == JZlib.Z_STREAM_END) + break; + + if (eos && retval == JZlib.Z_BUF_ERROR) + return 0; + + if (retval != JZlib.Z_OK) + throw new IOException ("Error inflating: " + zIn.msg); + } while (zIn.avail_out == count); + + return count - zIn.avail_out; + } + + /// + /// Reads a sequence of bytes from the stream and advances the position + /// within the stream by the number of bytes read. + /// + ///The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many + /// bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// The buffer. + /// The buffer offset. + /// The number of bytes to read. + /// The cancellation token. + ///+ /// + ///is . + /// + /// + ///+ /// is less than zero or greater than the length of . -or- + ///The + ///is not large enough to contain bytes starting + /// at the specified . + /// The stream has been disposed. + /// + ///+ /// An I/O error occurred. + /// + public override async TaskReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + CheckDisposed (); + + ValidateArguments (buffer, offset, count); + + if (count == 0) + return 0; + + zIn.next_out = buffer; + zIn.next_out_index = offset; + zIn.avail_out = count; + + do { + if (zIn.avail_in == 0 && !eos) { + cancellationToken.ThrowIfCancellationRequested (); + + zIn.avail_in = await InnerStream.ReadAsync (zIn.next_in, 0, zIn.next_in.Length, cancellationToken).ConfigureAwait (false); + eos = zIn.avail_in == 0; zIn.next_in_index = 0; } @@ -217,16 +283,16 @@ public override int Read (byte[] buffer, int offset, int count) /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// - /// ///is null . + ///is . /// /// ////// is less than zero or greater than the length of . -or- - ///The is not large enough to contain bytes strting + /// The ///is not large enough to contain bytes starting /// at the specified . @@ -258,7 +324,60 @@ public override void Write (byte[] buffer, int offset, int count) if (zOut.deflate (JZlib.Z_FULL_FLUSH) != JZlib.Z_OK) throw new IOException ("Error deflating: " + zOut.msg); - BaseStream.Write (zOut.next_out, 0, zOut.next_out.Length - zOut.avail_out); + InnerStream.Write (zOut.next_out, 0, zOut.next_out.Length - zOut.avail_out); + } while (zOut.avail_in > 0 || zOut.avail_out == 0); + } + + /// + /// Writes a sequence of bytes to the stream and advances the current + /// position within this stream by the number of bytes written. + /// + ///A task that represents the asynchronous write operation. + /// The buffer to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// The cancellation token. + ///+ /// + ///is . + /// + /// + ///+ /// is less than zero or greater than the length of . -or- + ///The + ///is not large enough to contain bytes starting + /// at the specified . + /// The stream has been disposed. + /// + ///+ /// The stream does not support writing. + /// + ///+ /// An I/O error occurred. + /// + public override async Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + CheckDisposed (); + + ValidateArguments (buffer, offset, count); + + if (count == 0) + return; + + zOut.next_in = buffer; + zOut.next_in_index = offset; + zOut.avail_in = count; + + do { + cancellationToken.ThrowIfCancellationRequested (); + + zOut.avail_out = zOut.next_out.Length; + zOut.next_out_index = 0; + + if (zOut.deflate (JZlib.Z_FULL_FLUSH) != JZlib.Z_OK) + throw new IOException ("Error deflating: " + zOut.msg); + + await InnerStream.WriteAsync (zOut.next_out, 0, zOut.next_out.Length - zOut.avail_out, cancellationToken).ConfigureAwait (false); } while (zOut.avail_in > 0 || zOut.avail_out == 0); } @@ -279,7 +398,28 @@ public override void Flush () { CheckDisposed (); - BaseStream.Flush (); + InnerStream.Flush (); + } + + ///+ /// Clears all output buffers for this stream and causes any buffered data to be written + /// to the underlying device. + /// + ///A task that represents the asynchronous flush operation. + ///+ /// The stream has been disposed. + /// + ///+ /// The stream does not support writing. + /// + ///+ /// An I/O error occurred. + /// + public override Task FlushAsync (CancellationToken cancellationToken) + { + CheckDisposed (); + + return InnerStream.FlushAsync (cancellationToken); } ///@@ -309,15 +449,15 @@ public override void SetLength (long value) } /// - /// Releases the unmanaged resources used by the - ///and + /// Releases the unmanaged resources used by the and /// optionally releases the managed resources. /// true to release both managed and unmanaged resources; - ///false to release only the unmanaged resources. + ///to release both managed and unmanaged resources; + /// to release only the unmanaged resources. protected override void Dispose (bool disposing) { if (disposing && !disposed) { - BaseStream.Dispose (); + InnerStream.Dispose (); disposed = true; zOut.free (); zIn.free (); diff --git a/MailKit/ConnectedEventArgs.cs b/MailKit/ConnectedEventArgs.cs new file mode 100644 index 0000000000..12e898c3a8 --- /dev/null +++ b/MailKit/ConnectedEventArgs.cs @@ -0,0 +1,91 @@ +// +// ConnectedEventArgs.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; + +using MailKit.Security; + +namespace MailKit +{ + /// + /// Connected event arguments. + /// + ///+ /// When a + public class ConnectedEventArgs : EventArgs + { + ///is connected, it will emit a + /// event. + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The name of the host that the client connected to. + /// The port that the client connected to on the remote host. + /// The SSL/TLS options that were used when connecting to the remote host. + public ConnectedEventArgs (string host, int port, SecureSocketOptions options) + { + Options = options; + Host = host; + Port = port; + } + + ///. + /// + /// Get the name of the remote host. + /// + ///+ /// Gets the name of the remote host. + /// + ///The host name of the server. + public string Host { + get; private set; + } + + ///+ /// Get the port. + /// + ///+ /// Gets the port. + /// + ///The port. + public int Port { + get; private set; + } + + ///+ /// Get the SSL/TLS options. + /// + ///+ /// Gets the SSL/TLS options. + /// + ///The SSL/TLS options. + public SecureSocketOptions Options { + get; private set; + } + } +} diff --git a/MailKit/DeliveryStatusNotification.cs b/MailKit/DeliveryStatusNotification.cs index 747b9db9a3..20af530a8f 100644 --- a/MailKit/DeliveryStatusNotification.cs +++ b/MailKit/DeliveryStatusNotification.cs @@ -1,9 +1,9 @@ -// +// // DeliveryStatusNotification.cs // // Author: Jeffrey Stedfast// -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -33,8 +33,11 @@ namespace MailKit { /// /// A set of flags that may be bitwise-or'd together to specify /// when a delivery status notification should be sent for a - /// particlar recipient. + /// particular recipient. /// + ///+ /// [Flags] public enum DeliveryStatusNotification { ///+ ///diff --git a/MailKit/DeliveryStatusNotificationType.cs b/MailKit/DeliveryStatusNotificationType.cs new file mode 100644 index 0000000000..16b9456e04 --- /dev/null +++ b/MailKit/DeliveryStatusNotificationType.cs @@ -0,0 +1,58 @@ +// +// DeliveryStatusNotificationReturnType.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// FIXME: Move this to the MailKit namespace. It wasn't ever supposed to be in MailKit.Net.Smtp! +namespace MailKit.Net.Smtp +{ + /// + /// Delivery status notification type. + /// + ///+ /// The delivery status notification type specifies whether or not + /// the full message should be included in any failed DSN issued for + /// a message transmission as opposed to just the headers. + /// + ///+ /// + public enum DeliveryStatusNotificationType + { + ///+ ///+ /// The return type is unspecified, allowing the server to choose. + /// + Unspecified, + + ///+ /// The full message should be included in any failed delivery status notification issued by the server. + /// + Full, + + ///+ /// Only the headers should be included in any failed delivery status notification issued by the server. + /// + HeadersOnly, + } +} diff --git a/MailKit/DisconnectedEventArgs.cs b/MailKit/DisconnectedEventArgs.cs new file mode 100644 index 0000000000..585f3d1cf9 --- /dev/null +++ b/MailKit/DisconnectedEventArgs.cs @@ -0,0 +1,70 @@ +// +// DisconnectedEventArgs.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using MailKit.Security; + +namespace MailKit +{ + /// + /// Disconnected event arguments. + /// + ///+ /// When a + public class DisconnectedEventArgs : ConnectedEventArgs + { + ///gets disconnected, it will emit a + /// event. + /// + /// Initializes a new instance of the + ///class. + /// + /// Initializes a new instance of the + /// The name of the host that the client was connected to. + /// The port that the client was connected to. + /// The SSL/TLS options that were used by the client. + /// Ifclass. + /// , the was disconnected via the + /// method. + public DisconnectedEventArgs (string host, int port, SecureSocketOptions options, bool requested) : base (host, port, options) + { + IsRequested = requested; + } + + /// + /// Get whether or not the service was explicitly asked to disconnect. + /// + ///+ /// If the + ///was disconnected via the + /// method, then + /// the value of will be . If the connection was unexpectedly + /// dropped, then the value will be . + /// + public bool IsRequested { + get; private set; + } + } +} diff --git a/MailKit/DuplexStream.cs b/MailKit/DuplexStream.cs index 2820c13bc8..cae4587e49 100644 --- a/MailKit/DuplexStream.cs +++ b/MailKit/DuplexStream.cs @@ -1,9 +1,9 @@ -// +// // DuplexStream.cs // // Author: Jeffrey Stedfast if the disconnect was explicitly requested; otherwise, . // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,8 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace MailKit { /// @@ -41,9 +43,9 @@ class DuplexStream : Stream /// The stream to use for input. /// The stream to use for output. /// - /// public DuplexStream (Stream istream, Stream ostream) { @@ -76,7 +78,7 @@ public Stream OutputStream { ///+ /// is null ./// is . -or- - ///+ /// is null ./// is . /// Gets whether the stream supports reading. /// - ///+ /// true if the stream supports reading; otherwise,false .public override bool CanRead { get { return true; } } @@ -84,7 +86,7 @@ public override bool CanRead { /// if the stream supports reading; otherwise, . /// Gets whether the stream supports writing. /// - ///+ /// true if the stream supports writing; otherwise,false .public override bool CanWrite { get { return true; } } @@ -92,7 +94,7 @@ public override bool CanWrite { /// if the stream supports writing; otherwise, . /// Gets whether the stream supports seeking. /// - ///+ /// true if the stream supports seeking; otherwise,false .public override bool CanSeek { get { return false; } } @@ -100,15 +102,15 @@ public override bool CanSeek { /// if the stream supports seeking; otherwise, . /// Gets whether the stream supports I/O timeouts. /// - ///+ /// true if the stream supports I/O timeouts; otherwise,false .public override bool CanTimeout { get { return InputStream.CanTimeout && OutputStream.CanTimeout; } } /// if the stream supports I/O timeouts; otherwise, . - /// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to read before timing out. + /// Gets or sets a value, in milliseconds, that determines how long the stream will attempt to read before timing out. /// - ///A value, in miliseconds, that determines how long the stream will attempt to read before timing out. + ///A value, in milliseconds, that determines how long the stream will attempt to read before timing out. ///The read timeout. public override int ReadTimeout { get { return InputStream.ReadTimeout; } @@ -116,9 +118,9 @@ public override int ReadTimeout { } ///- /// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to write before timing out. + /// Gets or sets a value, in milliseconds, that determines how long the stream will attempt to write before timing out. /// - ///A value, in miliseconds, that determines how long the stream will attempt to write before timing out. + ///A value, in milliseconds, that determines how long the stream will attempt to write before timing out. ///The write timeout. public override int WriteTimeout { get { return OutputStream.WriteTimeout; } @@ -178,12 +180,12 @@ void CheckDisposed () /// The buffer offset. /// The number of bytes to read. ///- /// ///is null . + ///is . /// /// ////// is less than zero or greater than the length of . -or- - ///The is not large enough to contain bytes strting + /// The ///is not large enough to contain bytes starting /// at the specified . @@ -201,20 +203,54 @@ public override int Read (byte[] buffer, int offset, int count) return InputStream.Read (buffer, offset, count); } + /// + /// Reads a sequence of bytes from the stream and advances the position + /// within the stream by the number of bytes read. + /// + ///The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many + /// bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// The buffer. + /// The buffer offset. + /// The number of bytes to read. + /// The cancellation token. + ///+ /// + ///is . + /// + /// + ///+ /// is less than zero or greater than the length of . -or- + ///The + ///is not large enough to contain bytes starting + /// at the specified . + /// The stream has been disposed. + /// + ///+ /// An I/O error occurred. + /// + public override TaskReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + CheckDisposed (); + + ValidateArguments (buffer, offset, count); + + return InputStream.ReadAsync (buffer, offset, count, cancellationToken); + } + /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// - /// The buffer to write. - /// The offset of the first byte to write. - /// The number of bytes to write. + /// The buffer to write. + /// The offset of the first byte to write. + /// The number of bytes to write. ///- /// ///is null . + ///is . /// /// ////// is less than zero or greater than the length of . -or- - ///The is not large enough to contain bytes strting + /// The ///is not large enough to contain bytes starting /// at the specified . @@ -235,6 +271,42 @@ public override void Write (byte[] buffer, int offset, int count) OutputStream.Write (buffer, offset, count); } + /// + /// Writes a sequence of bytes to the stream and advances the current + /// position within this stream by the number of bytes written. + /// + ///A task that represents the asynchronous write operation. + /// The buffer to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// The cancellation token. + ///+ /// + ///is . + /// + /// + ///+ /// is less than zero or greater than the length of . -or- + ///The + ///is not large enough to contain bytes starting + /// at the specified . + /// The stream has been disposed. + /// + ///+ /// The stream does not support writing. + /// + ///+ /// An I/O error occurred. + /// + public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + CheckDisposed (); + + ValidateArguments (buffer, offset, count); + + return OutputStream.WriteAsync (buffer, offset, count, cancellationToken); + } + ////// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. @@ -255,6 +327,28 @@ public override void Flush () OutputStream.Flush (); } + /// - ///+ /// Clears all output buffers for this stream and causes any buffered data to be written + /// to the underlying device. + /// + ///A task that represents the asynchronous flush operation. + /// The cancellation token. + ///+ /// The stream has been disposed. + /// + ///+ /// The stream does not support writing. + /// + ///+ /// An I/O error occurred. + /// + public override Task FlushAsync (CancellationToken cancellationToken) + { + CheckDisposed (); + + return OutputStream.FlushAsync (cancellationToken); + } + ////// Sets the position within the current stream. /// @@ -285,8 +379,8 @@ public override void SetLength (long value) /// Releases the unmanaged resources used by theand /// optionally releases the managed resources. /// true to release both managed and unmanaged resources; - ///false to release only the unmanaged resources. + ///to release both managed and unmanaged resources; + /// to release only the unmanaged resources. protected override void Dispose (bool disposing) { if (disposing && !disposed) { diff --git a/MailKit/Envelope.cs b/MailKit/Envelope.cs index 32c86d5e6a..83c51535bf 100644 --- a/MailKit/Envelope.cs +++ b/MailKit/Envelope.cs @@ -1,9 +1,9 @@ -// +// // Envelope.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,8 @@ using System; using System.Text; using System.Linq; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using MimeKit; using MimeKit.Utils; @@ -135,7 +137,7 @@ public InternetAddressList Bcc { /// The Message-Id that the message is replying to. /// /// The Message-Id that the message is replying to. - public string InReplyTo { + public string? InReplyTo { get; set; } @@ -157,7 +159,7 @@ public DateTimeOffset? Date { /// Gets the ID of the message, if available. /// ///The message identifier. - public string MessageId { + public string? MessageId { get; set; } @@ -168,7 +170,7 @@ public string MessageId { /// Gets the subject of the message. /// ///The subject. - public string Subject { + public string? Subject { get; set; } @@ -176,46 +178,60 @@ static void EncodeMailbox (StringBuilder builder, MailboxAddress mailbox) { builder.Append ('('); - if (mailbox.Name != null) - builder.AppendFormat ("{0} ", MimeUtils.Quote (mailbox.Name)); - else + if (mailbox.Name != null) { + MimeUtils.AppendQuoted (builder, mailbox.Name); + builder.Append (' '); + } else { builder.Append ("NIL "); + } - if (mailbox.Route.Count != 0) - builder.AppendFormat ("\"{0}\" ", mailbox.Route); - else + if (mailbox.Route.Count != 0) { + MimeUtils.AppendQuoted (builder, mailbox.Route.ToString ()); + builder.Append (' '); + } else { builder.Append ("NIL "); + } - if (mailbox.Address != null) { - int at = mailbox.Address.LastIndexOf ('@'); + int at = mailbox.Address.LastIndexOf ('@'); - if (at >= 0) { - var domain = mailbox.Address.Substring (at + 1); - var user = mailbox.Address.Substring (0, at); + if (at >= 0) { + var domain = mailbox.Address.Substring (at + 1); + var user = mailbox.Address.Substring (0, at); - builder.AppendFormat ("{0} {1}", MimeUtils.Quote (user), MimeUtils.Quote (domain)); - } else { - builder.AppendFormat ("{0} NIL", MimeUtils.Quote (mailbox.Address)); - } + MimeUtils.AppendQuoted (builder, user); + builder.Append (' '); + MimeUtils.AppendQuoted (builder, domain); } else { - builder.Append ("NIL NIL"); + MimeUtils.AppendQuoted (builder, mailbox.Address); + builder.Append (" \"localhost\""); } builder.Append (')'); } - static void EncodeAddressList (StringBuilder builder, InternetAddressList list) + static void EncodeInternetAddressListAddresses (StringBuilder builder, InternetAddressList addresses) { - if (list.Count == 0) { - builder.Append ("NIL"); - return; + foreach (var addr in addresses) { + if (addr is MailboxAddress mailbox) + EncodeMailbox (builder, mailbox); + else if (addr is GroupAddress group) + EncodeGroup (builder, group); } + } - builder.Append ('('); - - foreach (var mailbox in list.Mailboxes) - EncodeMailbox (builder, mailbox); + static void EncodeGroup (StringBuilder builder, GroupAddress group) + { + builder.Append ("(NIL NIL "); + MimeUtils.AppendQuoted (builder, group.Name ?? string.Empty); + builder.Append (" NIL)"); + EncodeInternetAddressListAddresses (builder, group.Members); + builder.Append ("(NIL NIL NIL NIL)"); + } + static void EncodeAddressList (StringBuilder builder, InternetAddressList list) + { + builder.Append ('('); + EncodeInternetAddressListAddresses (builder, list); builder.Append (')'); } @@ -223,15 +239,20 @@ internal void Encode (StringBuilder builder) { builder.Append ('('); - if (Date.HasValue) - builder.AppendFormat ("\"{0}\" ", DateUtils.FormatDate (Date.Value)); - else + if (Date.HasValue) { + builder.Append ('"'); + builder.Append (DateUtils.FormatDate (Date.Value)); + builder.Append ("\" "); + } else { builder.Append ("NIL "); + } - if (Subject != null) - builder.AppendFormat ("{0} ", MimeUtils.Quote (Subject)); - else + if (Subject != null) { + MimeUtils.AppendQuoted (builder, Subject); + builder.Append (' '); + } else { builder.Append ("NIL "); + } if (From.Count > 0) { EncodeAddressList (builder, From); @@ -276,20 +297,31 @@ internal void Encode (StringBuilder builder) } if (InReplyTo != null) { + string inReplyTo; + if (InReplyTo.Length > 1 && InReplyTo[0] != '<' && InReplyTo[InReplyTo.Length - 1] != '>') - builder.AppendFormat ("{0} ", MimeUtils.Quote ('<' + InReplyTo + '>')); + inReplyTo = '<' + InReplyTo + '>'; else - builder.AppendFormat ("{0} ", MimeUtils.Quote (InReplyTo)); - } else + inReplyTo = InReplyTo; + + MimeUtils.AppendQuoted (builder, inReplyTo); + builder.Append (' '); + } else { builder.Append ("NIL "); + } if (MessageId != null) { + string messageId; + if (MessageId.Length > 1 && MessageId[0] != '<' && MessageId[MessageId.Length - 1] != '>') - builder.AppendFormat ("{0}", MimeUtils.Quote ('<' + MessageId + '>')); + messageId = '<' + MessageId + '>'; else - builder.AppendFormat ("{0}", MimeUtils.Quote (MessageId)); - } else + messageId = MessageId; + + MimeUtils.AppendQuoted (builder, messageId); + } else { builder.Append ("NIL"); + } builder.Append (')'); } @@ -312,7 +344,12 @@ public override string ToString () return builder.ToString (); } - static bool TryParse (string text, ref int index, out string nstring) + static bool IsNIL (string text, int index) + { + return string.Compare (text, index, "NIL", 0, 3, StringComparison.Ordinal) == 0; + } + + static bool TryParse (string text, ref int index, out string? nstring) { nstring = null; @@ -323,7 +360,7 @@ static bool TryParse (string text, ref int index, out string nstring) return false; if (text[index] != '"') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { index += 3; return true; } @@ -360,28 +397,25 @@ static bool TryParse (string text, ref int index, out string nstring) return true; } - static bool TryParse (string text, ref int index, out MailboxAddress mailbox) + static bool TryParse (string text, ref int index, out InternetAddress? addr) { - string name, route, user, domain, address; - DomainList domains; - - mailbox = null; + addr = null; if (text[index] != '(') return false; index++; - if (!TryParse (text, ref index, out name)) + if (!TryParse (text, ref index, out string? name)) return false; - if (!TryParse (text, ref index, out route)) + if (!TryParse (text, ref index, out string? route)) return false; - if (!TryParse (text, ref index, out user)) + if (!TryParse (text, ref index, out string? user)) return false; - if (!TryParse (text, ref index, out domain)) + if (!TryParse (text, ref index, out string? domain)) return false; while (index < text.Length && text[index] == ' ') @@ -392,20 +426,25 @@ static bool TryParse (string text, ref int index, out MailboxAddress mailbox) index++; - address = domain != null ? user + "@" + domain : user; + if (domain != null) { + user ??= "NIL"; - if (route != null && DomainList.TryParse (route, out domains)) - mailbox = new MailboxAddress (name, domains, address); - else - mailbox = new MailboxAddress (name, address); + // Note: The serializer injects "localhost" as the domain when provided a UNIX mailbox or the special <> mailbox. + var address = domain == "localhost" ? user : user + "@" + domain; + + if (route != null && DomainList.TryParse (route, out var domains)) + addr = new MailboxAddress (name, domains, address); + else + addr = new MailboxAddress (name, address); + } else if (user != null) { + addr = new GroupAddress (user); + } return true; } - static bool TryParse (string text, ref int index, out InternetAddressList list) + static bool TryParse (string text, ref int index, [NotNullWhen (true)] out InternetAddressList? list) { - MailboxAddress mailbox; - list = null; while (index < text.Length && text[index] == ' ') @@ -415,7 +454,7 @@ static bool TryParse (string text, ref int index, out InternetAddressList list) return false; if (text[index] != '(') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { list = new InternetAddressList (); index += 3; return true; @@ -430,20 +469,37 @@ static bool TryParse (string text, ref int index, out InternetAddressList list) return false; list = new InternetAddressList (); + var stack = new List(); + int sp = 0; + + stack.Add (list); do { if (text[index] == ')') break; - if (!TryParse (text, ref index, out mailbox)) + if (!TryParse (text, ref index, out InternetAddress? addr)) return false; - list.Add (mailbox); + if (addr != null) { + stack[sp].Add (addr); + + if (addr is GroupAddress group) { + stack.Add (group.Members); + sp++; + } + } else if (sp > 0) { + stack.RemoveAt (sp); + sp--; + } while (index < text.Length && text[index] == ' ') index++; } while (index < text.Length); + // Note: technically, we should check that sp == 0 as well, since all groups should + // be popped off the stack, but in the interest of being liberal in what we accept, + // we'll ignore that. if (index >= text.Length) return false; @@ -452,10 +508,8 @@ static bool TryParse (string text, ref int index, out InternetAddressList list) return true; } - internal static bool TryParse (string text, ref int index, out Envelope envelope) + internal static bool TryParse (string text, ref int index, out Envelope? envelope) { - InternetAddressList from, sender, replyto, to, cc, bcc; - string inreplyto, messageid, subject, nstring; DateTimeOffset? date = null; envelope = null; @@ -464,7 +518,7 @@ internal static bool TryParse (string text, ref int index, out Envelope envelope index++; if (index >= text.Length || text[index] != '(') { - if (index + 3 <= text.Length && text.Substring (index, 3) == "NIL") { + if (index + 3 <= text.Length && IsNIL (text, index)) { index += 3; return true; } @@ -474,43 +528,41 @@ internal static bool TryParse (string text, ref int index, out Envelope envelope index++; - if (!TryParse (text, ref index, out nstring)) + if (!TryParse (text, ref index, out string? nstring)) return false; if (nstring != null) { - DateTimeOffset value; - - if (!DateUtils.TryParse (nstring, out value)) + if (!DateUtils.TryParse (nstring, out DateTimeOffset value)) return false; date = value; } - if (!TryParse (text, ref index, out subject)) + if (!TryParse (text, ref index, out string? subject)) return false; - if (!TryParse (text, ref index, out from)) + if (!TryParse (text, ref index, out InternetAddressList? from)) return false; - if (!TryParse (text, ref index, out sender)) + if (!TryParse (text, ref index, out InternetAddressList? sender)) return false; - if (!TryParse (text, ref index, out replyto)) + if (!TryParse (text, ref index, out InternetAddressList? replyto)) return false; - if (!TryParse (text, ref index, out to)) + if (!TryParse (text, ref index, out InternetAddressList? to)) return false; - if (!TryParse (text, ref index, out cc)) + if (!TryParse (text, ref index, out InternetAddressList? cc)) return false; - if (!TryParse (text, ref index, out bcc)) + if (!TryParse (text, ref index, out InternetAddressList? bcc)) return false; - if (!TryParse (text, ref index, out inreplyto)) + if (!TryParse (text, ref index, out string? inreplyto)) return false; - if (!TryParse (text, ref index, out messageid)) + if (!TryParse (text, ref index, out string? messageid)) return false; if (index >= text.Length || text[index] != ')') @@ -542,13 +594,13 @@ internal static bool TryParse (string text, ref int index, out Envelope envelope /// This syntax, while similar to IMAP's ENVELOPE syntax, is not /// completely compatible. /// - ///+ /// true , if the envelope was successfully parsed,false otherwise./// The text to parse. /// The parsed envelope. /// , if the envelope was successfully parsed, otherwise. - /// - public static bool TryParse (string text, out Envelope envelope) + public static bool TryParse (string text, out Envelope? envelope) { if (text == null) throw new ArgumentNullException (nameof (text)); @@ -559,4 +611,3 @@ public static bool TryParse (string text, out Envelope envelope) } } } - \ No newline at end of file diff --git a/MailKit/FetchRequest.cs b/MailKit/FetchRequest.cs new file mode 100644 index 0000000000..087304ec37 --- /dev/null +++ b/MailKit/FetchRequest.cs @@ -0,0 +1,151 @@ +// +// FetchRequest.cs +// +// Author: Jeffrey Stedfastis null . + ///is . /// +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +using MimeKit; + +namespace MailKit { + /// + /// A request for fetching various properties of a message. + /// + ///+ /// A request for fetching various properties of a message. + /// + public class FetchRequest : IFetchRequest + { + ///+ /// Initializes a new instance of the + ///class. + /// + /// Creates a new + public FetchRequest () + { + } + + ///. + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The items to fetch. + public FetchRequest (MessageSummaryItems items) + { + Items = items; + } + + ///. + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The items to fetch. + /// The specific set of headers to fetch. + ///. + /// + /// + ///is . + /// + /// One or more of the specified + public FetchRequest (MessageSummaryItems items, IEnumerableis invalid. + /// headers) : this (items) + { + Headers = (headers is HeaderSet set) ? set : new HeaderSet (headers); + } + + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The items to fetch. + /// The specific set of headers to fetch. + ///. + /// + /// + ///is . + /// + /// One or more of the specified + public FetchRequest (MessageSummaryItems items, IEnumerableis invalid. + /// headers) : this (items) + { + Headers = (headers is HeaderSet set) ? set : new HeaderSet (headers); + } + + /// + /// Get or set the mod-sequence value that indicates the last known state of the messages being requested. + /// + ///+ /// + ///Gets or sets the mod-sequence value that indicates the last known state of the messages being requested. + ///If this property is set, the results returned by Fetch + /// or FetchAsync will only include the message summaries which + /// have a higher mod-sequence value than the one specified. + ///If the mail store supports quick resynchronization and the application has enabled this feature via + /// + ///, then the Fetch or FetchAsync method + /// will emit events for messages that were expunged from the folder after + /// the change specified by the mod-sequence value. It should be noted that if another client has modified any message in the folder, the mail service may choose + /// to return information that was not explicitly requested. It is therefore important to be prepared to handle both + /// additional fields on a + ///for messages that were requested as well as summaries for + /// messages that were not requested at all. The mod-sequence value that indicates the last known state of the messages being requested. + public ulong? ChangedSince { get; set; } + + ///+ /// Get or set the message summary items to fetch. + /// + ///+ /// Gets or sets the message summary items to fetch. + /// + ///The message summary items. + public MessageSummaryItems Items { get; set; } + + ///+ /// Get the set of headers that will be fetched. + /// + ///+ /// Gets the set of headers that will be fetched. + /// + ///The set of headers to be fetched. + public HeaderSet? Headers { get; set; } + +#if ENABLE_LAZY_PREVIEW_API + ///+ /// Get or set options to use when fetching + ///. + /// + /// + public PreviewOptions PreviewOptions { get; set; } +#endif + } +} diff --git a/MailKit/FolderAccess.cs b/MailKit/FolderAccess.cs index a9a90cd640..2ca17a10ed 100644 --- a/MailKit/FolderAccess.cs +++ b/MailKit/FolderAccess.cs @@ -1,9 +1,9 @@ -// +// // FolderMode.cs // // Author: Jeffrey StedfastGets or sets options to use when fetching + ///. These options are only used if + ///includes the + /// value. // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,7 @@ namespace MailKit { /// A folder access mode. /// /// - /// public enum FolderAccess { ///+ //////diff --git a/MailKit/FolderAttributes.cs b/MailKit/FolderAttributes.cs index ad214fed0d..810d8302c4 100644 --- a/MailKit/FolderAttributes.cs +++ b/MailKit/FolderAttributes.cs @@ -1,9 +1,9 @@ -// +// // FolderAttributes.cs // // Author: Jeffrey Stedfast Flagged = (1 << 12), + ///// -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -107,24 +107,29 @@ public enum FolderAttributes { /// + /// The folder is the special "Important" folder. + /// + Important = (1 << 13), + ////// The folder is the special "Inbox" folder. /// - Inbox = (1 << 13), + Inbox = (1 << 14), ////// The folder is the special "Junk" folder. /// - Junk = (1 << 14), + Junk = (1 << 15), ////// The folder is the special "Sent" folder. /// - Sent = (1 << 15), + Sent = (1 << 16), ////// The folder is the special "Trash" folder. /// - Trash = (1 << 16), + Trash = (1 << 17), } } diff --git a/MailKit/FolderCreatedEventArgs.cs b/MailKit/FolderCreatedEventArgs.cs new file mode 100644 index 0000000000..f5a085580f --- /dev/null +++ b/MailKit/FolderCreatedEventArgs.cs @@ -0,0 +1,67 @@ +// +// FolderCreatedEventArgs.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; + +namespace MailKit { + /// + /// Event args used when a + ///is created. + /// + /// Event args used when a + public class FolderCreatedEventArgs : EventArgs + { + ///is created. + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The newly created folder. + ///. + /// + /// + public FolderCreatedEventArgs (IMailFolder folder) + { + if (folder == null) + throw new ArgumentNullException (nameof (folder)); + + Folder = folder; + } + + ///is . + /// + /// Get the folder that was just created. + /// + ///+ /// Gets the folder that was just created. + /// + ///The folder. + public IMailFolder Folder { + get; private set; + } + } +} diff --git a/MailKit/FolderFeature.cs b/MailKit/FolderFeature.cs new file mode 100644 index 0000000000..8015d9f8e1 --- /dev/null +++ b/MailKit/FolderFeature.cs @@ -0,0 +1,82 @@ +// +// FolderFeature.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +namespace MailKit +{ + /// + /// An optional feature that an + ///may support. + /// + /// An optional feature that an + public enum FolderFeature + { + ///may support. + /// + /// Indicates that the folder supports access rights. + /// + AccessRights, + + ///+ /// Indicates that the folder allows arbitrary annotations to be set on a message. + /// + Annotations, + + ///+ /// Indicates that the folder allows arbitrary metadata to be set. + /// + Metadata, + + ///+ /// Indicates that the folder uses modification sequences for every state change of a message. + /// + ModSequences, + + ///+ /// Indicates that the folder supports quick resynchronization when opening. + /// + QuickResync, + + ///+ /// Indicates that the folder supports quotas. + /// + Quotas, + + ///+ /// Indicates that the folder supports sorting messages. + /// + Sorting, + + ///+ /// Indicates that the folder supports threading messages. + /// + Threading, + + ///+ /// Indicates that the folder supports the use of UTF-8. + /// + UTF8, + } +} diff --git a/MailKit/FolderNamespace.cs b/MailKit/FolderNamespace.cs index 6a247787e5..5595b4451f 100644 --- a/MailKit/FolderNamespace.cs +++ b/MailKit/FolderNamespace.cs @@ -1,9 +1,9 @@ -// +// // FolderNamespace.cs // // Author: Jeffrey Stedfast// -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -60,7 +60,7 @@ public class FolderNamespace /// The directory separator. /// The folder path. /// - /// public FolderNamespace (char directorySeparator, string path) { diff --git a/MailKit/FolderNamespaceCollection.cs b/MailKit/FolderNamespaceCollection.cs index 374245fc15..ed6c168e94 100644 --- a/MailKit/FolderNamespaceCollection.cs +++ b/MailKit/FolderNamespaceCollection.cs @@ -1,9 +1,9 @@ -// +// // FolderNamespaceCollection.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -74,7 +74,7 @@ public int Count { /// /// The namespace to add. /// - /// public void Add (FolderNamespace @namespace) { @@ -101,11 +101,11 @@ public void Clear () ///is null . + ///is . /// /// Checks if the collection contains the specified namespace. /// - ///+ /// true if the specified namespace exists; - /// otherwisefalse ./// The namespace. /// if the specified namespace exists; + /// otherwise, . - /// public bool Contains (FolderNamespace @namespace) { @@ -116,16 +116,16 @@ public bool Contains (FolderNamespace @namespace) } ///is null . + ///is . /// - /// Removes the first occurance of the specified namespace. + /// Removes the first occurrence of the specified namespace. /// ///- /// Removes the first occurance of the specified namespace. + /// Removes the first occurrence of the specified namespace. /// - ///+ /// true if the frst occurance of the specified - /// namespace was removed; otherwisefalse ./// The namespace. /// if the first occurrence of the specified + /// namespace was removed; otherwise, . - /// public bool Remove (FolderNamespace @namespace) { @@ -144,7 +144,7 @@ public bool Remove (FolderNamespace @namespace) ///is null . + ///is . /// The folder namespace at the specified index. /// The index. ///- /// ///is null . + ///is . /// /// is out of range. @@ -224,8 +224,8 @@ public override string ToString () builder.Append ('\\'); builder.Append (namespaces[i].DirectorySeparator); builder.Append ("\" "); - builder.Append (MimeUtils.Quote (namespaces[i].Path)); - builder.Append (")"); + MimeUtils.AppendQuoted (builder, namespaces[i].Path); + builder.Append (')'); } builder.Append (')'); diff --git a/MailKit/FolderNotFoundException.cs b/MailKit/FolderNotFoundException.cs index ecb429e55b..55aa8b1380 100644 --- a/MailKit/FolderNotFoundException.cs +++ b/MailKit/FolderNotFoundException.cs @@ -1,9 +1,9 @@ -// +// // FolderNotFoundException.cs // // Author: Jeffrey Stedfast // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -52,8 +52,9 @@ public class FolderNotFoundException : Exception /// The serialization info. /// The streaming context. /// - /// + [Obsolete ("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] protected FolderNotFoundException (SerializationInfo info, StreamingContext context) : base (info, context) { FolderName = info.GetString ("FolderName"); @@ -70,7 +71,7 @@ protected FolderNotFoundException (SerializationInfo info, StreamingContext cont /// The name of the folder. /// The inner exception. ///is null . + ///is . /// - /// public FolderNotFoundException (string message, string folderName, Exception innerException) : base (message, innerException) { @@ -89,7 +90,7 @@ public FolderNotFoundException (string message, string folderName, Exception inn /// The error message. /// The name of the folder. ///is null . + ///is . /// - /// public FolderNotFoundException (string message, string folderName) : base (message) { @@ -107,7 +108,7 @@ public FolderNotFoundException (string message, string folderName) : base (messa /// /// The name of the folder. ///is null . + ///is . /// - /// public FolderNotFoundException (string folderName) : this ("The requested folder could not be found.", folderName) { @@ -135,17 +136,17 @@ public string FolderName { /// The serialization info. /// The streaming context. ///is null . + ///is . /// - /// [SecurityCritical] +#if NET8_0_OR_GREATER + [Obsolete ("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] +#endif public override void GetObjectData (SerializationInfo info, StreamingContext context) { - if (info == null) - throw new ArgumentNullException (nameof (info)); + base.GetObjectData (info, context); info.AddValue ("FolderName", FolderName); - - base.GetObjectData (info, context); } #endif } diff --git a/MailKit/FolderNotOpenException.cs b/MailKit/FolderNotOpenException.cs index 73d64d4508..034cdd0d8a 100644 --- a/MailKit/FolderNotOpenException.cs +++ b/MailKit/FolderNotOpenException.cs @@ -1,9 +1,9 @@ -// +// // FolderNotOpenException.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -55,14 +55,14 @@ public class FolderNotOpenException : InvalidOperationException /// The serialization info. /// The streaming context. /// - /// + [Obsolete ("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] protected FolderNotOpenException (SerializationInfo info, StreamingContext context) : base (info, context) { var value = info.GetString ("FolderAccess"); - FolderAccess access; - if (!Enum.TryParse (value, out access)) + if (!Enum.TryParse (value, out FolderAccess access)) FolderAccess = FolderAccess.ReadOnly; else FolderAccess = access; @@ -82,7 +82,7 @@ protected FolderNotOpenException (SerializationInfo info, StreamingContext conte /// The error message. /// The inner exception. ///is null . + ///is . /// - /// public FolderNotOpenException (string folderName, FolderAccess access, string message, Exception innerException) : base (message, innerException) { @@ -103,7 +103,7 @@ public FolderNotOpenException (string folderName, FolderAccess access, string me /// The minimum folder access required by the operation. /// The error message. ///is null . + ///is . /// - /// public FolderNotOpenException (string folderName, FolderAccess access, string message) : base (message) { @@ -123,7 +123,7 @@ public FolderNotOpenException (string folderName, FolderAccess access, string me /// The folder name. /// The minimum folder access required by the operation. ///is null . + ///is . /// - /// public FolderNotOpenException (string folderName, FolderAccess access) : this (folderName, access, GetDefaultMessage (access)) { @@ -170,17 +170,17 @@ static string GetDefaultMessage (FolderAccess access) /// The serialization info. /// The streaming context. ///is null . + ///is . /// - /// +#if NET8_0_OR_GREATER + [Obsolete ("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")] +#endif public override void GetObjectData (SerializationInfo info, StreamingContext context) { - if (info == null) - throw new ArgumentNullException (nameof (info)); + base.GetObjectData (info, context); info.AddValue ("FolderAccess", FolderAccess.ToString ()); info.AddValue ("FolderName", FolderName); - - base.GetObjectData (info, context); } #endif } diff --git a/MailKit/FolderQuota.cs b/MailKit/FolderQuota.cs index 28141e2926..89883c1d11 100644 --- a/MailKit/FolderQuota.cs +++ b/MailKit/FolderQuota.cs @@ -1,9 +1,9 @@ -// +// // FolderQuota.cs // // Author: Jeffrey Stedfastis null . + ///is . /// // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,8 +24,6 @@ // THE SOFTWARE. // -using System; - namespace MailKit { /// /// A folder quota. @@ -45,7 +43,7 @@ public class FolderQuota /// Creates a new ///with the specified root. /// /// The quota root. - public FolderQuota (IMailFolder quotaRoot) + public FolderQuota (IMailFolder? quotaRoot) { QuotaRoot = quotaRoot; } @@ -54,14 +52,14 @@ public FolderQuota (IMailFolder quotaRoot) /// Get the quota root. /// - /// Gets the quota root. If the quota root is ///null , then + /// Gets the quota root. If the quota root is, then /// it suggests that the folder does not have a quota. /// /// //////The quota root. - public IMailFolder QuotaRoot { + public IMailFolder? QuotaRoot { get; private set; } diff --git a/MailKit/FolderRenamedEventArgs.cs b/MailKit/FolderRenamedEventArgs.cs index a9fd221b47..1c2b26b08d 100644 --- a/MailKit/FolderRenamedEventArgs.cs +++ b/MailKit/FolderRenamedEventArgs.cs @@ -1,9 +1,9 @@ -// +// // FolderRenamedEventArgs.cs // // Author: Jeffrey Stedfast// -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -44,9 +44,9 @@ public class FolderRenamedEventArgs : EventArgs /// The old name of the folder. /// The new name of the folder. /// - /// public FolderRenamedEventArgs (string oldName, string newName) { diff --git a/MailKit/HeaderSet.cs b/MailKit/HeaderSet.cs new file mode 100644 index 0000000000..624ccc7b2c --- /dev/null +++ b/MailKit/HeaderSet.cs @@ -0,0 +1,466 @@ +// +// HeaderSet.cs +// +// Author: Jeffrey Stedfast+ /// is null ./// is . -or- - ///+ /// is null ./// is . +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections; +using System.Collections.Generic; + +using MimeKit; + +namespace MailKit { + /// + /// A set of headers. + /// + ///+ /// A set of headers. + /// + public class HeaderSet : ICollection+ { + const string AtomSafeCharacters = "!#$%&'*+-/=?^_`{|}~"; + + /// + /// A set of headers that only includes all headers. + /// + ///+ /// When used with a + public static readonly HeaderSet All = new HeaderSet () { Exclude = true, IsReadOnly = true }; + + ///, this pre-computed set of headers can be used + /// to fetch the entire list of headers for a message. + /// + /// A set of headers that only includes the standard envelope headers. + /// + ///+ /// When used with a + public static readonly HeaderSet Envelope = new HeaderSet (new HeaderId[] { + HeaderId.Sender, + HeaderId.From, + HeaderId.ReplyTo, + HeaderId.To, + HeaderId.Cc, + HeaderId.Bcc, + HeaderId.Subject, + HeaderId.Date, + HeaderId.MessageId, + HeaderId.InReplyTo + }) { IsReadOnly = true }; + + ///, this pre-computed set of headers can be used + /// to fetch the standard envelope headers for a message. + /// + /// A set of headers that only includes the + ///References header. + ///+ /// When used with a + public static readonly HeaderSet References = new HeaderSet (new HeaderId[] { HeaderId.References }) { IsReadOnly = true }; + + readonly HashSet, this pre-computed set of headers can be used + /// to fetch the References header for a message. Generally, this should be used in + /// combination within order to have all of the + /// information needed to thread messages using the + /// threading algorithm. + /// hash = new HashSet (StringComparer.Ordinal); + bool exclude; + + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + public HeaderSet () + { + } + + ///. + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The headers to include. + public HeaderSet (IEnumerable. + /// headers) + { + AddRange (headers); + } + + /// + /// Initializes a new instance of the + ///class. + /// + /// Creates a new + /// The headers to include. + public HeaderSet (IEnumerable. + /// headers) + { + AddRange (headers); + } + + void CheckReadOnly () + { + if (IsReadOnly) + throw new InvalidOperationException ("The HeaderSet is read-only."); + } + + /// + /// Get the number of headers in the set. + /// + ///+ /// Gets the number of headers in the set. + /// + ///The number of headers. + public int Count { + get { return hash.Count; } + } + + ///+ /// Get or set whether this set of headers is meant to be excluded when used with a + ///. + /// + /// Get or set whether this set of headers is meant to be excluded when used with a + ///. + /// + /// if the headers are meant to be excluded; otherwise, . + /// The operation is invalid because the + public bool Exclude { + get { return exclude; } + set { + CheckReadOnly (); + exclude = value; + } + } + + ///is read-only. + /// + /// Get whether or not the set of headers is read-only. + /// + ///+ /// Gets whether or not the set of headers is read-only. + /// + ///+ public bool IsReadOnly { + get; private set; + } + + static bool IsAsciiAtom (char c) + { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || AtomSafeCharacters.IndexOf (c) != -1; + } + + static bool IsValid (string header) + { + if (header.Length == 0) + return false; + + for (int i = 0; i < header.Length; i++) { + if (header[i] < 127 && !IsAsciiAtom (header[i])) + return false; + } + + return true; + } + + /// if this instance is read only; otherwise, . + /// Add the specified header. + /// + ///+ /// Adds the specified header to the set of headers. + /// + ///+ /// The header to add. + /// if the header was added to the set; otherwise, . + /// + ///is not a valid . + /// + /// The operation is invalid because the + public bool Add (HeaderId header) + { + if (header == HeaderId.Unknown) + throw new ArgumentOutOfRangeException (nameof (header)); + + CheckReadOnly (); + + return hash.Add (header.ToHeaderName ().ToUpperInvariant ()); + } + + ///is read-only. + /// + /// Add the specified header. + /// + ///+ /// Adds the specified header to the set of headers. + /// + ///+ /// The header to add. + /// if the header was added to the set; otherwise, . + /// + ///is . + /// + /// The operation is invalid because the + public bool Add (string header) + { + if (header == null) + throw new ArgumentNullException (nameof (header)); + + if (!IsValid (header)) + throw new ArgumentException ("The header field is invalid.", nameof (header)); + + CheckReadOnly (); + + return hash.Add (header.ToUpperInvariant ()); + } + + ///is read-only. + /// + /// Add the specified header. + /// + ///+ /// Adds the specified header to the set of headers. + /// + /// The header to add. + ///+ /// + ///is . + /// + /// The operation is invalid because the + void ICollectionis read-only. + /// .Add (string item) + { + Add (item); + } + + /// + /// Add a collection of headers. + /// + ///+ /// Adds the specified headers to the set of headers. + /// + /// The headers to add. + ///+ /// + ///is . + /// + /// One or more of the specified + ///is invalid. + /// + /// The operation is invalid because the + public void AddRange (IEnumerableis read-only. + /// headers) + { + if (headers == null) + throw new ArgumentNullException (nameof (headers)); + + CheckReadOnly (); + + foreach (var header in headers) { + if (header == HeaderId.Unknown) + throw new ArgumentException ("One or more of the headers is invalid.", nameof (headers)); + + hash.Add (header.ToHeaderName ().ToUpperInvariant ()); + } + } + + /// + /// Add a collection of headers. + /// + ///+ /// Adds the specified headers to the set of headers. + /// + /// The headers to add. + ///+ /// + ///is . + /// + /// One or more of the specified + ///is invalid. + /// + /// The operation is invalid because the + public void AddRange (IEnumerableis read-only. + /// headers) + { + if (headers == null) + throw new ArgumentNullException (nameof (headers)); + + CheckReadOnly (); + + foreach (var header in headers) { + if (header == null || !IsValid (header)) + throw new ArgumentException ("One or more of the headers is invalid.", nameof (headers)); + + hash.Add (header.ToUpperInvariant ()); + } + } + + /// + /// Clear the set of headers. + /// + ///+ /// Clears the set of headers. + /// + ///+ /// The operation is invalid because the + public void Clear () + { + CheckReadOnly (); + hash.Clear (); + } + + ///is read-only. + /// + /// Copy all of the headers in the + ///to the specified array. + /// + /// Copies all of the headers within the + /// The array to copy the headers to. + /// The index into the array. + ///into the array, + /// starting at the specified array index. + /// + /// + ///is . + /// + /// + public void CopyTo (string[] array, int arrayIndex) + { + hash.CopyTo (array, arrayIndex); + } + + ///is out of range. + /// + /// Check if the set of headers contains the specified header. + /// + ///+ /// Determines whether or not the set of headers contains the specified header. + /// + ///+ /// The header identifier. + /// if the specified header exists; + /// otherwise, . + /// + public bool Contains (HeaderId header) + { + if (header == HeaderId.Unknown) + throw new ArgumentOutOfRangeException (nameof (header)); + + return hash.Contains (header.ToHeaderName ().ToUpperInvariant ()); + } + + ///is not a valid . + /// + /// Check if the set of headers contains the specified header. + /// + ///+ /// Determines whether or not the set of headers contains the specified header. + /// + ///+ /// The name of the header. + /// if the specified header exists; + /// otherwise, . + /// + public bool Contains (string header) + { + if (header == null) + throw new ArgumentNullException (nameof (header)); + + return hash.Contains (header.ToUpperInvariant ()); + } + + ///is . + /// + /// Remove the specified header. + /// + ///+ /// Removes the specified header if it exists. + /// + ///+ /// The header. + /// if the specified header was removed; + /// otherwise, . + /// + ///is not a valid . + /// + /// The operation is invalid because the + public bool Remove (HeaderId header) + { + if (header == HeaderId.Unknown) + throw new ArgumentOutOfRangeException (nameof (header)); + + CheckReadOnly (); + + return hash.Remove (header.ToHeaderName ().ToUpperInvariant ()); + } + + ///is read-only. + /// + /// Remove the specified header. + /// + ///+ /// Removes the specified header if it exists. + /// + ///+ /// The header. + /// if the specified header was removed; + /// otherwise, . + /// + ///is . + /// + /// The operation is invalid because the + public bool Remove (string header) + { + if (header == null) + throw new ArgumentNullException (nameof (header)); + + CheckReadOnly (); + + return hash.Remove (header.ToUpperInvariant ()); + } + + ///is read-only. + /// + /// Get an enumerator for the set of headers. + /// + ///+ /// Gets an enumerator for the set of headers. + /// + ///The enumerator. + public IEnumeratorGetEnumerator () + { + return hash.GetEnumerator (); + } + + /// + /// Get an enumerator for the set of headers. + /// + ///+ /// Gets an enumerator for the set of headers. + /// + ///The enumerator. + IEnumerator IEnumerable.GetEnumerator () + { + return hash.GetEnumerator (); + } + } +} diff --git a/MailKit/IAppendRequest.cs b/MailKit/IAppendRequest.cs new file mode 100644 index 0000000000..7b83ab0c77 --- /dev/null +++ b/MailKit/IAppendRequest.cs @@ -0,0 +1,100 @@ +// +// IAppendRequest.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +using MimeKit; + +namespace MailKit { + /// + /// A request for appending a message to a folder. + /// + ///+ /// A request for appending a message to a folder. + /// + public interface IAppendRequest + { + ///+ /// Get the message that should be appended to the folder. + /// + ///+ /// Gets the message that should be appended to the folder. + /// + ///The message. + MimeMessage Message { get; } + + ///+ /// Get or set the message flags that should be set on the message. + /// + ///+ /// Gets or sets the message flags that should be set on the message. + /// + ///The message flags. + MessageFlags Flags { get; set; } + + ///+ /// Get or set the keywords that should be set on the message. + /// + ///+ /// Gets or sets the keywords that should be set on the message. + /// + ///The keywords. + ISet? Keywords { get; set; } + + /// + /// Get or set the timestamp that should be used by folder as the + ///. + /// + /// Gets or sets the timestamp that should be used by folder as the + ///. + /// The date and time to use for the INTERNALDATE or + DateTimeOffset? InternalDate { get; set; } + + ///if it should be left up to the folder to decide. + /// Get or set the list of annotations that should be set on the message. + /// + ///+ /// + ///Gets or sets the list of annotations that should be set on the message. + ///+ /// + ///This feature is not supported by all folders. + ///Use + ///with the enum value + /// to determine if this feature is supported. The list of annotations. + IList? Annotations { get; set; } + + /// + /// Get or set the transfer progress reporting mechanism. + /// + ///+ /// Gets or sets the transfer progress reporting mechanism. + /// + ///The transfer progress mechanism. + ITransferProgress? TransferProgress { get; set; } + } +} diff --git a/MailKit/IAuthenticationSecretDetector.cs b/MailKit/IAuthenticationSecretDetector.cs new file mode 100644 index 0000000000..c4f66bb336 --- /dev/null +++ b/MailKit/IAuthenticationSecretDetector.cs @@ -0,0 +1,93 @@ +// +// IAuthenticationSecretDetector.cs +// +// Author: Jeffrey Stedfast+// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +namespace MailKit { + /// + /// An authentication secret. + /// + ///+ /// An authentication secret. + /// + public struct AuthenticationSecret + { + ///+ /// Get the starting offset of the secret within a buffer. + /// + ///+ /// Gets the starting offset of the secret within a buffer. + /// + ///The start offset of the secret. + public int StartIndex { get; private set; } + + ///+ /// Get the length of the secret within a buffer. + /// + ///+ /// Gets the length of the secret within a buffer. + /// + ///The length of the secret. + public int Length { get; private set; } + + ///+ /// Create a new + ///. + /// + /// Creates a new + /// The start index of the secret. + /// The length of the secret. + public AuthenticationSecret (int startIndex, int length) + { + StartIndex = startIndex; + Length = length; + } + } + + ///. + /// + /// An interface for detecting authentication secrets. + /// + ///+ /// An interface for detecting authentication secrets. + /// + public interface IAuthenticationSecretDetector + + { + ///+ /// Detect a list of secrets within a buffer. + /// + ///+ /// Detects a list of secrets within a buffer. + /// + /// The buffer. + /// The buffer offset. + /// The length of the buffer. + ///A list of secrets. + IListDetectSecrets (byte[] buffer, int offset, int count); + } +} diff --git a/MailKit/IFetchRequest.cs b/MailKit/IFetchRequest.cs new file mode 100644 index 0000000000..30e984d4f0 --- /dev/null +++ b/MailKit/IFetchRequest.cs @@ -0,0 +1,86 @@ +// +// IFetchRequest.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2013-2026 .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +namespace MailKit { + /// + /// A request for fetching various properties of a message. + /// + ///+ /// A request for fetching various properties of a message. + /// + public interface IFetchRequest + { + ///+ /// Get or set the mod-sequence value that indicates the last known state of the messages being requested. + /// + ///+ /// + ///Gets or sets the mod-sequence value that indicates the last known state of the messages being requested. + ///If this property is set, the results returned by Fetch + /// or FetchAsync will only include the message summaries which + /// have a higher mod-sequence value than the one specified. + ///If the mail store supports quick resynchronization and the application has enabled this feature via + /// + ///, then the Fetch or FetchAsync method + /// will emit events for messages that were expunged from the folder after + /// the change specified by the mod-sequence value. It should be noted that if another client has modified any message in the folder, the mail service may choose + /// to return information that was not explicitly requested. It is therefore important to be prepared to handle both + /// additional fields on a + ///for messages that were requested as well as summaries for + /// messages that were not requested at all. The mod-sequence value that indicates the last known state of the messages being requested. + ulong? ChangedSince { get; set; } + + ///+ /// Get or set the message summary items to fetch. + /// + ///+ /// Gets or sets the message summary items to fetch. + /// + ///The message summary items. + MessageSummaryItems Items { get; set; } + + ///+ /// Get the set of headers that will be fetched. + /// + ///+ /// Gets the set of headers that will be fetched. + /// + ///The set of headers to be fetched. + HeaderSet? Headers { get; } + +#if ENABLE_LAZY_PREVIEW_API + ///+ /// Get the options to use when fetching + ///. + /// + /// + PreviewOptions PreviewOptions { get; } +#endif + } +} diff --git a/MailKit/IMailFolder.cs b/MailKit/IMailFolder.cs index 02ebcb5ab4..ad82962194 100644 --- a/MailKit/IMailFolder.cs +++ b/MailKit/IMailFolder.cs @@ -1,9 +1,9 @@ -// +// // IMailFolder.cs // // Author: Jeffrey StedfastGets the options to use when fetching + ///. These options are only used if + ///includes the + /// value. // -// Copyright (c) 2013-2017 Xamarin Inc. (www.xamarin.com) +// Copyright (c) 2013-2026 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -33,6 +33,12 @@ using MimeKit; using MailKit.Search; +#if NET5_0_OR_GREATER +using IReadOnlySetOfStrings = System.Collections.Generic.IReadOnlySet ; +#else +using IReadOnlySetOfStrings = System.Collections.Generic.ISet ; +#endif + namespace MailKit { /// /// An interface for a mailbox folder as used by . @@ -58,7 +64,7 @@ public interface IMailFolder : IEnumerable /// Root-level folders do not have a parent folder. /// /// The parent folder. - IMailFolder ParentFolder { get; } + IMailFolder? ParentFolder { get; } ////// Get the folder attributes. @@ -69,15 +75,58 @@ public interface IMailFolder : IEnumerable /// The folder attributes. FolderAttributes Attributes { get; } + ///+ /// Get the annotation access level. + /// + ///+ /// If annotations are supported, this property can be used to determine whether or not + /// the + ///supports reading and writing annotations. + /// The annotation access level. + AnnotationAccess AnnotationAccess { get; } + + ///+ /// Get the supported annotation scopes. + /// + ///+ /// If annotations are supported, this property can be used to determine which + /// annotation scopes are supported by the + ///. + /// The supported annotation scopes. + AnnotationScope AnnotationScopes { get; } + + ///+ /// Get the maximum size of annotation values supported by the folder. + /// + ///+ /// If annotations are supported, this property can be used to determine the + /// maximum size of annotation values supported by the + ///. + /// The maximum size of annotation values supported by the folder. + uint MaxAnnotationSize { get; } + ////// Get the permanent flags. /// ///- /// The permanent flags are the message flags that will persist between sessions. + /// ///The permanent flags are the message flags that will persist between sessions. + ///If the ///flag is set, then the folder allows + /// storing of user-defined (custom) message flags. The permanent flags. MessageFlags PermanentFlags { get; } + ///+ /// Get the permanent keywords. + /// + ///+ /// + ///The permanent keywords are the keywords that will persist between sessions. + ///If the + ///flag is set in , + /// then the folder allows storing of user-defined keywords as well. The permanent keywords. + IReadOnlySetOfStrings PermanentKeywords { get; } + ////// Get the accepted flags. /// @@ -89,6 +138,17 @@ public interface IMailFolder : IEnumerable/// The accepted flags. MessageFlags AcceptedFlags { get; } + ///+ /// Get the accepted keywords. + /// + ///+ /// The accepted keywords are the keywords that will be accepted and persist + /// for the current session. For the set of keywords that will persist between + /// sessions, see the + ///property. + /// The accepted keywords. + IReadOnlySetOfStrings AcceptedKeywords { get; } + ////// Get the directory separator. /// @@ -113,7 +173,7 @@ public interface IMailFolder : IEnumerable/// /// Gets whether or not the folder is a namespace folder. /// - ///+ /// true if the folder is a namespace folder; otherwise,false .bool IsNamespace { get; } /// if the folder is a namespace folder; otherwise, . @@ -134,13 +194,26 @@ public interface IMailFolder : IEnumerable /// The name of the folder. string Name { get; } + ///+ /// Get the unique identifier for the folder, if available. + /// + ///+ /// + ///Gets a unique identifier for the folder, if available. This is useful for clients + /// implementing a message cache that want to track the folder after it is renamed by another + /// client. + ///This property will only be available if the server supports the + /// OBJECTID extension. + ///The unique folder identifier. + string? Id { get; } + ////// Get whether or not the folder is subscribed. /// ////// Gets whether or not the folder is subscribed. /// - ///+ /// true if the folder is subscribed; otherwise,false .bool IsSubscribed { get; } /// if the folder is subscribed; otherwise, . @@ -149,27 +222,26 @@ public interface IMailFolder : IEnumerable /// /// Gets whether or not the folder is currently open. /// - ///+ /// true if the folder is currently open; otherwise,false .bool IsOpen { get; } /// if the folder is currently open; otherwise, . - /// Get whether or not the folder exists. + /// Get whether or not the folder can be opened. /// ///- /// Gets whether or not the folder exists. + /// Gets whether or not the folder can be opened. /// - ///- bool Exists { get; } + /// true if the folder exists; otherwise,false .+ bool CanOpen { get; } /// if the folder can be opened; otherwise, . - /// Get whether or not the folder supports mod-sequences. + /// Get whether or not the folder exists. /// ///- /// If mod-sequences are not supported by the folder, then all of the APIs that take a modseq - /// argument will throw - ///and should not be used. + /// Gets whether or not the folder exists. /// - bool SupportsModSeq { get; } + /// true if supports mod-sequences; otherwise,false .+ bool Exists { get; } /// if the folder exists; otherwise, . /// Get the highest mod-sequence value of all messages in the mailbox. @@ -211,13 +283,26 @@ public interface IMailFolder : IEnumerable /// The append limit. uint? AppendLimit { get; } + ///+ /// Get the size of the folder. + /// + ///+ /// + ///Gets the size of the folder in bytes. + ///If the value is not set, then the size is unspecified. + ///The size of the folder, in bytes. + ulong? Size { get; } + ////// Get the index of the first unread message in the folder. /// ///- /// This value will only be set after the folder has been opened. + /// - ///Gets the index of the first unread message in the folder. + ////// This value will only be set after the folder has been opened. + ///A value of -1 indicates that there are no unread messages in the folder or that the server + /// has not provided the index of the first unread message.The index of the first unread message. + ///The index of the first unread message or int FirstUnread { get; } ///-1 if there are no unread messages in the folder.@@ -251,9 +336,31 @@ public interface IMailFolder : IEnumerable /// /// Gets the total number of messages in the folder. /// + ///+ /// ///+ ///The total number of messages. int Count { get; } + ///+ /// Get the threading algorithms supported by the folder. + /// + ///+ /// Gets the threading algorithms supported by the folder. + /// + ///The supported threading algorithms. + HashSetThreadingAlgorithms { get; } + + /// + /// Determine whether or not an + ///supports a feature. + /// + /// Determines whether or not an + /// The desired feature. + ///supports a feature. + /// + bool Supports (FolderFeature feature); + /// if the feature is supported; otherwise, . /// Opens the folder using the requested folder access. /// @@ -271,7 +378,40 @@ public interface IMailFolder : IEnumerable/// The last known value. /// The last known list of unique message identifiers. /// The cancellation token. - FolderAccess Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is not a valid value. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///does not exist. + /// + /// The quick resynchronization feature has not been enabled. + /// + ///+ /// The mail store does not support the quick resynchronization feature. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + FolderAccess Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IListuids, CancellationToken cancellationToken = default); /// /// Asynchronously opens the folder using the requested folder access. @@ -290,7 +430,40 @@ public interface IMailFolder : IEnumerable /// The last known value. /// The last known list of unique message identifiers. /// The cancellation token. - Task OpenAsync (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is not a valid value. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///does not exist. + /// + /// The quick resynchronization feature has not been enabled. + /// + ///+ /// The mail store does not support the quick resynchronization feature. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskOpenAsync (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken = default); /// /// Open the folder using the requested folder access. @@ -301,7 +474,34 @@ public interface IMailFolder : IEnumerable /// The /// The requested folder access. /// The cancellation token. - FolderAccess Open (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken)); + ///state of the folder. + /// + ///is not a valid value. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + FolderAccess Open (FolderAccess access, CancellationToken cancellationToken = default); ////// Asynchronously open the folder using the requested folder access. @@ -312,7 +512,34 @@ public interface IMailFolder : IEnumerable /// The /// The requested folder access. /// The cancellation token. - Taskstate of the folder. OpenAsync (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is not a valid value. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskOpenAsync (FolderAccess access, CancellationToken cancellationToken = default); /// /// Close the folder, optionally expunging the messages marked for deletion. @@ -320,9 +547,33 @@ public interface IMailFolder : IEnumerable /// /// Closes the folder, optionally expunging the messages marked for deletion. /// - /// If set totrue , expunge. - /// The cancellation token. - void Close (bool expunge = false, CancellationToken cancellationToken = default (CancellationToken)); + /// If set to, expunge. + /// The cancellation token. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder is not currently open. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Close (bool expunge = false, CancellationToken cancellationToken = default); ////// Asynchronously close the folder, optionally expunging the messages marked for deletion. @@ -331,9 +582,33 @@ public interface IMailFolder : IEnumerable /// Asynchronously closes the folder, optionally expunging the messages marked for deletion. /// /// An asynchronous task context. - /// If set totrue , expunge. - /// The cancellation token. - Task CloseAsync (bool expunge = false, CancellationToken cancellationToken = default (CancellationToken)); + /// If set to, expunge. + /// The cancellation token. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder is not currently open. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task CloseAsync (bool expunge = false, CancellationToken cancellationToken = default); ////// Create a new subfolder with the given name. @@ -343,9 +618,39 @@ public interface IMailFolder : IEnumerable /// /// The created folder. /// The name of the folder to create. - ///true if the folder will be used to contain messages; otherwisefalse . - /// The cancellation token. - IMailFolder Create (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken)); + ///if the folder will be used to contain messages; otherwise, . + /// The cancellation token. + /// + /// + ///is . + /// + /// + ///is empty. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///is nil, and thus child folders cannot be created. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + IMailFolder? Create (string name, bool isMessageFolder, CancellationToken cancellationToken = default); ////// Asynchronously create a new subfolder with the given name. @@ -355,9 +660,39 @@ public interface IMailFolder : IEnumerable /// /// The created folder. /// The name of the folder to create. - ///true if the folder will be used to contain messages; otherwisefalse . - /// The cancellation token. - TaskCreateAsync (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken)); + /// if the folder will be used to contain messages; otherwise, . + /// The cancellation token. + /// + /// + ///is . + /// + /// + ///is empty. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///is nil, and thus child folders cannot be created. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskCreateAsync (string name, bool isMessageFolder, CancellationToken cancellationToken = default); /// /// Create a new subfolder with the given name. @@ -369,7 +704,42 @@ public interface IMailFolder : IEnumerable /// The name of the folder to create. /// A list of special uses for the folder being created. /// The cancellation token. - IMailFolder Create (string name, IEnumerable specialUses, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// + ///is empty. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///is nil, and thus child folders cannot be created. + /// + /// The + ///does not support the creation of special folders. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + IMailFolder? Create (string name, IEnumerablespecialUses, CancellationToken cancellationToken = default); /// /// Asynchronously create a new subfolder with the given name. @@ -381,7 +751,42 @@ public interface IMailFolder : IEnumerable /// The name of the folder to create. /// A list of special uses for the folder being created. /// The cancellation token. - Task CreateAsync (string name, IEnumerable specialUses, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// + ///is empty. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///is nil, and thus child folders cannot be created. + /// + /// The + ///does not support the creation of special folders. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskCreateAsync (string name, IEnumerable specialUses, CancellationToken cancellationToken = default); /// /// Create a new subfolder with the given name. @@ -393,7 +798,40 @@ public interface IMailFolder : IEnumerable /// The name of the folder to create. /// The special use for the folder being created. /// The cancellation token. - IMailFolder Create (string name, SpecialFolder specialUse, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is . + /// + /// + ///is empty. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///is nil, and thus child folders cannot be created. + /// + /// The + ///does not support the creation of special folders. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + IMailFolder? Create (string name, SpecialFolder specialUse, CancellationToken cancellationToken = default); ////// Asynchronously create a new subfolder with the given name. @@ -405,7 +843,40 @@ public interface IMailFolder : IEnumerable /// The name of the folder to create. /// The special use for the folder being created. /// The cancellation token. - Task CreateAsync (string name, SpecialFolder specialUse, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is . + /// + /// + ///is empty. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///is nil, and thus child folders cannot be created. + /// + /// The + ///does not support the creation of special folders. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskCreateAsync (string name, SpecialFolder specialUse, CancellationToken cancellationToken = default); /// /// Rename the folder. @@ -416,7 +887,44 @@ public interface IMailFolder : IEnumerable /// The new parent folder. /// The new name of the folder. /// The cancellation token. - void Rename (IMailFolder parent, string name, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// + ///+ /// does not belong to the . -or- + ///+ /// is not a legal folder name. + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder cannot be renamed (it is either a namespace or the Inbox). + /// + ///+ /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Rename (IMailFolder parent, string name, CancellationToken cancellationToken = default); ////// Asynchronously rename the folder. @@ -428,7 +936,44 @@ public interface IMailFolder : IEnumerable /// The new parent folder. /// The new name of the folder. /// The cancellation token. - Task RenameAsync (IMailFolder parent, string name, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// + ///+ /// does not belong to the . -or- + ///+ /// is not a legal folder name. + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder cannot be renamed (it is either a namespace or the Inbox). + /// + ///+ /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task RenameAsync (IMailFolder parent, string name, CancellationToken cancellationToken = default); ////// Delete the folder. @@ -437,7 +982,34 @@ public interface IMailFolder : IEnumerable /// Deletes the folder. /// /// The cancellation token. - void Delete (CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder cannot be deleted (it is either a namespace or the Inbox). + /// + ///+ /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Delete (CancellationToken cancellationToken = default); ////// Asynchronously delete the folder. @@ -447,7 +1019,34 @@ public interface IMailFolder : IEnumerable /// /// An asynchronous task context. /// The cancellation token. - Task DeleteAsync (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder cannot be deleted (it is either a namespace or the Inbox). + /// + ///+ /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task DeleteAsync (CancellationToken cancellationToken = default); ////// Subscribe to the folder. @@ -456,7 +1055,28 @@ public interface IMailFolder : IEnumerable /// Subscribes to the folder. /// /// The cancellation token. - void Subscribe (CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Subscribe (CancellationToken cancellationToken = default); ////// Asynchronously subscribe to the folder. @@ -466,7 +1086,34 @@ public interface IMailFolder : IEnumerable /// /// An asynchronous task context. /// The cancellation token. - Task SubscribeAsync (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder cannot be deleted (it is either a namespace or the Inbox). + /// + ///+ /// The + ///does not exist. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task SubscribeAsync (CancellationToken cancellationToken = default); ////// Unsubscribe from the folder. @@ -475,7 +1122,28 @@ public interface IMailFolder : IEnumerable /// Unsubscribes from the folder. /// /// The cancellation token. - void Unsubscribe (CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Unsubscribe (CancellationToken cancellationToken = default); ////// Asynchronously unsubscribe from the folder. @@ -485,7 +1153,28 @@ public interface IMailFolder : IEnumerable /// /// An asynchronous task context. /// The cancellation token. - Task UnsubscribeAsync (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task UnsubscribeAsync (CancellationToken cancellationToken = default); ////// Get the subfolders. @@ -499,9 +1188,30 @@ public interface IMailFolder : IEnumerable /// /// The subfolders. /// The status items to pre-populate. - /// If set totrue , only subscribed folders will be listed. - /// The cancellation token. - IEnumerableGetSubfolders (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)); + /// If set to , only subscribed folders will be listed. + /// The cancellation token. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + IListGetSubfolders (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default); /// /// Asynchronously get the subfolders. @@ -514,9 +1224,30 @@ public interface IMailFolder : IEnumerable /// /// The subfolders. /// The status items to pre-populate. - /// If set totrue , only subscribed folders will be listed. - /// The cancellation token. - Task> GetSubfoldersAsync (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)); + /// If set to , only subscribed folders will be listed. + /// The cancellation token. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task> GetSubfoldersAsync (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default); /// /// Get the subfolders. @@ -525,9 +1256,30 @@ public interface IMailFolder : IEnumerable /// Gets the subfolders. /// /// The subfolders. - /// If set totrue , only subscribed folders will be listed. - /// The cancellation token. - IEnumerableGetSubfolders (bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)); + /// If set to , only subscribed folders will be listed. + /// The cancellation token. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + IListGetSubfolders (bool subscribedOnly = false, CancellationToken cancellationToken = default); /// /// Asynchronously get the subfolders. @@ -536,9 +1288,30 @@ public interface IMailFolder : IEnumerable /// Asynchronously gets the subfolders. /// /// The subfolders. - /// If set totrue , only subscribed folders will be listed. - /// The cancellation token. - Task> GetSubfoldersAsync (bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)); + /// If set to , only subscribed folders will be listed. + /// The cancellation token. + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task> GetSubfoldersAsync (bool subscribedOnly = false, CancellationToken cancellationToken = default); /// /// Get the specified subfolder. @@ -549,7 +1322,37 @@ public interface IMailFolder : IEnumerable /// The subfolder. /// The name of the subfolder. /// The cancellation token. - IMailFolder GetSubfolder (string name, CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// + ///is . + /// + /// + ///is either an empty string or contains the . + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The requested folder could not be found. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + IMailFolder GetSubfolder (string name, CancellationToken cancellationToken = default); ////// Asynchronously get the specified subfolder. @@ -560,7 +1363,37 @@ public interface IMailFolder : IEnumerable /// The subfolder. /// The name of the subfolder. /// The cancellation token. - TaskGetSubfolderAsync (string name, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is . + /// + /// + ///is either an empty string or contains the . + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The requested folder could not be found. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskGetSubfolderAsync (string name, CancellationToken cancellationToken = default); /// /// Force the server to flush its state for the folder. @@ -569,7 +1402,31 @@ public interface IMailFolder : IEnumerable /// Forces the server to flush its state for the folder. /// /// The cancellation token. - void Check (CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder is not currently open. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Check (CancellationToken cancellationToken = default); ////// Asynchronously force the server to flush its state for the folder. @@ -579,7 +1436,31 @@ public interface IMailFolder : IEnumerable /// /// An asynchronous task context. /// The cancellation token. - Task CheckAsync (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The folder is not currently open. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task CheckAsync (CancellationToken cancellationToken = default); ////// Update the values of the specified items. @@ -596,7 +1477,34 @@ public interface IMailFolder : IEnumerable /// /// The items to update. /// The cancellation token. - void Status (StatusItems items, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///does not exist. + /// + /// The mail store does not support the STATUS command. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void Status (StatusItems items, CancellationToken cancellationToken = default); ////// Asynchronously update the values of the specified items. @@ -614,7 +1522,34 @@ public interface IMailFolder : IEnumerable /// An asynchronous task context. /// The items to update. /// The cancellation token. - Task StatusAsync (StatusItems items, CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The + ///does not exist. + /// + /// The mail store does not support the STATUS command. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task StatusAsync (StatusItems items, CancellationToken cancellationToken = default); ////// Get the complete access control list for the folder. @@ -624,7 +1559,31 @@ public interface IMailFolder : IEnumerable /// /// The access control list. /// The cancellation token. - AccessControlList GetAccessControlList (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + AccessControlList GetAccessControlList (CancellationToken cancellationToken = default); ////// Asynchronously get the complete access control list for the folder. @@ -634,7 +1593,31 @@ public interface IMailFolder : IEnumerable /// /// The access control list. /// The cancellation token. - TaskGetAccessControlListAsync (CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskGetAccessControlListAsync (CancellationToken cancellationToken = default); /// /// Get the access rights for a particular identifier. @@ -645,7 +1628,34 @@ public interface IMailFolder : IEnumerable /// The access rights. /// The identifier name. /// The cancellation token. - AccessRights GetAccessRights (string name, CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// + ///is . + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + AccessRights GetAccessRights (string name, CancellationToken cancellationToken = default); ////// Asynchronously get the access rights for a particular identifier. @@ -656,7 +1666,34 @@ public interface IMailFolder : IEnumerable /// The access rights. /// The identifier name. /// The cancellation token. - TaskGetAccessRightsAsync (string name, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///is . + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskGetAccessRightsAsync (string name, CancellationToken cancellationToken = default); /// /// Get the access rights for the current authenticated user. @@ -666,7 +1703,31 @@ public interface IMailFolder : IEnumerable /// /// The access rights. /// The cancellation token. - AccessRights GetMyAccessRights (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + AccessRights GetMyAccessRights (CancellationToken cancellationToken = default); ////// Asynchronously get the access rights for the current authenticated user. @@ -676,7 +1737,31 @@ public interface IMailFolder : IEnumerable /// /// The access rights. /// The cancellation token. - TaskGetMyAccessRightsAsync (CancellationToken cancellationToken = default (CancellationToken)); + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + TaskGetMyAccessRightsAsync (CancellationToken cancellationToken = default); /// /// Add access rights for the specified identity. @@ -687,7 +1772,36 @@ public interface IMailFolder : IEnumerable /// The identity name. /// The access rights. /// The cancellation token. - void AddAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void AddAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default); ////// Asynchronously add access rights for the specified identity. @@ -699,7 +1813,36 @@ public interface IMailFolder : IEnumerable /// The identity name. /// The access rights. /// The cancellation token. - Task AddAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task AddAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default); ////// Remove access rights for the specified identity. @@ -710,7 +1853,36 @@ public interface IMailFolder : IEnumerable /// The identity name. /// The access rights. /// The cancellation token. - void RemoveAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void RemoveAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default); ////// Asynchronously remove access rights for the specified identity. @@ -722,7 +1894,36 @@ public interface IMailFolder : IEnumerable /// The identity name. /// The access rights. /// The cancellation token. - Task RemoveAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task RemoveAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default); ////// Set the access rights for the specified identity. @@ -733,10 +1934,39 @@ public interface IMailFolder : IEnumerable /// The identity name. /// The access rights. /// The cancellation token. - void SetAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)); - - /// - /// Asynchronously set the access rights for the sepcified identity. + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void SetAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default); + + ///+ /// Asynchronously set the access rights for the specified identity. /// ////// Asynchronously sets the access rights for the specified identity. @@ -745,7 +1975,36 @@ public interface IMailFolder : IEnumerable /// The identity name. /// The cancellation token. - void RemoveAccess (string name, CancellationToken cancellationToken = default (CancellationToken)); + ////// The identity name. /// The access rights. /// The cancellation token. - Task SetAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)); + /// + /// + ///+ /// is . -or- + ///+ /// is . + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task SetAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default); ////// Remove all access rights for the given identity. @@ -755,7 +2014,34 @@ public interface IMailFolder : IEnumerable /// + /// + ///is . + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + void RemoveAccess (string name, CancellationToken cancellationToken = default); ////// Asynchronously remove all access rights for the given identity. @@ -766,7 +2052,34 @@ public interface IMailFolder : IEnumerable /// An asynchronous task context. /// The identity name. /// The cancellation token. - Task RemoveAccessAsync (string name, CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// + ///is . + /// + /// The + ///has been disposed. + /// + /// The + ///is not connected. + /// + /// The + ///is not authenticated. + /// + /// The mail store does not support the ACL extension. + /// + ///+ /// The operation was canceled via the cancellation token. + /// + ///+ /// An I/O error occurred. + /// + ///+ /// The server's response contained unexpected tokens. + /// + ///+ /// The command failed. + /// + Task RemoveAccessAsync (string name, CancellationToken cancellationToken = default); ////// Get the quota information for the folder. @@ -778,7 +2091,31 @@ public interface IMailFolder : IEnumerable /// /// The folder quota. /// The cancellation token. - FolderQuota GetQuota (CancellationToken cancellationToken = default (CancellationToken)); + ///+ /// The