diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..cab8e6cebec --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.rc diff diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..d88b900d042 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Report errors or unexpected behavior +title: '' +labels: '' +assignees: '' + +--- + + + +# Environment + +``` +Windows build number: [run "ver" at a command prompt] +PowerToys version: +PowerToy module for which you are reporting the bug (if applicable): +``` + +# Steps to reproduce + + + +# Expected behavior + + + +# Actual behavior + + + +# Screenshots + + diff --git a/.github/ISSUE_TEMPLATE/documentation-issue.md b/.github/ISSUE_TEMPLATE/documentation-issue.md new file mode 100644 index 00000000000..39e6e5599c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-issue.md @@ -0,0 +1,10 @@ +--- +name: Documentation Issue +about: Report issues in our documentation +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..66a856efe00 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +# Summary of the new feature/enhancement + + + +# Proposed technical implementation details (optional) + + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000000..e61d054d878 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ + +## Summary of the Pull Request + + +## References + + +## PR Checklist +* [ ] Closes #xxx +* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/PowerToys) and sign the CLA +* [ ] Tests added/passed +* [ ] Requires documentation to be updated +* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx + + +## Detailed Description of the Pull Request / Additional comments + + +## Validation Steps Performed diff --git a/.pipelines/build.cmd b/.pipelines/build.cmd new file mode 100644 index 00000000000..632efb6e8db --- /dev/null +++ b/.pipelines/build.cmd @@ -0,0 +1,2 @@ +cd /D "%~dp0" +dotnet build --no-restore ..\PowerToys.sln || exit /b 1 diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml new file mode 100644 index 00000000000..d6c8a243594 --- /dev/null +++ b/.pipelines/pipeline.user.windows.yml @@ -0,0 +1,43 @@ +environment: + host: + os: 'windows' + flavor: 'server' + version: '2016' + runtime: + provider: 'appcontainer' + image: 'cdpxwinrs4test.azurecr.io/global/vse2017u7-external-azsdk-mobile-ext-win1803:latest-nodetools' + source_mode: 'link' + +signing_options: + profile: 'azure' + +package_sources: + nuget: + feeds: + 'Toolset': 'https://msazure.pkgs.visualstudio.com/_packaging/Toolset/nuget/v3/index.json' + 'CloudES-CDP': 'https://cloudes.pkgs.visualstudio.com/_packaging/CDP/nuget/v3/index.json' + 'CloudES-Internal': 'https://cloudes.pkgs.visualstudio.com/_packaging/Internal/nuget/v3/index.json' + 'MsNugetMirror': 'https://msazure.pkgs.visualstudio.com/_packaging/MsNugetMirror/nuget/v3/index.json' + 'NugetMirror': 'https://msazure.pkgs.visualstudio.com/_packaging/NugetMirror/nuget/v3/index.json' + 'CorextMirror': 'https://msazure.pkgs.visualstudio.com/_packaging/CorextMirror/nuget/v3/index.json' + 'Official': 'https://msazure.pkgs.visualstudio.com/_packaging/Official/nuget/v3/index.json' + 'Toolset': 'https://msazure.pkgs.visualstudio.com/_packaging/Toolset/nuget/v3/index.json' + 'AzureCXP': 'https://msazure.pkgs.visualstudio.com/_packaging/AzureCXP/nuget/v3/index.json' + +restore: + commands: + - !!defaultcommand + name: 'Restore CSharp' + command: '.pipelines\restore.cmd' + +build: + commands: + - !!buildcommand + name: 'Build CSharp' + command: '.pipelines\build.cmd' + artifacts: + - from: '**\bin' + to: 'Build_Output' + include: + - '**/*' + diff --git a/.pipelines/restore.cmd b/.pipelines/restore.cmd new file mode 100644 index 00000000000..3a9d719a4eb --- /dev/null +++ b/.pipelines/restore.cmd @@ -0,0 +1,3 @@ +cd /D "%~dp0" + +dotnet restore ..\PowerToys.sln || exit /b 1 diff --git a/License.rtf b/License.rtf new file mode 100644 index 00000000000..204a30d029b Binary files /dev/null and b/License.rtf differ diff --git a/PowerToys.sln b/PowerToys.sln new file mode 100644 index 00000000000..19190ff9ac5 --- /dev/null +++ b/PowerToys.sln @@ -0,0 +1,120 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.452 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}" + ProjectSection(ProjectDependencies) = postProject + {48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227} + {74485049-C722-400F-ABE5-86AC52D929B3} = {74485049-C722-400F-ABE5-86AC52D929B3} + {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} + {07C389E3-6BC8-41CF-923E-307B1265FA2D} = {07C389E3-6BC8-41CF-923E-307B1265FA2D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "src\common\common.vcxproj", "{74485049-C722-400F-ABE5-86AC52D929B3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shortcut_guide", "src\modules\shortcut_guide\shortcut_guide.vcxproj", "{A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_powertoy", "src\modules\example_powertoy\example_powertoy.vcxproj", "{44CC9375-3E6E-4D99-8913-7FB748807EBD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{4574FDD0-F61D-4376-98BF-E5A1262C11EC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interface", "interface", "{3BB8493E-D18E-4485-A320-CB40F90F55AE}" + ProjectSection(SolutionItems) = preProject + src\modules\interface\lowlevel_keyboard_event_data.h = src\modules\interface\lowlevel_keyboard_event_data.h + src\modules\interface\powertoy_module_interface.h = src\modules\interface\powertoy_module_interface.h + src\modules\interface\win_hook_event_data.h = src\modules\interface\win_hook_event_data.h + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "settings", "src\editor\settings.vcxproj", "{07C389E3-6BC8-41CF-923E-307B1265FA2D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fancyzones", "fancyzones", "{D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesLib", "src\modules\fancyzones\lib\FancyZonesLib.vcxproj", "{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fancyzones", "src\modules\fancyzones\dll\FancyZonesModule.vcxproj", "{48804216-2A0E-4168-A6D8-9CD068D14227}" + ProjectSection(ProjectDependencies) = postProject + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests-FancyZones", "src\modules\fancyzones\tests\UnitTests\UnitTests.vcxproj", "{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpprestsdk", "deps\cpprestsdk\cpprestsdk.vcxproj", "{4E577735-DFAB-41AF-8A6E-B6E8872A2928}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deps", "deps", "{1FAF749F-0D6F-4BF5-A563-31A4B5279D27}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{1AFB6476-670D-4E80-A464-657E01DFF482}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests-CommonLib", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.ActiveCfg = Debug|x64 + {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.Build.0 = Debug|x64 + {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.ActiveCfg = Release|x64 + {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.Build.0 = Release|x64 + {74485049-C722-400F-ABE5-86AC52D929B3}.Debug|x64.ActiveCfg = Debug|x64 + {74485049-C722-400F-ABE5-86AC52D929B3}.Debug|x64.Build.0 = Debug|x64 + {74485049-C722-400F-ABE5-86AC52D929B3}.Release|x64.ActiveCfg = Release|x64 + {74485049-C722-400F-ABE5-86AC52D929B3}.Release|x64.Build.0 = Release|x64 + {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Debug|x64.ActiveCfg = Debug|x64 + {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Debug|x64.Build.0 = Debug|x64 + {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Release|x64.ActiveCfg = Release|x64 + {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Release|x64.Build.0 = Release|x64 + {44CC9375-3E6E-4D99-8913-7FB748807EBD}.Debug|x64.ActiveCfg = Debug|x64 + {44CC9375-3E6E-4D99-8913-7FB748807EBD}.Release|x64.ActiveCfg = Release|x64 + {07C389E3-6BC8-41CF-923E-307B1265FA2D}.Debug|x64.ActiveCfg = Debug|x64 + {07C389E3-6BC8-41CF-923E-307B1265FA2D}.Debug|x64.Build.0 = Debug|x64 + {07C389E3-6BC8-41CF-923E-307B1265FA2D}.Release|x64.ActiveCfg = Release|x64 + {07C389E3-6BC8-41CF-923E-307B1265FA2D}.Release|x64.Build.0 = Release|x64 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.ActiveCfg = Debug|x64 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.Build.0 = Debug|x64 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.ActiveCfg = Release|x64 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.Build.0 = Release|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.ActiveCfg = Debug|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.Build.0 = Debug|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.ActiveCfg = Release|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.Build.0 = Release|x64 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.ActiveCfg = Debug|x64 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.Build.0 = Debug|x64 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.ActiveCfg = Release|x64 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.Build.0 = Release|x64 + {4E577735-DFAB-41AF-8A6E-B6E8872A2928}.Debug|x64.ActiveCfg = Debug|x64 + {4E577735-DFAB-41AF-8A6E-B6E8872A2928}.Debug|x64.Build.0 = Debug|x64 + {4E577735-DFAB-41AF-8A6E-B6E8872A2928}.Release|x64.ActiveCfg = Release|x64 + {4E577735-DFAB-41AF-8A6E-B6E8872A2928}.Release|x64.Build.0 = Release|x64 + {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x64.ActiveCfg = Debug|x64 + {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x64.Build.0 = Debug|x64 + {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64 + {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.Build.0 = Debug|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64 + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {74485049-C722-400F-ABE5-86AC52D929B3} = {1AFB6476-670D-4E80-A464-657E01DFF482} + {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {44CC9375-3E6E-4D99-8913-7FB748807EBD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {3BB8493E-D18E-4485-A320-CB40F90F55AE} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} + {48804216-2A0E-4168-A6D8-9CD068D14227} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} + {4E577735-DFAB-41AF-8A6E-B6E8872A2928} = {1FAF749F-0D6F-4BF5-A563-31A4B5279D27} + {1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482} + {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index e61df461949..adeaa3b0e81 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,107 @@ - -# Overview - -PowerToys is a set of utilities for power users to tune and streamline their Windows experience for greater productivity. - -Inspired by the [Windows 95 era PowerToys project](https://en.wikipedia.org/wiki/Microsoft_PowerToys), this reboot provides power users with ways to squeeze more efficiency out of the Windows 10 shell and customize it for individual workflows. A great overview of the Windows 95 PowerToys can be found [here](https://socket3.wordpress.com/2016/10/22/using-windows-95-powertoys/). - -The first preview of these utilities and corresponding source code will be released Summer 2019. - -![logo](Logo.jpg) - -# What's Happening - -## June Update -Since the announcement of the PowerToys reboot at BUILD, the interest in the project has been incredible to see. Due to the excitement we are optimizing the first preview to make it easy to integrate new utilities into the repo. We also have two interns working on additional PowerToys. The specs for these are: - -* [Process terminate tool](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/Terminate%20Spec.md) -* [Batch file renamer](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/File%20Classification%20Spec.md) -* [Animated gif screen recorder](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/GIF%20Maker%20Spec.md) - -Finally, we are organizing a team to productize an internal window manager into the PowerToys project for the 2019 [One Week Hackathon](https://www.onmsft.com/news/take-a-peek-inside-microsofts-recent-one-week-hackathon). - -We are still targeting to release the preview and code during Summer 2019. - -## The first two utilities we're working on are: - -1. Maximize to new desktop widget - The MTND widget shows a pop-up button when a user hovers over the maximize / restore button on any window. Clicking it creates a new desktop, sends the app to that desktop and maximizes the app on the new desktop. - -![Maximize to new desktop widget](MTNDWidget.jpg) - -2. Windows key shortcut guide - The shortcut guide appears when a user holds the Windows key down for more than one second and shows the available shortcuts for the current state of the desktop. - -![Windows key shortcut guide](WindowsKeyShortcutGuide.jpg) - -# Backlog - -Here's the current set of utilities we're considering. Please use issues and +1's to guide the project to suggest new ideas and help us prioritize the list below. - -1. [Full window manager including specific layouts for docking and undocking laptops](https://github.com/microsoft/PowerToys/issues/4) -2. [Keyboard shortcut manager](https://github.com/microsoft/PowerToys/issues/6) -3. [Win+R replacement](https://github.com/microsoft/PowerToys/issues/44) -4. Better Alt+Tab including browser tab integration and search for running apps -5. [Battery tracker](https://github.com/microsoft/PowerToys/issues/7) -6. [Batch file re-namer](https://github.com/microsoft/PowerToys/issues/101) -7. [Quick resolution swaps in taskbar](https://github.com/microsoft/PowerToys/issues/27) -8. Mouse events without focus -9. Cmd (or PS or Bash) from here -10. Contents menu file browsing - -# Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.microsoft.com. - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +# Overview + +PowerToys is a set of utilities for power users to tune and streamline their Windows experience for greater productivity. + +Inspired by the [Windows 95 era PowerToys project](https://en.wikipedia.org/wiki/Microsoft_PowerToys), this reboot provides power users with ways to squeeze more efficiency out of the Windows 10 shell and customize it for individual workflows. A great overview of the Windows 95 PowerToys can be found [here](https://socket3.wordpress.com/2016/10/22/using-windows-95-powertoys/). + +The first preview of these utilities can be installed from [here](https://github.com/Microsoft/powertoys/releases). + +![logo](doc/images/Logo.jpg) + +# What's Happening + +## September Update +The first preview release of the PowerToys utilities and source code is now live! This release includes two preview quality utilities as well as the tools and docs to make it easy to create new PowerToys utilities. + +1. [FancyZones](/src/modules/fancyzones/) - FancyZones is a window manager that makes it easy to create copmlex window layouts and quickly position windows into those layouts. The FancyZones backlog can be found [here](https://github.com/Microsoft/PowerToys/tree/master/doc/planning/FancyZonesBacklog.md) + +![FancyZones](src/modules/fancyzones/FancyZones.png) + +FanzyZones Video Tutorial +[![FancyZones Video Tutorial](doc/images/FZTutorial.jpg)](https://www.youtube.com/watch?v=rTtGzZYAXgY) + +2. [Windows key shortcut guide](/src/modules/shortcut_guide) - The shortcut guide appears when a user holds the Windows key down for more than one second and shows the available shortcuts for the current state of the desktop. The shortcut guide backlog can be found [here](https://github.com/Microsoft/PowerToys/tree/master/doc/planning/ShortcutGuideBacklog.md) + +![Windows key shortcut guide](doc/images/WindowsKeyShortcutGuide.jpg) + +Additional utilities in the pipeline are: + +* Maximize to new desktop widget - The MTND widget shows a pop-up button when a user hovers over the maximize / restore button on any window. Clicking it creates a new desktop, sends the app to that desktop and maximizes the app on the new desktop. +* [Process terminate tool](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/Terminate%20Spec.md) +* [Batch file renamer](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/File%20Classification%20Spec.md) +* [Animated gif screen recorder](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/GIF%20Maker%20Spec.md) + +# Backlog + +The full backlog of utilities can be found [here](https://github.com/Microsoft/PowerToys/tree/master/doc/planning/PowerToysBacklog.md) + +# Where to download PowerToys + + The latest release of PowerToys can be downloaded from https://github.com/microsoft/PowerToys/releases
+ Click on `Assets` to show the files available in the release and then click on `PowerToysSetup.msi` to download the PowerToys installer.
+ PDB symbols for the release are available in a separate zip file `PDB symbols.zip`. + +# Developer Guidance + +## Build Prerequisites + * Windows 10 1803 (build 10.0.17134.0) or above in order to build and run PowerToys. + * Visual Studio 2019 Community edition or higher, with the 'Desktop Development with C++' component and the Windows 10 SDK version 10.0.18362.0 or higher. + +## Building the Code + * Open `powertoys.sln` in Visual Studio, in the `Solutions Configuration` drop-down menu select `Release` or `Debug`, from the `Build` menu choose `Build Solution`. + * The PowerToys binaries will be located in your repo under `x64\Release`. + * If you want to copy the `PowerToys.exe` binary to a different location, you'll also need to copy the `modules` and the `svgs` folders. + +## Prerequisites to Build the Installer + * Install the [WiX Toolset Visual Studio 2019 Extension](https://marketplace.visualstudio.com/items?itemName=RobMensching.WiXToolset). + * Install the [WiX Toolset build tools](https://wixtoolset.org/releases/). + +## Building the .msi Installer + * From the `installer` folder open `PowerToysSetup.sln` in Visual Studio, in the `Solutions Configuration` drop-down menu select `Release` or `Debug`, from the `Build` menu choose `Build Solution`. + * The resulting `PowerToysSetup.msi` installer will be available in the `installer\PowerToysSetup\x64\Release\` folder. + +## Debugging + The following configuration issue only applies if the user is a member of the Administrators group. + + Some PowerToys modules require to run with the highest permission level if the current user is a member of the Administrators group. The highest permission level is required in order to be able to perform some actions when an elevated application (e.g. Task Manager) is in the foreground or is the target of an action. Without elevated privileges some PowerToys modules will still work but with some limitations: + - the `FancyZones` module will be not be able to move an elevated window to a zone. + - the `Shortcut Guide` module will not appear if the foreground window belongs to an elevated application. + + In order to run and debug PowerToys from Visual Studio when the user is a member of the Administrators group, Visual Studio has to be started with elevated privileges. If you want to avoid running Visual Studio with elevated privileges and don't mind the limitations described above, you can do the following: open the `runner` project properties and navigate to the `Linker -> Manifest File` settings, edit the `UAC Execution Level` property and change it from `highestAvailable (/level='highestAvailable')` to `asInvoker (/level='asInvoker')`, save the changes. + +## How to create new PowerToys + +See the instructions on [how to install the PowerToys Module project template](tools/project_template).
+Specifications for the [PowerToys settings API](doc/specs/PowerToys-settings.md). + +## Coding Guidance + +Please review these brief docs below relating to our coding standards etc. + +> 👉 If you find something missing from these docs, feel free to contribute to any of our documentation files anywhere in the repository (or make some new ones\!) + +This is a work in progress as we learn what we'll need to provide people in order to be effective contributors to our project. +- [Coding Style](doc/coding/style.md) +- [Code Organization](doc/coding/organization.md) + +# Contributing +This project welcomes contributions and suggestions and we are excited to work with the power user community to build a set of tools for helping you get the most our of Windows. + +We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](contributing.md). We will be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort. + +> ⚠ **Note**: PowerToys is still a nascent project and the team is actively working out of this repository. We will be periodically re-structuring the code to make it easier to comprehend, navigate, build, test, and contribute to, so **DO expect significant changes to code layout on a regular basis**. + +> ⚠ **License Info**: Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +# Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
+For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments. + +[conduct-code]: https://opensource.microsoft.com/codeofconduct/ +[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/ +[conduct-email]: mailto:opencode@microsoft.com diff --git a/build/pipelines/ci.yml b/build/pipelines/ci.yml new file mode 100644 index 00000000000..85b915c48ca --- /dev/null +++ b/build/pipelines/ci.yml @@ -0,0 +1,24 @@ +trigger: + batch: true + branches: + include: + - master + paths: + exclude: + - doc/* + - temp/* + - tools/* + +pr: + branches: + include: + - master + +# 0.0.yyMM.dd## +# 0.0.1904.0900 +name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr) + +jobs: + - template: ./templates/build-powertoys-ci.yml + parameters: + platform: x64 diff --git a/build/pipelines/templates/build-powertoys-ci.yml b/build/pipelines/templates/build-powertoys-ci.yml new file mode 100644 index 00000000000..bfffbea6f86 --- /dev/null +++ b/build/pipelines/templates/build-powertoys-ci.yml @@ -0,0 +1,17 @@ +parameters: + configuration: 'Release' + platform: '' + additionalBuildArguments: '' + +jobs: +- job: Build${{ parameters.platform }}${{ parameters.configuration }} + displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }} + variables: + BuildConfiguration: ${{ parameters.configuration }} + BuildPlatform: ${{ parameters.platform }} + pool: { vmImage: windows-2019 } + + steps: + - template: build-powertoys-steps.yml + parameters: + additionalBuildArguments: ${{ parameters.additionalBuildArguments }} \ No newline at end of file diff --git a/build/pipelines/templates/build-powertoys-steps.yml b/build/pipelines/templates/build-powertoys-steps.yml new file mode 100644 index 00000000000..f13df444dcd --- /dev/null +++ b/build/pipelines/templates/build-powertoys-steps.yml @@ -0,0 +1,35 @@ +parameters: + additionalBuildArguments: '' + +steps: +- checkout: self + submodules: true + clean: true + +- task: NuGetToolInstaller@0 + displayName: Ensure NuGet 4.8.1 + inputs: + versionSpec: 4.8.1 + +- task: VisualStudioTestPlatformInstaller@1 + displayName: Ensure VSTest Platform + +- task: NuGetCommand@2 + displayName: Restore NuGet packages + inputs: + command: restore + feedsToUse: config + configPath: NuGet.config + restoreSolution: PowerToys.sln + restoreDirectory: '$(Build.SourcesDirectory)\packages' + +- task: VSBuild@1 + displayName: 'Build solution **\PowerToys.sln' + inputs: + solution: '**\PowerToys.sln' + vsVersion: 15.0 + platform: '$(BuildPlatform)' + configuration: '$(BuildConfiguration)' + msbuildArgs: ${{ parameters.additionalBuildArguments }} + clean: true + maximumCpuCount: true diff --git a/contributing.md b/contributing.md new file mode 100644 index 00000000000..948c823af03 --- /dev/null +++ b/contributing.md @@ -0,0 +1,128 @@ +# Power Toys Contributor's Guide + +Below is our guidance for how to report issues, propose new features, and submit contributions via Pull Requests (PRs). + +## Before you start, file an issue + +Please follow this simple rule to help us eliminate any unnecessary wasted effort & frustration, and ensure an efficient and effective use of everyone's time - yours, ours, and other community members': + +> 👉 If you have a question, think you've discovered an issue, would like to propose a new feature, etc., then find/file an issue **BEFORE** starting work to fix/implement it. + +### Search existing issues first + +Before filing a new issue, search existing open and closed issues first: It is likely someone else has found the problem you're seeing, and someone may be working on or have already contributed a fix! + +If no existing item describes your issue/feature, great - please file a new issue: + +### File a new Issue + +* Don't know whether you're reporting an issue or requesting a feature? File an issue +* Have a question that you don't see answered in docs, videos, etc.? File an issue +* Want to know if we're planning on building a particular feature? File an issue +* Got a great idea for a new utility or feature? File an issue/request/idea +* Don't understand how to do something? File an issue/Community Guidance Request +* Found an existing issue that describes yours? Great - upvote and add additional commentary / info / repro-steps / etc. + +### Complete the template + +**Please include as much information as possible in your issue**. The more information you provide, the more likely your issue/ask will be understood and implemented. Helpful information includes: + +* What device you're running (inc. CPU type, memory, disk, etc.) +* What build of Windows your device is running + + 👉 Tip: Run the following in PowerShell Core + + ```powershell + C:\> $PSVersionTable.OS + Microsoft Windows 10.0.18909 + ``` + + ... or in Windows PowerShell + + ```powershell + C:\> $PSVersionTable.BuildVersion + + Major Minor Build Revision + ----- ----- ----- -------- + 10 0 18912 1001 + ``` + + ... or Cmd: + + ```cmd + C:\> ver + + Microsoft Windows [Version 10.0.18900.1001] + ``` + +* What tools and apps you're using (e.g. VS 2019, VSCode, etc.) +* Don't assume we're experts in setting up YOUR environment and don't assume we are experts in YOUR workflow. Teach us to help you! +* **We LOVE detailed repro steps!** What steps do we need to take to reproduce the issue? Assume we love to read repro steps. As much detail as you can stand is probably _barely_ enough detail for us! +* Prefer error message text where possible or screenshots of errors if text cannot be captured +* **If you intend to implement the fix/feature yourself then say so!** If you do not indicate otherwise we will assume that the issue is our to solve, or may label the issue as `Help-Wanted`. + +### DO NOT post "+1" comments + +> ⚠ DO NOT post "+1", "me too", or similar comments - they just add noise to an issue. + +If you don't have any additional info/context to add but would like to indicate that you're affected by the issue, upvote the original issue by clicking its [+😊] button and hitting 👍 (+1) icon. This way we can actually measure how impactful an issue is. + +--- + +## Contributing fixes / features + +For those able & willing to help fix issues and/or implement features ... + +### To Spec or not to Spec + +Some issues/features may be quick and simple to describe and understand. For such scenarios, once a team member has agreed with your approach, skip ahead to the section headed "Fork, Branch, and Create your PR", below. + +Small issues that do not require a spec will be labelled Issue-Bug or Issue-Task. + +However, some issues/features will require careful thought & formal design before implementation. For these scenarios, we'll request that a spec is written and the associated issue will be labeled Issue-Feature. + +Specs help collaborators discuss different approaches to solve a problem, describe how the feature will behave, how the feature will impact the user, what happens if something goes wrong, etc. Driving towards agreement in a spec, before any code is written, often results in simpler code, and less wasted effort in the long run. + +Specs will be managed in a very similar manner as code contributions so please follow the "Fork, Branch and Create your PR" below. + +### Writing / Contributing-to a Spec + +To write/contribute to a spec: fork, branch and commit via PRs, as you would with any code changes. + +Specs are written in markdown, stored under the `doc/specs` folder and named `[issue id] - [spec description].md`. + +👉 **It is important to follow the spec templates and complete the requested information**. The available spec templates will help ensure that specs contain the minimum information & decisions necessary to permit development to begin. In particular, specs require you to confirm that you've already discussed the issue/idea with the team in an issue and that you provide the issue ID for reference. + +Team members will be happy to help review specs and guide them to completion. + +### Help Wanted + +Once the team have approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/PowerToys/labels/Help-Wanted). + +--- + +## Development + +### Fork, Clone, Branch and Create your PR + +Once you've discussed your proposed feature/fix/etc. with a team member, and you've agreed an approach or a spec has been written and approved, it's time to start development: + +1. Fork the repo if you haven't already +1. Clone your fork locally +1. Create & push a feature branch +1. Create a [Draft Pull Request (PR)](https://github.blog/2019-02-14-introducing-draft-pull-requests/) +1. Work on your changes + +### Code Review + +When you'd like the team to take a look, (even if the work is not yet fully-complete), mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge. + +### Merge + +Once your code has been reviewed and approved by the requisite number of team members, it will be merged into the master branch. Once merged, your PR will be automatically closed. + +--- + +## Thank you + +Thank you in advance for your contribution! diff --git a/deps/cpprestsdk/README.md b/deps/cpprestsdk/README.md new file mode 100644 index 00000000000..e7eed0124e7 --- /dev/null +++ b/deps/cpprestsdk/README.md @@ -0,0 +1,12 @@ +# C++ Rest SDK - JSON library + +This JSON library is taken from the C++ REST SDK in https://github.com/microsoft/cpprestsdk + +Based in the [v2.10.13 release](https://github.com/microsoft/cpprestsdk/tree/v2.10.13/Release), it consists of the needed files to build and use the JSON classes described in `include/cpprest/json.h`. + +Changes made to the files in order to build in the PowerToys project: +- Removal of `#include` references to files that are not needed. +- `#include "pch.h"` instead of `#include "stdafx.h"` to use the PowerToys pre-compiled header. +- `#define _NO_ASYNCRTIMP` in [`include/cpprest/details/cpprest_compat.h`](./include/cpprest/details/cpprest_compat.h) since this class will be statically linked. + +The contents of the C++ Rest SDK license are included in [license.txt](./license.txt). diff --git a/deps/cpprestsdk/cpprestsdk.vcxproj b/deps/cpprestsdk/cpprestsdk.vcxproj new file mode 100644 index 00000000000..26444752a8d --- /dev/null +++ b/deps/cpprestsdk/cpprestsdk.vcxproj @@ -0,0 +1,121 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {4E577735-DFAB-41AF-8A6E-B6E8872A2928} + Win32Proj + common + 10.0 + cpprestsdk + + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + pch.h + stdcpplatest + MultiThreadedDebug + include;%(AdditionalIncludeDirectories) + + + Windows + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + stdcpplatest + pch.h + MultiThreaded + include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/deps/cpprestsdk/include/cpprest/asyncrt_utils.h b/deps/cpprestsdk/include/cpprest/asyncrt_utils.h new file mode 100644 index 00000000000..19880fa47c2 --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/asyncrt_utils.h @@ -0,0 +1,697 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Various common utilities. + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#pragma once + +#include "cpprest/details/basic_types.h" +//#include "pplx/pplxtasks.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#if !defined(ANDROID) && !defined(__ANDROID__) && defined(HAVE_XLOCALE_H) // CodePlex 269 +/* Systems using glibc: xlocale.h has been removed from glibc 2.26 + The above include of locale.h is sufficient + Further details: https://sourceware.org/git/?p=glibc.git;a=commit;h=f0be25b6336db7492e47d2e8e72eb8af53b5506d */ +#include +#endif +#endif + +/// Various utilities for string conversions and date and time manipulation. +namespace utility +{ +// Left over from VS2010 support, remains to avoid breaking. +typedef std::chrono::seconds seconds; + +/// Functions for converting to/from std::chrono::seconds to xml string. +namespace timespan +{ +/// +/// Converts a timespan/interval in seconds to xml duration string as specified by +/// http://www.w3.org/TR/xmlschema-2/#duration +/// +_ASYNCRTIMP utility::string_t __cdecl seconds_to_xml_duration(utility::seconds numSecs); + +/// +/// Converts an xml duration to timespan/interval in seconds +/// http://www.w3.org/TR/xmlschema-2/#duration +/// +_ASYNCRTIMP utility::seconds __cdecl xml_duration_to_seconds(const utility::string_t& timespanString); +} // namespace timespan + +/// Functions for Unicode string conversions. +namespace conversions +{ +/// +/// Converts a UTF-16 string to a UTF-8 string. +/// +/// A two byte character UTF-16 string. +/// A single byte character UTF-8 string. +_ASYNCRTIMP std::string __cdecl utf16_to_utf8(const utf16string& w); + +/// +/// Converts a UTF-8 string to a UTF-16 +/// +/// A single byte character UTF-8 string. +/// A two byte character UTF-16 string. +_ASYNCRTIMP utf16string __cdecl utf8_to_utf16(const std::string& s); + +/// +/// Converts a ASCII (us-ascii) string to a UTF-16 string. +/// +/// A single byte character us-ascii string. +/// A two byte character UTF-16 string. +_ASYNCRTIMP utf16string __cdecl usascii_to_utf16(const std::string& s); + +/// +/// Converts a Latin1 (iso-8859-1) string to a UTF-16 string. +/// +/// A single byte character UTF-8 string. +/// A two byte character UTF-16 string. +_ASYNCRTIMP utf16string __cdecl latin1_to_utf16(const std::string& s); + +/// +/// Converts a Latin1 (iso-8859-1) string to a UTF-8 string. +/// +/// A single byte character UTF-8 string. +/// A single byte character UTF-8 string. +_ASYNCRTIMP utf8string __cdecl latin1_to_utf8(const std::string& s); + +/// +/// Converts to a platform dependent Unicode string type. +/// +/// A single byte character UTF-8 string. +/// A platform dependent string type. +#ifdef _UTF16_STRINGS +_ASYNCRTIMP utility::string_t __cdecl to_string_t(std::string&& s); +#else +inline utility::string_t&& to_string_t(std::string&& s) { return std::move(s); } +#endif + +/// +/// Converts to a platform dependent Unicode string type. +/// +/// A two byte character UTF-16 string. +/// A platform dependent string type. +#ifdef _UTF16_STRINGS +inline utility::string_t&& to_string_t(utf16string&& s) { return std::move(s); } +#else +_ASYNCRTIMP utility::string_t __cdecl to_string_t(utf16string&& s); +#endif +/// +/// Converts to a platform dependent Unicode string type. +/// +/// A single byte character UTF-8 string. +/// A platform dependent string type. +#ifdef _UTF16_STRINGS +_ASYNCRTIMP utility::string_t __cdecl to_string_t(const std::string& s); +#else +inline const utility::string_t& to_string_t(const std::string& s) { return s; } +#endif + +/// +/// Converts to a platform dependent Unicode string type. +/// +/// A two byte character UTF-16 string. +/// A platform dependent string type. +#ifdef _UTF16_STRINGS +inline const utility::string_t& to_string_t(const utf16string& s) { return s; } +#else +_ASYNCRTIMP utility::string_t __cdecl to_string_t(const utf16string& s); +#endif + +/// +/// Converts to a UTF-16 from string. +/// +/// A single byte character UTF-8 string. +/// A two byte character UTF-16 string. +_ASYNCRTIMP utf16string __cdecl to_utf16string(const std::string& value); + +/// +/// Converts to a UTF-16 from string. +/// +/// A two byte character UTF-16 string. +/// A two byte character UTF-16 string. +inline const utf16string& to_utf16string(const utf16string& value) { return value; } +/// +/// Converts to a UTF-16 from string. +/// +/// A two byte character UTF-16 string. +/// A two byte character UTF-16 string. +inline utf16string&& to_utf16string(utf16string&& value) { return std::move(value); } + +/// +/// Converts to a UTF-8 string. +/// +/// A single byte character UTF-8 string. +/// A single byte character UTF-8 string. +inline std::string&& to_utf8string(std::string&& value) { return std::move(value); } + +/// +/// Converts to a UTF-8 string. +/// +/// A single byte character UTF-8 string. +/// A single byte character UTF-8 string. +inline const std::string& to_utf8string(const std::string& value) { return value; } + +/// +/// Converts to a UTF-8 string. +/// +/// A two byte character UTF-16 string. +/// A single byte character UTF-8 string. +_ASYNCRTIMP std::string __cdecl to_utf8string(const utf16string& value); + +/// +/// Encode the given byte array into a base64 string +/// +_ASYNCRTIMP utility::string_t __cdecl to_base64(const std::vector& data); + +/// +/// Encode the given 8-byte integer into a base64 string +/// +_ASYNCRTIMP utility::string_t __cdecl to_base64(uint64_t data); + +/// +/// Decode the given base64 string to a byte array +/// +_ASYNCRTIMP std::vector __cdecl from_base64(const utility::string_t& str); + +template +CASABLANCA_DEPRECATED("All locale-sensitive APIs will be removed in a future update. Use stringstreams directly if " + "locale support is required.") +utility::string_t print_string(const Source& val, const std::locale& loc = std::locale()) +{ + utility::ostringstream_t oss; + oss.imbue(loc); + oss << val; + if (oss.bad()) + { + throw std::bad_cast(); + } + return oss.str(); +} + +CASABLANCA_DEPRECATED("All locale-sensitive APIs will be removed in a future update. Use stringstreams directly if " + "locale support is required.") +inline utility::string_t print_string(const utility::string_t& val) { return val; } + +namespace details +{ +#if defined(__ANDROID__) +template +inline std::string to_string(const T t) +{ + std::ostringstream os; + os.imbue(std::locale::classic()); + os << t; + return os.str(); +} +#endif + +template +inline utility::string_t to_string_t(const T t) +{ +#ifdef _UTF16_STRINGS + using std::to_wstring; + return to_wstring(t); +#else +#if !defined(__ANDROID__) + using std::to_string; +#endif + return to_string(t); +#endif +} + +template +utility::string_t print_string(const Source& val) +{ + utility::ostringstream_t oss; + oss.imbue(std::locale::classic()); + oss << val; + if (oss.bad()) + { + throw std::bad_cast(); + } + return oss.str(); +} + +inline const utility::string_t& print_string(const utility::string_t& val) { return val; } + +template +utf8string print_utf8string(const Source& val) +{ + return conversions::to_utf8string(print_string(val)); +} +inline const utf8string& print_utf8string(const utf8string& val) { return val; } + +template +Target scan_string(const utility::string_t& str) +{ + Target t; + utility::istringstream_t iss(str); + iss.imbue(std::locale::classic()); + iss >> t; + if (iss.bad()) + { + throw std::bad_cast(); + } + return t; +} + +inline const utility::string_t& scan_string(const utility::string_t& str) { return str; } +} // namespace details + +template +CASABLANCA_DEPRECATED("All locale-sensitive APIs will be removed in a future update. Use stringstreams directly if " + "locale support is required.") +Target scan_string(const utility::string_t& str, const std::locale& loc = std::locale()) +{ + Target t; + utility::istringstream_t iss(str); + iss.imbue(loc); + iss >> t; + if (iss.bad()) + { + throw std::bad_cast(); + } + return t; +} + +CASABLANCA_DEPRECATED("All locale-sensitive APIs will be removed in a future update. Use stringstreams directly if " + "locale support is required.") +inline utility::string_t scan_string(const utility::string_t& str) { return str; } +} // namespace conversions + +namespace details +{ +/// +/// Cross platform RAII container for setting thread local locale. +/// +class scoped_c_thread_locale +{ +public: + _ASYNCRTIMP scoped_c_thread_locale(); + _ASYNCRTIMP ~scoped_c_thread_locale(); + +#if !defined(ANDROID) && !defined(__ANDROID__) // CodePlex 269 +#ifdef _WIN32 + typedef _locale_t xplat_locale; +#else + typedef locale_t xplat_locale; +#endif + + static _ASYNCRTIMP xplat_locale __cdecl c_locale(); +#endif +private: +#ifdef _WIN32 + std::string m_prevLocale; + int m_prevThreadSetting; +#elif !(defined(ANDROID) || defined(__ANDROID__)) + locale_t m_prevLocale; +#endif + scoped_c_thread_locale(const scoped_c_thread_locale&); + scoped_c_thread_locale& operator=(const scoped_c_thread_locale&); +}; + +/// +/// Our own implementation of alpha numeric instead of std::isalnum to avoid +/// taking global lock for performance reasons. +/// +inline bool __cdecl is_alnum(const unsigned char uch) CPPREST_NOEXCEPT +{ // test if uch is an alnum character + // special casing char to avoid branches + // clang-format off + static CPPREST_CONSTEXPR bool is_alnum_table[UCHAR_MAX + 1] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF */ + /* 0X */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 1X */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 2X */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 3X */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0-9 */ + /* 4X */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A-Z */ + /* 5X */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + /* 6X */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a-z */ + /* 7X */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + /* non-ASCII values initialized to 0 */ + }; + // clang-format on + return (is_alnum_table[uch]); +} + +/// +/// Our own implementation of alpha numeric instead of std::isalnum to avoid +/// taking global lock for performance reasons. +/// +inline bool __cdecl is_alnum(const char ch) CPPREST_NOEXCEPT { return (is_alnum(static_cast(ch))); } + +/// +/// Our own implementation of alpha numeric instead of std::isalnum to avoid +/// taking global lock for performance reasons. +/// +template +inline bool __cdecl is_alnum(Elem ch) CPPREST_NOEXCEPT +{ + // assumes 'x' == L'x' for the ASCII range + typedef typename std::make_unsigned::type UElem; + const auto uch = static_cast(ch); + return (uch <= static_cast('z') && is_alnum(static_cast(uch))); +} + +/// +/// Simplistic implementation of make_unique. A better implementation would be based on variadic templates +/// and therefore not be compatible with Dev10. +/// +template +std::unique_ptr<_Type> make_unique() +{ + return std::unique_ptr<_Type>(new _Type()); +} + +template +std::unique_ptr<_Type> make_unique(_Arg1&& arg1) +{ + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1))); +} + +template +std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2) +{ + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2))); +} + +template +std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3) +{ + return std::unique_ptr<_Type>( + new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2), std::forward<_Arg3>(arg3))); +} + +template +std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3, _Arg4&& arg4) +{ + return std::unique_ptr<_Type>(new _Type( + std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2), std::forward<_Arg3>(arg3), std::forward<_Arg4>(arg4))); +} + +template +std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3, _Arg4&& arg4, _Arg5&& arg5) +{ + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), + std::forward<_Arg2>(arg2), + std::forward<_Arg3>(arg3), + std::forward<_Arg4>(arg4), + std::forward<_Arg5>(arg5))); +} + +template +std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3, _Arg4&& arg4, _Arg5&& arg5, _Arg6&& arg6) +{ + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), + std::forward<_Arg2>(arg2), + std::forward<_Arg3>(arg3), + std::forward<_Arg4>(arg4), + std::forward<_Arg5>(arg5), + std::forward<_Arg6>(arg6))); +} + +/// +/// Cross platform utility function for performing case insensitive string equality comparison. +/// +/// First string to compare. +/// Second strong to compare. +/// true if the strings are equivalent, false otherwise +_ASYNCRTIMP bool __cdecl str_iequal(const std::string& left, const std::string& right) CPPREST_NOEXCEPT; + +/// +/// Cross platform utility function for performing case insensitive string equality comparison. +/// +/// First string to compare. +/// Second strong to compare. +/// true if the strings are equivalent, false otherwise +_ASYNCRTIMP bool __cdecl str_iequal(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT; + +/// +/// Cross platform utility function for performing case insensitive string less-than comparison. +/// +/// First string to compare. +/// Second strong to compare. +/// true if a lowercase view of left is lexicographically less than a lowercase view of right; otherwise, +/// false. +_ASYNCRTIMP bool __cdecl str_iless(const std::string& left, const std::string& right) CPPREST_NOEXCEPT; + +/// +/// Cross platform utility function for performing case insensitive string less-than comparison. +/// +/// First string to compare. +/// Second strong to compare. +/// true if a lowercase view of left is lexicographically less than a lowercase view of right; otherwise, +/// false. +_ASYNCRTIMP bool __cdecl str_iless(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT; + +/// +/// Convert a string to lowercase in place. +/// +/// The string to convert to lowercase. +_ASYNCRTIMP void __cdecl inplace_tolower(std::string& target) CPPREST_NOEXCEPT; + +/// +/// Convert a string to lowercase in place. +/// +/// The string to convert to lowercase. +_ASYNCRTIMP void __cdecl inplace_tolower(std::wstring& target) CPPREST_NOEXCEPT; + +#ifdef _WIN32 + +/// +/// Category error type for Windows OS errors. +/// +class windows_category_impl : public std::error_category +{ +public: + virtual const char* name() const CPPREST_NOEXCEPT { return "windows"; } + + virtual std::string message(int errorCode) const CPPREST_NOEXCEPT; + + virtual std::error_condition default_error_condition(int errorCode) const CPPREST_NOEXCEPT; +}; + +/// +/// Gets the one global instance of the windows error category. +/// +/// An error category instance. +_ASYNCRTIMP const std::error_category& __cdecl windows_category(); + +#else + +/// +/// Gets the one global instance of the linux error category. +/// +/// An error category instance. +_ASYNCRTIMP const std::error_category& __cdecl linux_category(); + +#endif + +/// +/// Gets the one global instance of the current platform's error category. +/// +_ASYNCRTIMP const std::error_category& __cdecl platform_category(); + +/// +/// Creates an instance of std::system_error from a OS error code. +/// +inline std::system_error __cdecl create_system_error(unsigned long errorCode) +{ + std::error_code code((int)errorCode, platform_category()); + return std::system_error(code, code.message()); +} + +/// +/// Creates a std::error_code from a OS error code. +/// +inline std::error_code __cdecl create_error_code(unsigned long errorCode) +{ + return std::error_code((int)errorCode, platform_category()); +} + +/// +/// Creates the corresponding error message from a OS error code. +/// +inline utility::string_t __cdecl create_error_message(unsigned long errorCode) +{ + return utility::conversions::to_string_t(create_error_code(errorCode).message()); +} + +} // namespace details + +class datetime +{ +public: + typedef uint64_t interval_type; + + /// + /// Defines the supported date and time string formats. + /// + enum date_format + { + RFC_1123, + ISO_8601 + }; + + /// + /// Returns the current UTC time. + /// + static _ASYNCRTIMP datetime __cdecl utc_now(); + + /// + /// An invalid UTC timestamp value. + /// + enum : interval_type + { + utc_timestamp_invalid = static_cast(-1) + }; + + /// + /// Returns seconds since Unix/POSIX time epoch at 01-01-1970 00:00:00. + /// If time is before epoch, utc_timestamp_invalid is returned. + /// + static interval_type utc_timestamp() + { + const auto seconds = utc_now().to_interval() / _secondTicks; + if (seconds >= 11644473600LL) + { + return seconds - 11644473600LL; + } + else + { + return utc_timestamp_invalid; + } + } + + datetime() : m_interval(0) {} + + /// + /// Creates datetime from a string representing time in UTC in RFC 1123 format. + /// + /// Returns a datetime of zero if not successful. + static _ASYNCRTIMP datetime __cdecl from_string(const utility::string_t& timestring, date_format format = RFC_1123); + + /// + /// Returns a string representation of the datetime. + /// + _ASYNCRTIMP utility::string_t to_string(date_format format = RFC_1123) const; + + /// + /// Returns the integral time value. + /// + interval_type to_interval() const { return m_interval; } + + datetime operator-(interval_type value) const { return datetime(m_interval - value); } + + datetime operator+(interval_type value) const { return datetime(m_interval + value); } + + bool operator==(datetime dt) const { return m_interval == dt.m_interval; } + + bool operator!=(const datetime& dt) const { return !(*this == dt); } + + static interval_type from_milliseconds(unsigned int milliseconds) { return milliseconds * _msTicks; } + + static interval_type from_seconds(unsigned int seconds) { return seconds * _secondTicks; } + + static interval_type from_minutes(unsigned int minutes) { return minutes * _minuteTicks; } + + static interval_type from_hours(unsigned int hours) { return hours * _hourTicks; } + + static interval_type from_days(unsigned int days) { return days * _dayTicks; } + + bool is_initialized() const { return m_interval != 0; } + +private: + friend int operator-(datetime t1, datetime t2); + + static const interval_type _msTicks = static_cast(10000); + static const interval_type _secondTicks = 1000 * _msTicks; + static const interval_type _minuteTicks = 60 * _secondTicks; + static const interval_type _hourTicks = 60 * 60 * _secondTicks; + static const interval_type _dayTicks = 24 * 60 * 60 * _secondTicks; + + // Private constructor. Use static methods to create an instance. + datetime(interval_type interval) : m_interval(interval) {} + + // Storing as hundreds of nanoseconds 10e-7, i.e. 1 here equals 100ns. + interval_type m_interval; +}; + +inline int operator-(datetime t1, datetime t2) +{ + auto diff = (t1.m_interval - t2.m_interval); + + // Round it down to seconds + diff /= 10 * 1000 * 1000; + + return static_cast(diff); +} + +/// +/// Nonce string generator class. +/// +class nonce_generator +{ +public: + /// + /// Define default nonce length. + /// + enum + { + default_length = 32 + }; + + /// + /// Nonce generator constructor. + /// + /// Length of the generated nonce string. + nonce_generator(int length = default_length) + : m_random(static_cast(utility::datetime::utc_timestamp())), m_length(length) + { + } + + /// + /// Generate a nonce string containing random alphanumeric characters (A-Za-z0-9). + /// Length of the generated string is set by length(). + /// + /// The generated nonce string. + _ASYNCRTIMP utility::string_t generate(); + + /// + /// Get length of generated nonce string. + /// + /// Nonce string length. + int length() const { return m_length; } + + /// + /// Set length of the generated nonce string. + /// + /// Lenght of nonce string. + void set_length(int length) { m_length = length; } + +private: + std::mt19937 m_random; + int m_length; +}; + +} // namespace utility diff --git a/deps/cpprestsdk/include/cpprest/base_uri.h b/deps/cpprestsdk/include/cpprest/base_uri.h new file mode 100644 index 00000000000..7c6943119c0 --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/base_uri.h @@ -0,0 +1,391 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Protocol independent support for URIs. + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#pragma once + +#include "cpprest/asyncrt_utils.h" +#include "cpprest/details/basic_types.h" +#include +#include +#include +#include + +namespace web +{ +namespace details +{ +struct uri_components +{ + uri_components() : m_path(_XPLATSTR("/")), m_port(-1) {} + + uri_components(const uri_components&) = default; + uri_components& operator=(const uri_components&) = default; + + // This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped. + uri_components(uri_components&& other) CPPREST_NOEXCEPT : m_scheme(std::move(other.m_scheme)), + m_host(std::move(other.m_host)), + m_user_info(std::move(other.m_user_info)), + m_path(std::move(other.m_path)), + m_query(std::move(other.m_query)), + m_fragment(std::move(other.m_fragment)), + m_port(other.m_port) + { + } + + // This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped. + uri_components& operator=(uri_components&& other) CPPREST_NOEXCEPT + { + if (this != &other) + { + m_scheme = std::move(other.m_scheme); + m_host = std::move(other.m_host); + m_user_info = std::move(other.m_user_info); + m_path = std::move(other.m_path); + m_query = std::move(other.m_query); + m_fragment = std::move(other.m_fragment); + m_port = other.m_port; + } + return *this; + } + + _ASYNCRTIMP utility::string_t join(); + + utility::string_t m_scheme; + utility::string_t m_host; + utility::string_t m_user_info; + utility::string_t m_path; + utility::string_t m_query; + utility::string_t m_fragment; + int m_port; +}; +} // namespace details + +/// +/// A single exception type to represent errors in parsing, encoding, and decoding URIs. +/// +class uri_exception : public std::exception +{ +public: + uri_exception(std::string msg) : m_msg(std::move(msg)) {} + + ~uri_exception() CPPREST_NOEXCEPT {} + + const char* what() const CPPREST_NOEXCEPT { return m_msg.c_str(); } + +private: + std::string m_msg; +}; + +/// +/// A flexible, protocol independent URI implementation. +/// +/// URI instances are immutable. Querying the various fields on an empty URI will return empty strings. Querying +/// various diagnostic members on an empty URI will return false. +/// +/// +/// This implementation accepts both URIs ('http://msn.com/path') and URI relative-references +/// ('/path?query#frag'). +/// +/// This implementation does not provide any scheme-specific handling -- an example of this +/// would be the following: 'http://path1/path'. This is a valid URI, but it's not a valid +/// http-uri -- that is, it's syntactically correct but does not conform to the requirements +/// of the http scheme (http requires a host). +/// We could provide this by allowing a pluggable 'scheme' policy-class, which would provide +/// extra capability for validating and canonicalizing a URI according to scheme, and would +/// introduce a layer of type-safety for URIs of differing schemes, and thus differing semantics. +/// +/// One issue with implementing a scheme-independent URI facility is that of comparing for equality. +/// For instance, these URIs are considered equal 'http://msn.com', 'http://msn.com:80'. That is -- +/// the 'default' port can be either omitted or explicit. Since we don't have a way to map a scheme +/// to it's default port, we don't have a way to know these are equal. This is just one of a class of +/// issues with regard to scheme-specific behavior. +/// +class uri +{ +public: + /// + /// The various components of a URI. This enum is used to indicate which + /// URI component is being encoded to the encode_uri_component. This allows + /// specific encoding to be performed. + /// + /// Scheme and port don't allow '%' so they don't need to be encoded. + /// + class components + { + public: + enum component + { + user_info, + host, + path, + query, + fragment, + full_uri + }; + }; + + /// + /// Encodes a URI component according to RFC 3986. + /// Note if a full URI is specified instead of an individual URI component all + /// characters not in the unreserved set are escaped. + /// + /// The URI as a string. + /// The encoded string. + _ASYNCRTIMP static utility::string_t __cdecl encode_uri(const utility::string_t& raw, + uri::components::component = components::full_uri); + + /// + /// Encodes a string by converting all characters except for RFC 3986 unreserved characters to their + /// hexadecimal representation. + /// + /// The encoded string. + _ASYNCRTIMP static utility::string_t __cdecl encode_data_string(const utility::string_t& data); + + /// + /// Decodes an encoded string. + /// + /// The URI as a string. + /// The decoded string. + _ASYNCRTIMP static utility::string_t __cdecl decode(const utility::string_t& encoded); + + /// + /// Splits a path into its hierarchical components. + /// + /// The path as a string + /// A std::vector<utility::string_t> containing the segments in the path. + _ASYNCRTIMP static std::vector __cdecl split_path(const utility::string_t& path); + + /// + /// Splits a query into its key-value components. + /// + /// The query string + /// A std::map<utility::string_t, utility::string_t> containing the key-value components of + /// the query. + _ASYNCRTIMP static std::map __cdecl split_query( + const utility::string_t& query); + + /// + /// Validates a string as a URI. + /// + /// + /// This function accepts both uris ('http://msn.com') and uri relative-references ('path1/path2?query'). + /// + /// The URI string to be validated. + /// true if the given string represents a valid URI, false otherwise. + _ASYNCRTIMP static bool __cdecl validate(const utility::string_t& uri_string); + + /// + /// Creates an empty uri + /// + uri() : m_uri(_XPLATSTR("/")) {} + + /// + /// Creates a URI from the given encoded string. This will throw an exception if the string + /// does not contain a valid URI. Use uri::validate if processing user-input. + /// + /// A pointer to an encoded string to create the URI instance. + _ASYNCRTIMP uri(const utility::char_t* uri_string); + + /// + /// Creates a URI from the given encoded string. This will throw an exception if the string + /// does not contain a valid URI. Use uri::validate if processing user-input. + /// + /// An encoded URI string to create the URI instance. + _ASYNCRTIMP uri(const utility::string_t& uri_string); + + /// + /// Copy constructor. + /// + uri(const uri&) = default; + + /// + /// Copy assignment operator. + /// + uri& operator=(const uri&) = default; + + /// + /// Move constructor. + /// + // This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped. + uri(uri&& other) CPPREST_NOEXCEPT : m_uri(std::move(other.m_uri)), m_components(std::move(other.m_components)) {} + + /// + /// Move assignment operator + /// + // This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped. + uri& operator=(uri&& other) CPPREST_NOEXCEPT + { + if (this != &other) + { + m_uri = std::move(other.m_uri); + m_components = std::move(other.m_components); + } + return *this; + } + + /// + /// Get the scheme component of the URI as an encoded string. + /// + /// The URI scheme as a string. + const utility::string_t& scheme() const { return m_components.m_scheme; } + + /// + /// Get the user information component of the URI as an encoded string. + /// + /// The URI user information as a string. + const utility::string_t& user_info() const { return m_components.m_user_info; } + + /// + /// Get the host component of the URI as an encoded string. + /// + /// The URI host as a string. + const utility::string_t& host() const { return m_components.m_host; } + + /// + /// Get the port component of the URI. Returns -1 if no port is specified. + /// + /// The URI port as an integer. + int port() const { return m_components.m_port; } + + /// + /// Get the path component of the URI as an encoded string. + /// + /// The URI path as a string. + const utility::string_t& path() const { return m_components.m_path; } + + /// + /// Get the query component of the URI as an encoded string. + /// + /// The URI query as a string. + const utility::string_t& query() const { return m_components.m_query; } + + /// + /// Get the fragment component of the URI as an encoded string. + /// + /// The URI fragment as a string. + const utility::string_t& fragment() const { return m_components.m_fragment; } + + /// + /// Creates a new uri object with the same authority portion as this one, omitting the resource and query portions. + /// + /// The new uri object with the same authority. + _ASYNCRTIMP uri authority() const; + + /// + /// Gets the path, query, and fragment portion of this uri, which may be empty. + /// + /// The new URI object with the path, query and fragment portion of this URI. + _ASYNCRTIMP uri resource() const; + + /// + /// An empty URI specifies no components, and serves as a default value + /// + bool is_empty() const { return this->m_uri.empty() || this->m_uri == _XPLATSTR("/"); } + + /// + /// A loopback URI is one which refers to a hostname or ip address with meaning only on the local machine. + /// + /// + /// Examples include "localhost", or ip addresses in the loopback range (127.0.0.0/24). + /// + /// true if this URI references the local host, false otherwise. + bool is_host_loopback() const + { + return !is_empty() && + ((host() == _XPLATSTR("localhost")) || (host().size() > 4 && host().substr(0, 4) == _XPLATSTR("127."))); + } + + /// + /// A wildcard URI is one which refers to all hostnames that resolve to the local machine (using the * or +) + /// + /// + /// http://*:80 + /// + bool is_host_wildcard() const + { + return !is_empty() && (this->host() == _XPLATSTR("*") || this->host() == _XPLATSTR("+")); + } + + /// + /// A portable URI is one with a hostname that can be resolved globally (used from another machine). + /// + /// true if this URI can be resolved globally (used from another machine), false + /// otherwise. The hostname "localhost" is a reserved name that is guaranteed to resolve to the + /// local machine, and cannot be used for inter-machine communication. Likewise the hostnames "*" and "+" on Windows + /// represent wildcards, and do not map to a resolvable address. + /// + bool is_host_portable() const { return !(is_empty() || is_host_loopback() || is_host_wildcard()); } + + /// + /// A default port is one where the port is unspecified, and will be determined by the operating system. + /// The choice of default port may be dictated by the scheme (http -> 80) or not. + /// + /// true if this URI instance has a default port, false otherwise. + bool is_port_default() const { return !is_empty() && this->port() == 0; } + + /// + /// An "authority" URI is one with only a scheme, optional userinfo, hostname, and (optional) port. + /// + /// true if this is an "authority" URI, false otherwise. + bool is_authority() const { return !is_empty() && is_path_empty() && query().empty() && fragment().empty(); } + + /// + /// Returns whether the other URI has the same authority as this one + /// + /// The URI to compare the authority with. + /// true if both the URI's have the same authority, false otherwise. + bool has_same_authority(const uri& other) const { return !is_empty() && this->authority() == other.authority(); } + + /// + /// Returns whether the path portion of this URI is empty + /// + /// true if the path portion of this URI is empty, false otherwise. + bool is_path_empty() const { return path().empty() || path() == _XPLATSTR("/"); } + + /// + /// Returns the full (encoded) URI as a string. + /// + /// The full encoded URI string. + utility::string_t to_string() const { return m_uri; } + + /// + /// Returns an URI resolved against this as the base URI + /// according to RFC3986, Section 5 (https://tools.ietf.org/html/rfc3986#section-5). + /// + /// The relative URI to be resolved against this as base. + /// The new resolved URI string. + _ASYNCRTIMP utility::string_t resolve_uri(const utility::string_t& relativeUri) const; + + _ASYNCRTIMP bool operator==(const uri& other) const; + + bool operator<(const uri& other) const { return m_uri < other.m_uri; } + + bool operator!=(const uri& other) const { return !(this->operator==(other)); } + +private: + friend class uri_builder; + + /// + /// Creates a URI from the given URI components. + /// + /// A URI components object to create the URI instance. + _ASYNCRTIMP uri(const details::uri_components& components); + + // Used by uri_builder + static utility::string_t __cdecl encode_query_impl(const utf8string& raw); + + utility::string_t m_uri; + details::uri_components m_components; +}; + +} // namespace web diff --git a/deps/cpprestsdk/include/cpprest/details/SafeInt3.hpp b/deps/cpprestsdk/include/cpprest/details/SafeInt3.hpp new file mode 100644 index 00000000000..0a9dbdd76af --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/details/SafeInt3.hpp @@ -0,0 +1,7482 @@ +/*----------------------------------------------------------------------------------------------------------- +SafeInt.hpp +Version 3.0.18p + +This software is licensed under the Microsoft Public License (Ms-PL). +For more information about Microsoft open source licenses, refer to +http://www.microsoft.com/opensource/licenses.mspx + +This license governs use of the accompanying software. If you use the software, you accept this license. +If you do not accept the license, do not use the software. + +Definitions +The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here +as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to +the software. A "contributor" is any person that distributes its contribution under this license. +"Licensed patents" are a contributor's patent claims that read directly on its contribution. + +Grant of Rights +(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations +in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to +reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution +or any derivative works that you create. + +(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in +section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed +patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution +in the software or derivative works of the contribution in the software. + +Conditions and Limitations +(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, + or trademarks. +(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the + software, your patent license from such contributor to the software ends automatically. +(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and + attribution notices that are present in the software. +(D) If you distribute any portion of the software in source code form, you may do so only under this license + by including a complete copy of this license with your distribution. If you distribute any portion of the + software in compiled or object code form, you may only do so under a license that complies with this license. +(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, + guarantees, or conditions. You may have additional consumer rights under your local laws which this license + cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties + of merchantability, fitness for a particular purpose and non-infringement. + + +Copyright (c) Microsoft Corporation. All rights reserved. + +This header implements an integer handling class designed to catch +unsafe integer operations + +This header compiles properly at Wall on Visual Studio, -Wall on gcc, and -Weverything on clang. + +Please read the leading comments before using the class. +---------------------------------------------------------------*/ +#pragma once + +// It is a bit tricky to sort out what compiler we are actually using, +// do this once here, and avoid cluttering the code +#define VISUAL_STUDIO_COMPILER 0 +#define CLANG_COMPILER 1 +#define GCC_COMPILER 2 +#define UNKNOWN_COMPILER -1 + +// Clang will sometimes pretend to be Visual Studio +// and does pretend to be gcc. Check it first, as nothing else pretends to be clang +#if defined __clang__ +#define SAFEINT_COMPILER CLANG_COMPILER +#elif defined __GNUC__ +#define SAFEINT_COMPILER GCC_COMPILER +#elif defined _MSC_VER +#define SAFEINT_COMPILER VISUAL_STUDIO_COMPILER +#else +#define SAFEINT_COMPILER UNKNOWN_COMPILER +#endif + +// Enable compiling with /Wall under VC +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(push) +// Disable warnings coming from headers +#pragma warning(disable : 4987 4820 4987 4820) + +#endif + +// Need this for ptrdiff_t on some compilers +#include +#include + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER && defined _M_AMD64 +#include +#define SAFEINT_USE_INTRINSICS 1 +#else +#define SAFEINT_USE_INTRINSICS 0 +#endif + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(pop) +#endif + +// Various things needed for GCC +#if SAFEINT_COMPILER == GCC_COMPILER || SAFEINT_COMPILER == CLANG_COMPILER + +#define NEEDS_INT_DEFINED + +#if !defined NULL +#define NULL 0 +#endif + +// GCC warning suppression +#if SAFEINT_COMPILER == GCC_COMPILER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#endif + +#include + +// clang only +#if SAFEINT_COMPILER == CLANG_COMPILER + +#if __has_feature(cxx_nullptr) +#define NEEDS_NULLPTR_DEFINED 0 +#endif + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#endif + +#endif + +// If the user made a choice, respect it #if !defined +#if !defined NEEDS_NULLPTR_DEFINED +// Visual Studio 2010 and higher support this +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#if (_MSC_VER < 1600) +#define NEEDS_NULLPTR_DEFINED 1 +#else +#define NEEDS_NULLPTR_DEFINED 0 +#endif +#else +// Let everything else trigger based on whether we use c++11 or above +#if __cplusplus >= 201103L +#define NEEDS_NULLPTR_DEFINED 0 +#else +#define NEEDS_NULLPTR_DEFINED 1 +#endif +#endif +#endif + +#if NEEDS_NULLPTR_DEFINED +#define nullptr NULL +#endif + +#ifndef C_ASSERT +#define C_ASSERT_DEFINED_SAFEINT +#define C_ASSERT(e) typedef char __C_ASSERT__[(e) ? 1 : -1] +#endif + +// Let's test some assumptions +// We're assuming two's complement negative numbers +C_ASSERT(-1 == static_cast(0xffffffff)); + +/************* Compiler Options +***************************************************************************************************** + +SafeInt supports several compile-time options that can change the behavior of the class. + +Compiler options: +SAFEINT_WARN_64BIT_PORTABILITY - this re-enables various warnings that happen when /Wp64 is used. Enabling this +option is not recommended. NEEDS_INT_DEFINED - if your compiler does not support __int8, __int16, +__int32 and __int64, you can enable this. SAFEINT_ASSERT_ON_EXCEPTION - it is often easier to stop on an assert +and figure out a problem than to try and figure out how you landed in the catch block. SafeIntDefaultExceptionHandler - +if you'd like to replace the exception handlers SafeInt provides, define your replacement and define this. Note - two +built in (Windows-specific) options exist: + - SAFEINT_FAILFAST - bypasses all exception handlers, exits the app with an +exception + - SAFEINT_RAISE_EXCEPTION - throws Win32 exceptions, which can be caught +SAFEINT_DISALLOW_UNSIGNED_NEGATION - Invoking the unary negation operator creates warnings, but if you'd like it to +completely fail to compile, define this. ANSI_CONVERSIONS - This changes the class to use default +comparison behavior, which may be unsafe. Enabling this option is not recommended. SAFEINT_DISABLE_BINARY_ASSERT - +binary AND, OR or XOR operations on mixed size types can produce unexpected results. If you do this, the default is to +assert. Set this if you prefer not to assert under these conditions. SIZE_T_CAST_NEEDED - some compilers +complain if there is not a cast to size_t, others complain if there is one. This lets you not have your compiler +complain. SAFEINT_DISABLE_SHIFT_ASSERT - Set this option if you don't want to assert when shifting more bits than +the type has. Enabling this option is not recommended. + +************************************************************************************************************************************/ + +/* +* The SafeInt class is designed to have as low an overhead as possible +* while still ensuring that all integer operations are conducted safely. +* Nearly every operator has been overloaded, with a very few exceptions. +* +* A usability-safety trade-off has been made to help ensure safety. This +* requires that every operation return either a SafeInt or a bool. If we +* allowed an operator to return a base integer type T, then the following +* can happen: +* +* char i = SafeInt(32) * 2 + SafeInt(16) * 4; +* +* The * operators take precedence, get overloaded, return a char, and then +* you have: +* +* char i = (char)64 + (char)64; //overflow! +* +* This situation would mean that safety would depend on usage, which isn't +* acceptable. +* +* One key operator that is missing is an implicit cast to type T. The reason for +* this is that if there is an implicit cast operator, then we end up with +* an ambiguous compile-time precedence. Because of this amiguity, there +* are two methods that are provided: +* +* Casting operators for every native integer type +* Version 3 note - it now compiles correctly for size_t without warnings +* +* SafeInt::Ptr() - returns the address of the internal integer +* Note - the '&' (address of) operator has been overloaded and returns +* the address of the internal integer. +* +* The SafeInt class should be used in any circumstances where ensuring +* integrity of the calculations is more important than performance. See Performance +* Notes below for additional information. +* +* Many of the conditionals will optimize out or be inlined for a release +* build (especially with /Ox), but it does have significantly more overhead, +* especially for signed numbers. If you do not _require_ negative numbers, use +* unsigned integer types - certain types of problems cannot occur, and this class +* performs most efficiently. +* +* Here's an example of when the class should ideally be used - +* +* void* AllocateMemForStructs(int StructSize, int HowMany) +* { +* SafeInt s(StructSize); +* +* s *= HowMany; +* +* return malloc(s); +* +* } +* +* Here's when it should NOT be used: +* +* void foo() +* { +* int i; +* +* for(i = 0; i < 0xffff; i++) +* .... +* } +* +* Error handling - a SafeInt class will throw exceptions if something +* objectionable happens. The exceptions are SafeIntException classes, +* which contain an enum as a code. +* +* Typical usage might be: +* +* bool foo() +* { +* SafeInt s; //note that s == 0 unless set +* +* try{ +* s *= 23; +* .... +* } +* catch(SafeIntException err) +* { +* //handle errors here +* } +* } +* +* Update for 3.0 - the exception class is now a template parameter. +* You can replace the exception class with any exception class you like. This is accomplished by: +* 1) Create a class that has the following interface: +* + template <> class YourSafeIntExceptionHandler < YourException > + { + public: + static __declspec(noreturn) void __stdcall SafeIntOnOverflow() + { + throw YourException( YourSafeIntArithmeticOverflowError ); + } + + static __declspec(noreturn) void __stdcall SafeIntOnDivZero() + { + throw YourException( YourSafeIntDivideByZeroError ); + } + }; +* +* Note that you don't have to throw C++ exceptions, you can throw Win32 exceptions, or do +* anything you like, just don't return from the call back into the code. +* +* 2) Either explicitly declare SafeInts like so: +* SafeInt< int, YourSafeIntExceptionHandler > si; +* or +* #define SafeIntDefaultExceptionHandler YourSafeIntExceptionHandler +* +* Performance: +* +* Due to the highly nested nature of this class, you can expect relatively poor +* performance in unoptimized code. In tests of optimized code vs. correct inline checks +* in native code, this class has been found to take approximately 8% more CPU time (this varies), +* most of which is due to exception handling. Solutions: +* +* 1) Compile optimized code - /Ox is best, /O2 also performs well. Interestingly, /O1 +* (optimize for size) does not work as well. +* 2) If that 8% hit is really a serious problem, walk through the code and inline the +* exact same checks as the class uses. +* 3) Some operations are more difficult than others - avoid using signed integers, and if +* possible keep them all the same size. 64-bit integers are also expensive. Mixing +* different integer sizes and types may prove expensive. Be aware that literals are +* actually ints. For best performance, cast literals to the type desired. +* +* +* Performance update +* The current version of SafeInt uses template specialization to force the compiler to invoke only the +* operator implementation needed for any given pair of types. This will dramatically improve the perf +* of debug builds. +* +* 3.0 update - not only have we maintained the specialization, there were some cases that were overly complex, +* and using some additional cases (e.g. signed __int64 and unsigned __int64) resulted in some simplification. +* Additionally, there was a lot of work done to better optimize the 64-bit multiplication. +* +* Binary Operators +* +* All of the binary operators have certain assumptions built into the class design. +* This is to ensure correctness. Notes on each class of operator follow: +* +* Arithmetic Operators (*,/,+,-,%) +* There are three possible variants: +* SafeInt< T, E > op SafeInt< T, E > +* SafeInt< T, E > op U +* U op SafeInt< T, E > +* +* The SafeInt< T, E > op SafeInt< U, E > variant is explicitly not supported, and if you try to do +* this the compiler with throw the following error: +* +* error C2593: 'operator *' is ambiguous +* +* This is because the arithmetic operators are required to return a SafeInt of some type. +* The compiler cannot know whether you'd prefer to get a type T or a type U returned. If +* you need to do this, you need to extract the value contained within one of the two using +* the casting operator. For example: +* +* SafeInt< T, E > t, result; +* SafeInt< U, E > u; +* +* result = t * (U)u; +* +* Comparison Operators +* Because each of these operators return type bool, mixing SafeInts of differing types is +* allowed. +* +* Shift Operators +* Shift operators always return the type on the left hand side of the operator. Mixed type +* operations are allowed because the return type is always known. +* +* Boolean Operators +* Like comparison operators, these overloads always return type bool, and mixed-type SafeInts +* are allowed. Additionally, specific overloads exist for type bool on both sides of the +* operator. +* +* Binary Operators +* Mixed-type operations are discouraged, however some provision has been made in order to +* enable things like: +* +* SafeInt c = 2; +* +* if(c & 0x02) +* ... +* +* The "0x02" is actually an int, and it needs to work. +* In the case of binary operations on integers smaller than 32-bit, or of mixed type, corner +* cases do exist where you could get unexpected results. In any case where SafeInt returns a different +* result than the underlying operator, it will call assert(). You should examine your code and cast things +* properly so that you are not programming with side effects. +* +* Documented issues: +* +* This header compiles correctly at /W4 using VC++ 8 (Version 14.00.50727.42) and later. +* As of this writing, I believe it will also work for VC 7.1, but not for VC 7.0 or below. +* If you need a version that will work with lower level compilers, try version 1.0.7. None +* of them work with Visual C++ 6, and gcc didn't work very well, either, though this hasn't +* been tried recently. +* +* It is strongly recommended that any code doing integer manipulation be compiled at /W4 +* - there are a number of warnings which pertain to integer manipulation enabled that are +* not enabled at /W3 (default for VC++) +* +* Perf note - postfix operators are slightly more costly than prefix operators. +* Unless you're actually assigning it to something, ++SafeInt is less expensive than SafeInt++ +* +* The comparison operator behavior in this class varies from the ANSI definition, which is +* arguably broken. As an example, consider the following: +* +* unsigned int l = 0xffffffff; +* char c = -1; +* +* if(c == l) +* printf("Why is -1 equal to 4 billion???\n"); +* +* The problem here is that c gets cast to an int, now has a value of 0xffffffff, and then gets +* cast again to an unsigned int, losing the true value. This behavior is despite the fact that +* an __int64 exists, and the following code will yield a different (and intuitively correct) +* answer: +* +* if((__int64)c == (__int64)l)) +* printf("Why is -1 equal to 4 billion???\n"); +* else +* printf("Why doesn't the compiler upcast to 64-bits when needed?\n"); +* +* Note that combinations with smaller integers won't display the problem - if you +* changed "unsigned int" above to "unsigned short", you'd get the right answer. +* +* If you prefer to retain the ANSI standard behavior insert +* #define ANSI_CONVERSIONS +* into your source. Behavior differences occur in the following cases: +* 8, 16, and 32-bit signed int, unsigned 32-bit int +* any signed int, unsigned 64-bit int +* Note - the signed int must be negative to show the problem +* +* +* Revision history: +* +* Oct 12, 2003 - Created +* Author - David LeBlanc - dleblanc@microsoft.com +* +* Oct 27, 2003 - fixed numerous items pointed out by michmarc and bdawson +* Dec 28, 2003 - 1.0 +* added support for mixed-type operations +* thanks to vikramh +* also fixed broken __int64 multiplication section +* added extended support for mixed-type operations where possible +* Jan 28, 2004 - 1.0.1 +* changed WCHAR to wchar_t +* fixed a construct in two mixed-type assignment overloads that was +* not compiling on some compilers +* Also changed name of private method to comply with standards on +* reserved names +* Thanks to Niels Dekker for the input +* Feb 12, 2004 - 1.0.2 +* Minor changes to remove dependency on Windows headers +* Consistently used __int16, __int32 and __int64 to ensure +* portability +* May 10, 2004 - 1.0.3 +* Corrected bug in one case of GreaterThan +* July 22, 2004 - 1.0.4 +* Tightened logic in addition check (saving 2 instructions) +* Pulled error handler out into function to enable user-defined replacement +* Made internal type of SafeIntException an enum (as per Niels' suggestion) +* Added casts for base integer types (as per Scott Meyers' suggestion) +* Updated usage information - see important new perf notes. +* Cleaned up several const issues (more thanks to Niels) +* +* Oct 1, 2004 - 1.0.5 +* Added support for SEH exceptions instead of C++ exceptions - Win32 only +* Made handlers for DIV0 and overflows individually overridable +* Commented out the destructor - major perf gains here +* Added cast operator for type long, since long != __int32 +* Corrected a couple of missing const modifiers +* Fixed broken >= and <= operators for type U op SafeInt< T, E > +* Nov 5, 2004 - 1.0.6 +* Implemented new logic in binary operators to resolve issues with +* implicit casts +* Fixed casting operator because char != signed char +* Defined __int32 as int instead of long +* Removed unsafe SafeInt::Value method +* Re-implemented casting operator as a result of removing Value method +* Dec 1, 2004 - 1.0.7 +* Implemented specialized operators for pointer arithmetic +* Created overloads for cases of U op= SafeInt. What you do with U +* after that may be dangerous. +* Fixed bug in corner case of MixedSizeModulus +* Fixed bug in MixedSizeMultiply and MixedSizeDivision with input of 0 +* Added throw() decorations +* +* Apr 12, 2005 - 2.0 +* Extensive revisions to leverage template specialization. +* April, 2007 Extensive revisions for version 3.0 +* Nov 22, 2009 Forked from MS internal code +* Changes needed to support gcc compiler - many thanks to Niels Dekker +* for determining not just the issues, but also suggesting fixes. +* Also updating some of the header internals to be the same as the upcoming Visual Studio version. +* +* Jan 16, 2010 64-bit gcc has long == __int64, which means that many of the existing 64-bit +* templates are over-specialized. This forces a redefinition of all the 64-bit +* multiplication routines to use pointers instead of references for return +* values. Also, let's use some intrinsics for x64 Microsoft compiler to +* reduce code size, and hopefully improve efficiency. +* +* June 21, 2014 Better support for clang, higher warning levels supported for all 3 primary supported + compilers (Visual Studio, clang, gcc). + Also started to converge the code base such that the public CodePlex version will + be a drop-in replacement for the Visual Studio version. + +* Note about code style - throughout this class, casts will be written using C-style (T), +* not C++ style static_cast< T >. This is because the class is nearly always dealing with integer +* types, and in this case static_cast and a C cast are equivalent. Given the large number of casts, +* the code is a little more readable this way. In the event a cast is needed where static_cast couldn't +* be substituted, we'll use the new templatized cast to make it explicit what the operation is doing. +* +************************************************************************************************************ +* Version 3.0 changes: +* +* 1) The exception type thrown is now replacable, and you can throw your own exception types. This should help +* those using well-developed exception classes. +* 2) The 64-bit multiplication code has had a lot of perf work done, and should be faster than 2.0. +* 3) There is now limited floating point support. You can initialize a SafeInt with a floating point type, +* and you can cast it out (or assign) to a float as well. +* 4) There is now an Align method. I noticed people use this a lot, and rarely check errors, so now you have one. +* +* Another major improvement is the addition of external functions - if you just want to check an operation, this can now +happen: +* All of the following can be invoked without dealing with creating a class, or managing exceptions. This is especially +handy +* for 64-bit porting, since SafeCast compiles away for a 32-bit cast from size_t to unsigned long, but checks it for +64-bit. +* +* inline bool SafeCast( const T From, U& To ) throw() +* inline bool SafeEquals( const T t, const U u ) throw() +* inline bool SafeNotEquals( const T t, const U u ) throw() +* inline bool SafeGreaterThan( const T t, const U u ) throw() +* inline bool SafeGreaterThanEquals( const T t, const U u ) throw() +* inline bool SafeLessThan( const T t, const U u ) throw() +* inline bool SafeLessThanEquals( const T t, const U u ) throw() +* inline bool SafeModulus( const T& t, const U& u, T& result ) throw() +* inline bool SafeMultiply( T t, U u, T& result ) throw() +* inline bool SafeDivide( T t, U u, T& result ) throw() +* inline bool SafeAdd( T t, U u, T& result ) throw() +* inline bool SafeSubtract( T t, U u, T& result ) throw() +* +*/ + +// use these if the compiler does not support _intXX +#ifdef NEEDS_INT_DEFINED +#define __int8 char +#define __int16 short +#define __int32 int +#define __int64 long long +#endif + +namespace msl +{ +namespace safeint3 +{ +// catch these to handle errors +// Currently implemented code values: +// ERROR_ARITHMETIC_OVERFLOW +// EXCEPTION_INT_DIVIDE_BY_ZERO +enum SafeIntError +{ + SafeIntNoError = 0, + SafeIntArithmeticOverflow, + SafeIntDivideByZero +}; + +} // namespace safeint3 +} // namespace msl + +/* + * Error handler classes + * Using classes to deal with exceptions is going to allow the most + * flexibility, and we can mix different error handlers in the same project + * or even the same file. It isn't advisable to do this in the same function + * because a SafeInt< int, MyExceptionHandler > isn't the same thing as + * SafeInt< int, YourExceptionHander >. + * If for some reason you have to translate between the two, cast one of them back to its + * native type. + * + * To use your own exception class with SafeInt, first create your exception class, + * which may look something like the SafeIntException class below. The second step is to + * create a template specialization that implements SafeIntOnOverflow and SafeIntOnDivZero. + * For example: + * + * template <> class SafeIntExceptionHandler < YourExceptionClass > + * { + * static __declspec(noreturn) void __stdcall SafeIntOnOverflow() + * { + * throw YourExceptionClass( EXCEPTION_INT_OVERFLOW ); + * } + * + * static __declspec(noreturn) void __stdcall SafeIntOnDivZero() + * { + * throw YourExceptionClass( EXCEPTION_INT_DIVIDE_BY_ZERO ); + * } + * }; + * + * typedef SafeIntExceptionHandler < YourExceptionClass > YourSafeIntExceptionHandler + * You'd then declare your SafeInt objects like this: + * SafeInt< int, YourSafeIntExceptionHandler > + * + * Unfortunately, there is no such thing as partial template specialization in typedef + * statements, so you have three options if you find this cumbersome: + * + * 1) Create a holder class: + * + * template < typename T > + * class MySafeInt + * { + * public: + * SafeInt< T, MyExceptionClass> si; + * }; + * + * You'd then declare an instance like so: + * MySafeInt< int > i; + * + * You'd lose handy things like initialization - it would have to be initialized as: + * + * i.si = 0; + * + * 2) You could create a typedef for every int type you deal with: + * + * typedef SafeInt< int, MyExceptionClass > MySafeInt; + * typedef SafeInt< char, MyExceptionClass > MySafeChar; + * + * and so on. The second approach is probably more usable, and will just drop into code + * better, which is the original intent of the SafeInt class. + * + * 3) If you're going to consistently use a different class to handle your exceptions, + * you can override the default typedef like so: + * + * #define SafeIntDefaultExceptionHandler YourSafeIntExceptionHandler + * + * Overall, this is probably the best approach. + * */ + +// On the Microsoft compiler, violating a throw() annotation is a silent error. +// Other compilers might turn these into exceptions, and some users may want to not have throw() enabled. +// In addition, some error handlers may not throw C++ exceptions, which makes everything no throw. +#if defined SAFEINT_REMOVE_NOTHROW +#define SAFEINT_NOTHROW +#else +#define SAFEINT_NOTHROW throw() +#endif + +namespace msl +{ +namespace safeint3 +{ +// If you would like to use your own custom assert +// Define SAFEINT_ASSERT +#if !defined SAFEINT_ASSERT +#include +#define SAFEINT_ASSERT(x) assert(x) +#endif + +#if defined SAFEINT_ASSERT_ON_EXCEPTION +inline void SafeIntExceptionAssert() SAFEINT_NOTHROW { SAFEINT_ASSERT(false); } +#else +inline void SafeIntExceptionAssert() SAFEINT_NOTHROW {} +#endif + +#if SAFEINT_COMPILER == GCC_COMPILER || SAFEINT_COMPILER == CLANG_COMPILER +#define SAFEINT_NORETURN __attribute__((noreturn)) +#define SAFEINT_STDCALL +#define SAFEINT_VISIBLE __attribute__((__visibility__("default"))) +#define SAFEINT_WEAK __attribute__((weak)) +#else +#define SAFEINT_NORETURN __declspec(noreturn) +#define SAFEINT_STDCALL __stdcall +#define SAFEINT_VISIBLE +#define SAFEINT_WEAK +#endif + +class SAFEINT_VISIBLE SafeIntException +{ +public: + SafeIntException() SAFEINT_NOTHROW { m_code = SafeIntNoError; } + SafeIntException(SafeIntError code) SAFEINT_NOTHROW { m_code = code; } + SafeIntError m_code; +}; + +namespace SafeIntInternal +{ +// Visual Studio version of SafeInt provides for two possible error +// handlers: +// SafeIntErrorPolicy_SafeIntException - C++ exception, default if not otherwise defined +// SafeIntErrorPolicy_InvalidParameter - Calls fail fast (Windows-specific), bypasses any exception handlers, +// exits the app with a crash +template +class SafeIntExceptionHandler; + +template<> +class SafeIntExceptionHandler +{ +public: + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnOverflow() + { + SafeIntExceptionAssert(); + throw SafeIntException(SafeIntArithmeticOverflow); + } + + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnDivZero() + { + SafeIntExceptionAssert(); + throw SafeIntException(SafeIntDivideByZero); + } +}; + +#if !defined _CRT_SECURE_INVALID_PARAMETER +// Calling fail fast is somewhat more robust than calling abort, +// but abort is the closest we can manage without Visual Studio support +// Need the header for abort() +#include +#define _CRT_SECURE_INVALID_PARAMETER(msg) abort() +#endif + +class SafeInt_InvalidParameter +{ +public: + static SAFEINT_NORETURN void SafeIntOnOverflow() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + _CRT_SECURE_INVALID_PARAMETER("SafeInt Arithmetic Overflow"); + } + + static SAFEINT_NORETURN void SafeIntOnDivZero() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + _CRT_SECURE_INVALID_PARAMETER("SafeInt Divide By Zero"); + } +}; + +#if defined _WINDOWS_ + +class SafeIntWin32ExceptionHandler +{ +public: + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnOverflow() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + RaiseException(static_cast(EXCEPTION_INT_OVERFLOW), EXCEPTION_NONCONTINUABLE, 0, 0); + } + + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnDivZero() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + RaiseException(static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), EXCEPTION_NONCONTINUABLE, 0, 0); + } +}; + +#endif + +} // namespace SafeIntInternal + +// both of these have cross-platform support +typedef SafeIntInternal::SafeIntExceptionHandler CPlusPlusExceptionHandler; +typedef SafeIntInternal::SafeInt_InvalidParameter InvalidParameterExceptionHandler; + +// This exception handler is no longer recommended, but is left here in order not to break existing users +#if defined _WINDOWS_ +typedef SafeIntInternal::SafeIntWin32ExceptionHandler Win32ExceptionHandler; +#endif + +// For Visual Studio compatibility +#if defined VISUAL_STUDIO_SAFEINT_COMPAT +typedef CPlusPlusExceptionHandler SafeIntErrorPolicy_SafeIntException; +typedef InvalidParameterExceptionHandler SafeIntErrorPolicy_InvalidParameter; +#endif + +// If the user hasn't defined a default exception handler, +// define one now, depending on whether they would like Win32 or C++ exceptions + +// This library will use conditional noexcept soon, but not in this release +// Some users might mix exception handlers, which is not advised, but is supported +#if !defined SafeIntDefaultExceptionHandler +#if defined SAFEINT_RAISE_EXCEPTION +#if !defined _WINDOWS_ +#error Include windows.h in order to use Win32 exceptions +#endif + +#define SafeIntDefaultExceptionHandler Win32ExceptionHandler +#elif defined SAFEINT_FAILFAST +#define SafeIntDefaultExceptionHandler InvalidParameterExceptionHandler +#else +#define SafeIntDefaultExceptionHandler CPlusPlusExceptionHandler +#if !defined SAFEINT_EXCEPTION_HANDLER_CPP +#define SAFEINT_EXCEPTION_HANDLER_CPP 1 +#endif +#endif +#endif + +#if !defined SAFEINT_EXCEPTION_HANDLER_CPP +#define SAFEINT_EXCEPTION_HANDLER_CPP 0 +#endif + +// If an error handler is chosen other than C++ exceptions, such as Win32 exceptions, fail fast, +// or abort, then all methods become no throw. Some teams track throw() annotations closely, +// and the following option provides for this. +#if SAFEINT_EXCEPTION_HANDLER_CPP +#define SAFEINT_CPP_THROW +#else +#define SAFEINT_CPP_THROW SAFEINT_NOTHROW +#endif + +// Turns out we can fool the compiler into not seeing compile-time constants with +// a simple template specialization +template +class CompileConst; +template<> +class CompileConst +{ +public: + static bool Value() SAFEINT_NOTHROW { return true; } +}; +template<> +class CompileConst +{ +public: + static bool Value() SAFEINT_NOTHROW { return false; } +}; + +// The following template magic is because we're now not allowed +// to cast a float to an enum. This means that if we happen to assign +// an enum to a SafeInt of some type, it won't compile, unless we prevent +// isFloat = ( (T)( (float)1.1 ) > (T)1 ) +// from compiling in the case of an enum, which is the point of the specialization +// that follows. + +// If we have support for std, then we can do this easily, and detect enums as well +template +class NumericType; + +#if defined _LIBCPP_TYPE_TRAITS || defined _TYPE_TRAITS_ +// Continue to special case bool +template<> +class NumericType +{ +public: + enum + { + isBool = true, + isFloat = false, + isInt = false + }; +}; +template +class NumericType +{ +public: + enum + { + isBool = false, // We specialized out a bool + isFloat = std::is_floating_point::value, + // If it is an enum, then consider it an int type + // This does allow someone to make a SafeInt from an enum type, which is not recommended, + // but it also allows someone to add an enum value to a SafeInt, which is handy. + isInt = std::is_integral::value || std::is_enum::value + }; +}; + +#else + +template<> +class NumericType +{ +public: + enum + { + isBool = true, + isFloat = false, + isInt = false + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +#if defined SAFEINT_USE_WCHAR_T || defined _NATIVE_WCHAR_T_DEFINED +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +#endif +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType<__int64> +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = false, + isInt = true + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = true, + isInt = false + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = true, + isInt = false + }; +}; +template<> +class NumericType +{ +public: + enum + { + isBool = false, + isFloat = true, + isInt = false + }; +}; +// Catch-all for anything not supported +template +class NumericType +{ +public: + // We have some unknown type, which could be an enum. For parity with the code that uses , + // We can try a static_cast - it if compiles, then it might be an enum, and should work. + // If it is something else that just happens to have a constructor that takes an int, and a casting operator, + // then it is possible something will go wrong, and for best results, cast it directly to an int before letting it + // interact with a SafeInt + + enum + { + isBool = false, + isFloat = false, + isInt = static_cast(static_cast(0)) == 0 + }; +}; +#endif // type traits + +// Use this to avoid compile-time const truncation warnings +template +class SafeIntMinMax; + +template<> +class SafeIntMinMax +{ +public: + const static signed __int8 min = (-0x7f - 1); + const static signed __int8 max = 0x7f; +}; +template<> +class SafeIntMinMax +{ +public: + const static __int16 min = (-0x7fff - 1); + const static __int16 max = 0x7fff; +}; +template<> +class SafeIntMinMax +{ +public: + const static __int32 min = (-0x7fffffff - 1); + const static __int32 max = 0x7fffffff; +}; +template<> +class SafeIntMinMax +{ +public: + const static __int64 min = static_cast<__int64>(0x8000000000000000LL); + const static __int64 max = 0x7fffffffffffffffLL; +}; + +template<> +class SafeIntMinMax +{ +public: + const static unsigned __int8 min = 0; + const static unsigned __int8 max = 0xff; +}; +template<> +class SafeIntMinMax +{ +public: + const static unsigned __int16 min = 0; + const static unsigned __int16 max = 0xffff; +}; +template<> +class SafeIntMinMax +{ +public: + const static unsigned __int32 min = 0; + const static unsigned __int32 max = 0xffffffff; +}; +template<> +class SafeIntMinMax +{ +public: + const static unsigned __int64 min = 0; + const static unsigned __int64 max = 0xffffffffffffffffULL; +}; + +template +class IntTraits +{ +public: + C_ASSERT(NumericType::isInt); + enum + { + isSigned = ((T)(-1) < 0), + is64Bit = (sizeof(T) == 8), + is32Bit = (sizeof(T) == 4), + is16Bit = (sizeof(T) == 2), + is8Bit = (sizeof(T) == 1), + isLT32Bit = (sizeof(T) < 4), + isLT64Bit = (sizeof(T) < 8), + isInt8 = (sizeof(T) == 1 && isSigned), + isUint8 = (sizeof(T) == 1 && !isSigned), + isInt16 = (sizeof(T) == 2 && isSigned), + isUint16 = (sizeof(T) == 2 && !isSigned), + isInt32 = (sizeof(T) == 4 && isSigned), + isUint32 = (sizeof(T) == 4 && !isSigned), + isInt64 = (sizeof(T) == 8 && isSigned), + isUint64 = (sizeof(T) == 8 && !isSigned), + bitCount = (sizeof(T) * 8), + isBool = ((T)2 == (T)1) + }; + + // On version 13.10 enums cannot define __int64 values + // so we'll use const statics instead! + // These must be cast to deal with the possibility of a SafeInt being given an enum as an argument + const static T maxInt = static_cast(SafeIntMinMax::max); + const static T minInt = static_cast(SafeIntMinMax::min); +}; + +template +const T IntTraits::maxInt; +template +const T IntTraits::minInt; + +template +class SafeIntCompare +{ +public: + enum + { + isBothSigned = (IntTraits::isSigned && IntTraits::isSigned), + isBothUnsigned = (!IntTraits::isSigned && !IntTraits::isSigned), + isLikeSigned = ((bool)(IntTraits::isSigned) == (bool)(IntTraits::isSigned)), + isCastOK = ((isLikeSigned && sizeof(T) >= sizeof(U)) || (IntTraits::isSigned && sizeof(T) > sizeof(U))), + isBothLT32Bit = (IntTraits::isLT32Bit && IntTraits::isLT32Bit), + isBothLT64Bit = (IntTraits::isLT64Bit && IntTraits::isLT64Bit) + }; +}; + +// all of the arithmetic operators can be solved by the same code within +// each of these regions without resorting to compile-time constant conditionals +// most operators collapse the problem into less than the 22 zones, but this is used +// as the first cut +// using this also helps ensure that we handle all of the possible cases correctly + +template +class IntRegion +{ +public: + enum + { + // unsigned-unsigned zone + IntZone_UintLT32_UintLT32 = SafeIntCompare::isBothUnsigned && SafeIntCompare::isBothLT32Bit, + IntZone_Uint32_UintLT64 = + SafeIntCompare::isBothUnsigned && IntTraits::is32Bit && IntTraits::isLT64Bit, + IntZone_UintLT32_Uint32 = + SafeIntCompare::isBothUnsigned && IntTraits::isLT32Bit && IntTraits::is32Bit, + IntZone_Uint64_Uint = SafeIntCompare::isBothUnsigned && IntTraits::is64Bit, + IntZone_UintLT64_Uint64 = + SafeIntCompare::isBothUnsigned && IntTraits::isLT64Bit && IntTraits::is64Bit, + // unsigned-signed + IntZone_UintLT32_IntLT32 = + !IntTraits::isSigned && IntTraits::isSigned && SafeIntCompare::isBothLT32Bit, + IntZone_Uint32_IntLT64 = IntTraits::isUint32 && IntTraits::isSigned && IntTraits::isLT64Bit, + IntZone_UintLT32_Int32 = !IntTraits::isSigned && IntTraits::isLT32Bit && IntTraits::isInt32, + IntZone_Uint64_Int = IntTraits::isUint64 && IntTraits::isSigned && IntTraits::isLT64Bit, + IntZone_UintLT64_Int64 = !IntTraits::isSigned && IntTraits::isLT64Bit && IntTraits::isInt64, + IntZone_Uint64_Int64 = IntTraits::isUint64 && IntTraits::isInt64, + // signed-signed + IntZone_IntLT32_IntLT32 = SafeIntCompare::isBothSigned && SafeIntCompare::isBothLT32Bit, + IntZone_Int32_IntLT64 = SafeIntCompare::isBothSigned && IntTraits::is32Bit && IntTraits::isLT64Bit, + IntZone_IntLT32_Int32 = SafeIntCompare::isBothSigned && IntTraits::isLT32Bit && IntTraits::is32Bit, + IntZone_Int64_Int64 = SafeIntCompare::isBothSigned && IntTraits::isInt64 && IntTraits::isInt64, + IntZone_Int64_Int = SafeIntCompare::isBothSigned && IntTraits::is64Bit && IntTraits::isLT64Bit, + IntZone_IntLT64_Int64 = SafeIntCompare::isBothSigned && IntTraits::isLT64Bit && IntTraits::is64Bit, + // signed-unsigned + IntZone_IntLT32_UintLT32 = + IntTraits::isSigned && !IntTraits::isSigned && SafeIntCompare::isBothLT32Bit, + IntZone_Int32_UintLT32 = IntTraits::isInt32 && !IntTraits::isSigned && IntTraits::isLT32Bit, + IntZone_IntLT64_Uint32 = IntTraits::isSigned && IntTraits::isLT64Bit && IntTraits::isUint32, + IntZone_Int64_UintLT64 = IntTraits::isInt64 && !IntTraits::isSigned && IntTraits::isLT64Bit, + IntZone_Int_Uint64 = IntTraits::isSigned && IntTraits::isUint64 && IntTraits::isLT64Bit, + IntZone_Int64_Uint64 = IntTraits::isInt64 && IntTraits::isUint64 + }; +}; + +// In all of the following functions, we have two versions +// One for SafeInt, which throws C++ (or possibly SEH) exceptions +// The non-throwing versions are for use by the helper functions that return success and failure. +// Some of the non-throwing functions are not used, but are maintained for completeness. + +// There's no real alternative to duplicating logic, but keeping the two versions +// immediately next to one another will help reduce problems + +// useful function to help with getting the magnitude of a negative number +enum AbsMethod +{ + AbsMethodInt, + AbsMethodInt64, + AbsMethodNoop +}; + +template +class GetAbsMethod +{ +public: + enum + { + method = IntTraits::isLT64Bit && IntTraits::isSigned + ? AbsMethodInt + : IntTraits::isInt64 ? AbsMethodInt64 : AbsMethodNoop + }; +}; + +// let's go ahead and hard-code a dependency on the +// representation of negative numbers to keep compilers from getting overly +// happy with optimizing away things like -MIN_INT. +template +class AbsValueHelper; + +template +class AbsValueHelper +{ +public: + static unsigned __int32 Abs(T t) SAFEINT_NOTHROW + { + SAFEINT_ASSERT(t < 0); + return ~(unsigned __int32)t + 1; + } +}; + +template +class AbsValueHelper +{ +public: + static unsigned __int64 Abs(T t) SAFEINT_NOTHROW + { + SAFEINT_ASSERT(t < 0); + return ~(unsigned __int64)t + 1; + } +}; + +template +class AbsValueHelper +{ +public: + static T Abs(T t) SAFEINT_NOTHROW + { + // Why are you calling Abs on an unsigned number ??? + SAFEINT_ASSERT(false); + return t; + } +}; + +template +class NegationHelper; +// Previous versions had an assert that the type being negated was 32-bit or higher +// In retrospect, this seems like something to just document +// Negation will normally upcast to int +// For example -(unsigned short)0xffff == (int)0xffff0001 +// This class will retain the type, and will truncate, which may not be what +// you wanted +// If you want normal operator casting behavior, do this: +// SafeInt ss = 0xffff; +// then: +// -(SafeInt(ss)) +// will then emit a signed int with the correct value and bitfield + +template +class NegationHelper // Signed +{ +public: + template + static T NegativeThrow(T t) SAFEINT_CPP_THROW + { + // corner case + if (t != IntTraits::minInt) + { + // cast prevents unneeded checks in the case of small ints + return -t; + } + E::SafeIntOnOverflow(); + } + + static bool Negative(T t, T& ret) SAFEINT_NOTHROW + { + // corner case + if (t != IntTraits::minInt) + { + // cast prevents unneeded checks in the case of small ints + ret = -t; + return true; + } + return false; + } +}; + +// Helper classes to work keep compilers from +// optimizing away negation +template +class SignedNegation; + +template<> +class SignedNegation +{ +public: + static signed __int32 Value(unsigned __int64 in) SAFEINT_NOTHROW + { + return (signed __int32)(~(unsigned __int32)in + 1); + } + + static signed __int32 Value(unsigned __int32 in) SAFEINT_NOTHROW { return (signed __int32)(~in + 1); } +}; + +template<> +class SignedNegation +{ +public: + static signed __int64 Value(unsigned __int64 in) SAFEINT_NOTHROW { return (signed __int64)(~in + 1); } +}; + +template +class NegationHelper // unsigned +{ +public: + template + static T NegativeThrow(T t) SAFEINT_CPP_THROW + { +#if defined SAFEINT_DISALLOW_UNSIGNED_NEGATION + C_ASSERT(sizeof(T) == 0); +#endif + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(push) +// this avoids warnings from the unary '-' operator being applied to unsigned numbers +#pragma warning(disable : 4146) +#endif + // Note - this could be quenched on gcc + // by doing something like: + // return (T)-((__int64)t); + // but it seems like you would want a warning when doing this. + return (T)-t; + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(pop) +#endif + } + + static bool Negative(T t, T& ret) SAFEINT_NOTHROW + { + if (IntTraits::isLT32Bit) + { + // See above + SAFEINT_ASSERT(false); + } +#if defined SAFEINT_DISALLOW_UNSIGNED_NEGATION + C_ASSERT(sizeof(T) == 0); +#endif + // Do it this way to avoid warning + ret = -t; + return true; + } +}; + +// core logic to determine casting behavior +enum CastMethod +{ + CastOK = 0, + CastCheckLTZero, + CastCheckGTMax, + CastCheckSafeIntMinMaxUnsigned, + CastCheckSafeIntMinMaxSigned, + CastToFloat, + CastFromFloat, + CastToBool, + CastFromBool +}; + +template +class GetCastMethod +{ +public: + enum + { + method = (IntTraits::isBool && !IntTraits::isBool) + ? CastFromBool + : + + (!IntTraits::isBool && IntTraits::isBool) + ? CastToBool + : + + (SafeIntCompare::isCastOK) + ? CastOK + : + + ((IntTraits::isSigned && !IntTraits::isSigned && + sizeof(FromType) >= sizeof(ToType)) || + (SafeIntCompare::isBothUnsigned && sizeof(FromType) > sizeof(ToType))) + ? CastCheckGTMax + : + + (!IntTraits::isSigned && IntTraits::isSigned && + sizeof(ToType) >= sizeof(FromType)) + ? CastCheckLTZero + : + + (!IntTraits::isSigned) ? CastCheckSafeIntMinMaxUnsigned + : CastCheckSafeIntMinMaxSigned + }; +}; + +template +class GetCastMethod +{ +public: + enum + { + method = CastOK + }; +}; + +template +class GetCastMethod +{ +public: + enum + { + method = CastOK + }; +}; + +template +class GetCastMethod +{ +public: + enum + { + method = CastOK + }; +}; + +template +class GetCastMethod +{ +public: + enum + { + method = CastFromFloat + }; +}; + +template +class GetCastMethod +{ +public: + enum + { + method = CastFromFloat + }; +}; + +template +class GetCastMethod +{ +public: + enum + { + method = CastFromFloat + }; +}; + +template +class SafeCastHelper; + +template +class SafeCastHelper +{ +public: + static bool Cast(U u, T& t) SAFEINT_NOTHROW + { + t = (T)u; + return true; + } + + template + static void CastThrow(U u, T& t) SAFEINT_CPP_THROW + { + t = (T)u; + } +}; + +// special case floats and doubles +// tolerate loss of precision +template +class SafeCastHelper +{ +public: + static bool Cast(U u, T& t) SAFEINT_NOTHROW + { + if (u <= (U)IntTraits::maxInt && u >= (U)IntTraits::minInt) + { + t = (T)u; + return true; + } + return false; + } + + template + static void CastThrow(U u, T& t) SAFEINT_CPP_THROW + { + if (u <= (U)IntTraits::maxInt && u >= (U)IntTraits::minInt) + { + t = (T)u; + return; + } + E::SafeIntOnOverflow(); + } +}; + +// Match on any method where a bool is cast to type T +template +class SafeCastHelper +{ +public: + static bool Cast(bool b, T& t) SAFEINT_NOTHROW + { + t = (T)(b ? 1 : 0); + return true; + } + + template + static void CastThrow(bool b, T& t) SAFEINT_CPP_THROW + { + t = (T)(b ? 1 : 0); + } +}; + +template +class SafeCastHelper +{ +public: + static bool Cast(T t, bool& b) SAFEINT_NOTHROW + { + b = !!t; + return true; + } + + template + static void CastThrow(bool b, T& t) SAFEINT_CPP_THROW + { + b = !!t; + } +}; + +template +class SafeCastHelper +{ +public: + static bool Cast(U u, T& t) SAFEINT_NOTHROW + { + if (u < 0) return false; + + t = (T)u; + return true; + } + + template + static void CastThrow(U u, T& t) SAFEINT_CPP_THROW + { + if (u < 0) E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template +class SafeCastHelper +{ +public: + static bool Cast(U u, T& t) SAFEINT_NOTHROW + { + if (u > (U)IntTraits::maxInt) return false; + + t = (T)u; + return true; + } + + template + static void CastThrow(U u, T& t) SAFEINT_CPP_THROW + { + if (u > (U)IntTraits::maxInt) E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template +class SafeCastHelper +{ +public: + static bool Cast(U u, T& t) SAFEINT_NOTHROW + { + // U is signed - T could be either signed or unsigned + if (u > IntTraits::maxInt || u < 0) return false; + + t = (T)u; + return true; + } + + template + static void CastThrow(U u, T& t) SAFEINT_CPP_THROW + { + // U is signed - T could be either signed or unsigned + if (u > IntTraits::maxInt || u < 0) E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template +class SafeCastHelper +{ +public: + static bool Cast(U u, T& t) SAFEINT_NOTHROW + { + // T, U are signed + if (u > IntTraits::maxInt || u < IntTraits::minInt) return false; + + t = (T)u; + return true; + } + + template + static void CastThrow(U u, T& t) SAFEINT_CPP_THROW + { + // T, U are signed + if (u > IntTraits::maxInt || u < IntTraits::minInt) E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +// core logic to determine whether a comparison is valid, or needs special treatment +enum ComparisonMethod +{ + ComparisonMethod_Ok = 0, + ComparisonMethod_CastInt, + ComparisonMethod_CastInt64, + ComparisonMethod_UnsignedT, + ComparisonMethod_UnsignedU +}; + +// Note - the standard is arguably broken in the case of some integer +// conversion operations +// For example, signed char a = -1 = 0xff +// unsigned int b = 0xffffffff +// If you then test if a < b, a value-preserving cast +// is made, and you're essentially testing +// (unsigned int)a < b == false +// +// I do not think this makes sense - if you perform +// a cast to an __int64, which can clearly preserve both value and signedness +// then you get a different and intuitively correct answer +// IMHO, -1 should be less than 4 billion +// If you prefer to retain the ANSI standard behavior +// insert #define ANSI_CONVERSIONS into your source +// Behavior differences occur in the following cases: +// 8, 16, and 32-bit signed int, unsigned 32-bit int +// any signed int, unsigned 64-bit int +// Note - the signed int must be negative to show the problem + +template +class ValidComparison +{ +public: + enum + { +#ifdef ANSI_CONVERSIONS + method = ComparisonMethod_Ok +#else + method = ((SafeIntCompare::isLikeSigned) + ? ComparisonMethod_Ok + : ((IntTraits::isSigned && sizeof(T) < 8 && sizeof(U) < 4) || + (IntTraits::isSigned && sizeof(T) < 4 && sizeof(U) < 8)) + ? ComparisonMethod_CastInt + : ((IntTraits::isSigned && sizeof(U) < 8) || (IntTraits::isSigned && sizeof(T) < 8)) + ? ComparisonMethod_CastInt64 + : (!IntTraits::isSigned) ? ComparisonMethod_UnsignedT : ComparisonMethod_UnsignedU) +#endif + }; +}; + +template +class EqualityTest; + +template +class EqualityTest +{ +public: + static bool IsEquals(const T t, const U u) SAFEINT_NOTHROW { return (t == u); } +}; + +template +class EqualityTest +{ +public: + static bool IsEquals(const T t, const U u) SAFEINT_NOTHROW { return ((int)t == (int)u); } +}; + +template +class EqualityTest +{ +public: + static bool IsEquals(const T t, const U u) SAFEINT_NOTHROW { return ((__int64)t == (__int64)u); } +}; + +template +class EqualityTest +{ +public: + static bool IsEquals(const T t, const U u) SAFEINT_NOTHROW + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if (u < 0) return false; + + // else safe to cast to type T + return (t == (T)u); + } +}; + +template +class EqualityTest +{ +public: + static bool IsEquals(const T t, const U u) SAFEINT_NOTHROW + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if (t < 0) return false; + + // else safe to cast to type U + return ((U)t == u); + } +}; + +template +class GreaterThanTest; + +template +class GreaterThanTest +{ +public: + static bool GreaterThan(const T t, const U u) SAFEINT_NOTHROW { return (t > u); } +}; + +template +class GreaterThanTest +{ +public: + static bool GreaterThan(const T t, const U u) SAFEINT_NOTHROW { return ((int)t > (int)u); } +}; + +template +class GreaterThanTest +{ +public: + static bool GreaterThan(const T t, const U u) SAFEINT_NOTHROW { return ((__int64)t > (__int64)u); } +}; + +template +class GreaterThanTest +{ +public: + static bool GreaterThan(const T t, const U u) SAFEINT_NOTHROW + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if (u < 0) return true; + + // else safe to cast to type T + return (t > (T)u); + } +}; + +template +class GreaterThanTest +{ +public: + static bool GreaterThan(const T t, const U u) SAFEINT_NOTHROW + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if (t < 0) return false; + + // else safe to cast to type U + return ((U)t > u); + } +}; + +// Modulus is simpler than comparison, but follows much the same logic +// using this set of functions, it can't fail except in a div 0 situation +template +class ModulusHelper; + +template +class ModulusHelper +{ +public: + static SafeIntError Modulus(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + // trap corner case + if (CompileConst::isSigned>::Value()) + { + // Some compilers don't notice that this only compiles when u is signed + // Add cast to make them happy + if (u == (U)-1) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)(t % u); + return SafeIntNoError; + } + + template + static void ModulusThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + // trap corner case + if (CompileConst::isSigned>::Value()) + { + if (u == (U)-1) + { + result = 0; + return; + } + } + + result = (T)(t % u); + } +}; + +template +class ModulusHelper +{ +public: + static SafeIntError Modulus(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + // trap corner case + if (CompileConst::isSigned>::Value()) + { + if (u == (U)-1) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)(t % u); + return SafeIntNoError; + } + + template + static void ModulusThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + // trap corner case + if (CompileConst::isSigned>::Value()) + { + if (u == (U)-1) + { + result = 0; + return; + } + } + + result = (T)(t % u); + } +}; + +template +class ModulusHelper +{ +public: + static SafeIntError Modulus(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + // trap corner case + if (CompileConst::isSigned>::Value()) + { + if (u == (U)-1) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)((__int64)t % (__int64)u); + return SafeIntNoError; + } + + template + static void ModulusThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + if (CompileConst::isSigned>::Value()) + { + if (u == (U)-1) + { + result = 0; + return; + } + } + + result = (T)((__int64)t % (__int64)u); + } +}; + +// T is unsigned __int64, U is any signed int +template +class ModulusHelper +{ +public: + static SafeIntError Modulus(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + // u could be negative - if so, need to convert to positive + // casts below are always safe due to the way modulus works + if (u < 0) + result = (T)(t % AbsValueHelper::method>::Abs(u)); + else + result = (T)(t % u); + + return SafeIntNoError; + } + + template + static void ModulusThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + // u could be negative - if so, need to convert to positive + if (u < 0) + result = (T)(t % AbsValueHelper::method>::Abs(u)); + else + result = (T)(t % u); + } +}; + +// U is unsigned __int64, T any signed int +template +class ModulusHelper +{ +public: + static SafeIntError Modulus(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + // t could be negative - if so, need to convert to positive + if (t < 0) + result = (T)(~(AbsValueHelper::method>::Abs(t) % u) + 1); + else + result = (T)((T)t % u); + + return SafeIntNoError; + } + + template + static void ModulusThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + // t could be negative - if so, need to convert to positive + if (t < 0) + result = (T)(~(AbsValueHelper::method>::Abs(t) % u) + 1); + else + result = (T)((T)t % u); + } +}; + +// core logic to determine method to check multiplication +enum MultiplicationState +{ + MultiplicationState_CastInt = 0, // One or both signed, smaller than 32-bit + MultiplicationState_CastInt64, // One or both signed, smaller than 64-bit + MultiplicationState_CastUint, // Both are unsigned, smaller than 32-bit + MultiplicationState_CastUint64, // Both are unsigned, both 32-bit or smaller + MultiplicationState_Uint64Uint, // Both are unsigned, lhs 64-bit, rhs 32-bit or smaller + MultiplicationState_Uint64Uint64, // Both are unsigned int64 + MultiplicationState_Uint64Int, // lhs is unsigned int64, rhs int32 + MultiplicationState_Uint64Int64, // lhs is unsigned int64, rhs signed int64 + MultiplicationState_UintUint64, // Both are unsigned, lhs 32-bit or smaller, rhs 64-bit + MultiplicationState_UintInt64, // lhs unsigned 32-bit or less, rhs int64 + MultiplicationState_Int64Uint, // lhs int64, rhs unsigned int32 + MultiplicationState_Int64Int64, // lhs int64, rhs int64 + MultiplicationState_Int64Int, // lhs int64, rhs int32 + MultiplicationState_IntUint64, // lhs int, rhs unsigned int64 + MultiplicationState_IntInt64, // lhs int, rhs int64 + MultiplicationState_Int64Uint64, // lhs int64, rhs uint64 + MultiplicationState_Error +}; + +template +class MultiplicationMethod +{ +public: + enum + { + // unsigned-unsigned + method = + (IntRegion::IntZone_UintLT32_UintLT32 + ? MultiplicationState_CastUint + : (IntRegion::IntZone_Uint32_UintLT64 || IntRegion::IntZone_UintLT32_Uint32) + ? MultiplicationState_CastUint64 + : SafeIntCompare::isBothUnsigned && IntTraits::isUint64 && IntTraits::isUint64 + ? MultiplicationState_Uint64Uint64 + : (IntRegion::IntZone_Uint64_Uint) + ? MultiplicationState_Uint64Uint + : (IntRegion::IntZone_UintLT64_Uint64) ? MultiplicationState_UintUint64 : + // unsigned-signed + (IntRegion::IntZone_UintLT32_IntLT32) + ? MultiplicationState_CastInt + : (IntRegion::IntZone_Uint32_IntLT64 || + IntRegion::IntZone_UintLT32_Int32) + ? MultiplicationState_CastInt64 + : (IntRegion::IntZone_Uint64_Int) + ? MultiplicationState_Uint64Int + : (IntRegion::IntZone_UintLT64_Int64) + ? MultiplicationState_UintInt64 + : (IntRegion::IntZone_Uint64_Int64) + ? MultiplicationState_Uint64Int64 + : + // signed-signed + (IntRegion::IntZone_IntLT32_IntLT32) + ? MultiplicationState_CastInt + : (IntRegion::IntZone_Int32_IntLT64 || + IntRegion::IntZone_IntLT32_Int32) + ? MultiplicationState_CastInt64 + : (IntRegion::IntZone_Int64_Int64) + ? MultiplicationState_Int64Int64 + : (IntRegion::IntZone_Int64_Int) + ? MultiplicationState_Int64Int + : (IntRegion:: + IntZone_IntLT64_Int64) + ? MultiplicationState_IntInt64 + : + // signed-unsigned + (IntRegion:: + IntZone_IntLT32_UintLT32) + ? MultiplicationState_CastInt + : (IntRegion:: + IntZone_Int32_UintLT32 || + IntRegion:: + IntZone_IntLT64_Uint32) + ? MultiplicationState_CastInt64 + : (IntRegion< + T, + U>:: + IntZone_Int64_UintLT64) + ? MultiplicationState_Int64Uint + : (IntRegion< + T, + U>:: + IntZone_Int_Uint64) + ? MultiplicationState_IntUint64 + : (IntRegion< + T, + U>::IntZone_Int64_Uint64 + ? MultiplicationState_Int64Uint64 + : MultiplicationState_Error)) + }; +}; + +template +class MultiplicationHelper; + +template +class MultiplicationHelper +{ +public: + // accepts signed, both less than 32-bit + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + int tmp = t * u; + + if (tmp > IntTraits::maxInt || tmp < IntTraits::minInt) return false; + + ret = (T)tmp; + return true; + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + int tmp = t * u; + + if (tmp > IntTraits::maxInt || tmp < IntTraits::minInt) E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template +class MultiplicationHelper +{ +public: + // accepts unsigned, both less than 32-bit + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + unsigned int tmp = (unsigned int)(t * u); + + if (tmp > IntTraits::maxInt) return false; + + ret = (T)tmp; + return true; + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + unsigned int tmp = (unsigned int)(t * u); + + if (tmp > IntTraits::maxInt) E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template +class MultiplicationHelper +{ +public: + // mixed signed or both signed where at least one argument is 32-bit, and both a 32-bit or less + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + __int64 tmp = (__int64)t * (__int64)u; + + if (tmp > (__int64)IntTraits::maxInt || tmp < (__int64)IntTraits::minInt) return false; + + ret = (T)tmp; + return true; + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + __int64 tmp = (__int64)t * (__int64)u; + + if (tmp > (__int64)IntTraits::maxInt || tmp < (__int64)IntTraits::minInt) E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template +class MultiplicationHelper +{ +public: + // both unsigned where at least one argument is 32-bit, and both are 32-bit or less + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + unsigned __int64 tmp = (unsigned __int64)t * (unsigned __int64)u; + + if (tmp > (unsigned __int64)IntTraits::maxInt) return false; + + ret = (T)tmp; + return true; + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + unsigned __int64 tmp = (unsigned __int64)t * (unsigned __int64)u; + + if (tmp > (unsigned __int64)IntTraits::maxInt) E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +// T = left arg and return type +// U = right arg +template +class LargeIntRegMultiply; + +#if SAFEINT_USE_INTRINSICS +// As usual, unsigned is easy +inline bool IntrinsicMultiplyUint64(const unsigned __int64& a, + const unsigned __int64& b, + unsigned __int64* pRet) SAFEINT_NOTHROW +{ + unsigned __int64 ulHigh = 0; + *pRet = _umul128(a, b, &ulHigh); + return ulHigh == 0; +} + +// Signed, is not so easy +inline bool IntrinsicMultiplyInt64(const signed __int64& a, + const signed __int64& b, + signed __int64* pRet) SAFEINT_NOTHROW +{ + __int64 llHigh = 0; + *pRet = _mul128(a, b, &llHigh); + + // Now we need to figure out what we expect + // If llHigh is 0, then treat *pRet as unsigned + // If llHigh is < 0, then treat *pRet as signed + + if ((a ^ b) < 0) + { + // Negative result expected + if (llHigh == -1 && *pRet < 0 || llHigh == 0 && *pRet == 0) + { + // Everything is within range + return true; + } + } + else + { + // Result should be positive + // Check for overflow + if (llHigh == 0 && (unsigned __int64)*pRet <= IntTraits::maxInt) return true; + } + return false; +} + +#endif + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(const unsigned __int64& a, + const unsigned __int64& b, + unsigned __int64* pRet) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64(a, b, pRet); +#else + unsigned __int32 aHigh, aLow, bHigh, bLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // Note - same approach applies for 128 bit math on a 64-bit system + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if (aHigh == 0) + { + if (bHigh != 0) + { + *pRet = (unsigned __int64)aLow * (unsigned __int64)bHigh; + } + } + else if (bHigh == 0) + { + if (aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)bLow; + } + } + else + { + return false; + } + + if (*pRet != 0) + { + unsigned __int64 tmp; + + if ((unsigned __int32)(*pRet >> 32) != 0) return false; + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)bLow; + *pRet += tmp; + + if (*pRet < tmp) return false; + + return true; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)bLow; + return true; +#endif + } + + template + static void RegMultiplyThrow(const unsigned __int64& a, + const unsigned __int64& b, + unsigned __int64* pRet) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyUint64(a, b, pRet)) E::SafeIntOnOverflow(); +#else + unsigned __int32 aHigh, aLow, bHigh, bLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // Note - same approach applies for 128 bit math on a 64-bit system + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if (aHigh == 0) + { + if (bHigh != 0) + { + *pRet = (unsigned __int64)aLow * (unsigned __int64)bHigh; + } + } + else if (bHigh == 0) + { + if (aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)bLow; + } + } + else + { + E::SafeIntOnOverflow(); + } + + if (*pRet != 0) + { + unsigned __int64 tmp; + + if ((unsigned __int32)(*pRet >> 32) != 0) E::SafeIntOnOverflow(); + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)bLow; + *pRet += tmp; + + if (*pRet < tmp) E::SafeIntOnOverflow(); + + return; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)bLow; +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(const unsigned __int64& a, unsigned __int32 b, unsigned __int64* pRet) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64(a, (unsigned __int64)b, pRet); +#else + unsigned __int32 aHigh, aLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * b + // => (aHigh * b * 2^32) + (aLow * b) + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + + *pRet = 0; + + if (aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)b; + + unsigned __int64 tmp; + + if ((unsigned __int32)(*pRet >> 32) != 0) return false; + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)b; + *pRet += tmp; + + if (*pRet < tmp) return false; + + return true; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)b; + return true; +#endif + } + + template + static void RegMultiplyThrow(const unsigned __int64& a, + unsigned __int32 b, + unsigned __int64* pRet) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyUint64(a, (unsigned __int64)b, pRet)) E::SafeIntOnOverflow(); +#else + unsigned __int32 aHigh, aLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * b + // => (aHigh * b * 2^32) + (aLow * b) + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + + *pRet = 0; + + if (aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)b; + + unsigned __int64 tmp; + + if ((unsigned __int32)(*pRet >> 32) != 0) E::SafeIntOnOverflow(); + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)b; + *pRet += tmp; + + if (*pRet < tmp) E::SafeIntOnOverflow(); + + return; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)b; + return; +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + // Intrinsic not needed + static bool RegMultiply(const unsigned __int64& a, signed __int32 b, unsigned __int64* pRet) SAFEINT_NOTHROW + { + if (b < 0 && a != 0) return false; + +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64(a, (unsigned __int64)b, pRet); +#else + return LargeIntRegMultiply::RegMultiply(a, (unsigned __int32)b, pRet); +#endif + } + + template + static void RegMultiplyThrow(const unsigned __int64& a, signed __int32 b, unsigned __int64* pRet) SAFEINT_CPP_THROW + { + if (b < 0 && a != 0) E::SafeIntOnOverflow(); + +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyUint64(a, (unsigned __int64)b, pRet)) E::SafeIntOnOverflow(); +#else + LargeIntRegMultiply::template RegMultiplyThrow( + a, (unsigned __int32)b, pRet); +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(const unsigned __int64& a, signed __int64 b, unsigned __int64* pRet) SAFEINT_NOTHROW + { + if (b < 0 && a != 0) return false; + +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64(a, (unsigned __int64)b, pRet); +#else + return LargeIntRegMultiply::RegMultiply(a, (unsigned __int64)b, pRet); +#endif + } + + template + static void RegMultiplyThrow(const unsigned __int64& a, signed __int64 b, unsigned __int64* pRet) SAFEINT_CPP_THROW + { + if (b < 0 && a != 0) E::SafeIntOnOverflow(); + +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyUint64(a, (unsigned __int64)b, pRet)) E::SafeIntOnOverflow(); +#else + LargeIntRegMultiply::template RegMultiplyThrow( + a, (unsigned __int64)b, pRet); +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + // Devolves into ordinary 64-bit calculation + static bool RegMultiply(signed __int32 a, const unsigned __int64& b, signed __int32* pRet) SAFEINT_NOTHROW + { + unsigned __int32 bHigh, bLow; + bool fIsNegative = false; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // aHigh == 0 implies: + // ( aLow * bHigh * 2^32 ) + ( aLow + bLow ) + // If the first part is != 0, fail + + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if (bHigh != 0 && a != 0) return false; + + if (a < 0) + { + a = (signed __int32)AbsValueHelper::method>::Abs(a); + fIsNegative = true; + } + + unsigned __int64 tmp = (unsigned __int32)a * (unsigned __int64)bLow; + + if (!fIsNegative) + { + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int32)tmp; + return true; + } + } + else + { + if (tmp <= (unsigned __int64)IntTraits::maxInt + 1) + { + *pRet = SignedNegation::Value(tmp); + return true; + } + } + + return false; + } + + template + static void RegMultiplyThrow(signed __int32 a, const unsigned __int64& b, signed __int32* pRet) SAFEINT_CPP_THROW + { + unsigned __int32 bHigh, bLow; + bool fIsNegative = false; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if (bHigh != 0 && a != 0) E::SafeIntOnOverflow(); + + if (a < 0) + { + a = (signed __int32)AbsValueHelper::method>::Abs(a); + fIsNegative = true; + } + + unsigned __int64 tmp = (unsigned __int32)a * (unsigned __int64)bLow; + + if (!fIsNegative) + { + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int32)tmp; + return; + } + } + else + { + if (tmp <= (unsigned __int64)IntTraits::maxInt + 1) + { + *pRet = SignedNegation::Value(tmp); + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + // Becomes ordinary 64-bit multiplication, intrinsic not needed + static bool RegMultiply(unsigned __int32 a, const unsigned __int64& b, unsigned __int32* pRet) SAFEINT_NOTHROW + { + // Consider that a*b can be broken up into: + // (bHigh * 2^32 + bLow) * a + // => (bHigh * a * 2^32) + (bLow * a) + // In this case, the result must fit into 32-bits + // If bHigh != 0 && a != 0, immediate error. + + if ((unsigned __int32)(b >> 32) != 0 && a != 0) return false; + + unsigned __int64 tmp = b * (unsigned __int64)a; + + if ((unsigned __int32)(tmp >> 32) != 0) // overflow + return false; + + *pRet = (unsigned __int32)tmp; + return true; + } + + template + static void RegMultiplyThrow(unsigned __int32 a, + const unsigned __int64& b, + unsigned __int32* pRet) SAFEINT_CPP_THROW + { + if ((unsigned __int32)(b >> 32) != 0 && a != 0) E::SafeIntOnOverflow(); + + unsigned __int64 tmp = b * (unsigned __int64)a; + + if ((unsigned __int32)(tmp >> 32) != 0) // overflow + E::SafeIntOnOverflow(); + + *pRet = (unsigned __int32)tmp; + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(unsigned __int32 a, const signed __int64& b, unsigned __int32* pRet) SAFEINT_NOTHROW + { + if (b < 0 && a != 0) return false; + return LargeIntRegMultiply::RegMultiply(a, (unsigned __int64)b, pRet); + } + + template + static void RegMultiplyThrow(unsigned __int32 a, const signed __int64& b, unsigned __int32* pRet) SAFEINT_CPP_THROW + { + if (b < 0 && a != 0) E::SafeIntOnOverflow(); + + LargeIntRegMultiply::template RegMultiplyThrow( + a, (unsigned __int64)b, pRet); + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(const signed __int64& a, const signed __int64& b, signed __int64* pRet) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64(a, b, pRet); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + if (b1 < 0) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper::method>::Abs(b1); + } + + if (LargeIntRegMultiply::RegMultiply( + (unsigned __int64)a1, (unsigned __int64)b1, &tmp)) + { + // The unsigned multiplication didn't overflow + if (aNegative ^ bNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return true; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template + static void RegMultiplyThrow(const signed __int64& a, + const signed __int64& b, + signed __int64* pRet) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyInt64(a, b, pRet)) E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + if (b1 < 0) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper::method>::Abs(b1); + } + + LargeIntRegMultiply::template RegMultiplyThrow( + (unsigned __int64)a1, (unsigned __int64)b1, &tmp); + + // The unsigned multiplication didn't overflow or we'd be in the exception handler + if (aNegative ^ bNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(const signed __int64& a, unsigned __int32 b, signed __int64* pRet) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64(a, (signed __int64)b, pRet); +#else + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + if (LargeIntRegMultiply::RegMultiply((unsigned __int64)a1, b, &tmp)) + { + // The unsigned multiplication didn't overflow + if (aNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return true; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template + static void RegMultiplyThrow(const signed __int64& a, unsigned __int32 b, signed __int64* pRet) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyInt64(a, (signed __int64)b, pRet)) E::SafeIntOnOverflow(); +#else + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + LargeIntRegMultiply::template RegMultiplyThrow( + (unsigned __int64)a1, b, &tmp); + + // The unsigned multiplication didn't overflow + if (aNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(const signed __int64& a, signed __int32 b, signed __int64* pRet) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64(a, (signed __int64)b, pRet); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + if (b1 < 0) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper::method>::Abs(b1); + } + + if (LargeIntRegMultiply::RegMultiply( + (unsigned __int64)a1, (unsigned __int32)b1, &tmp)) + { + // The unsigned multiplication didn't overflow + if (aNegative ^ bNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return true; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template + static void RegMultiplyThrow(signed __int64 a, signed __int32 b, signed __int64* pRet) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if (!IntrinsicMultiplyInt64(a, (signed __int64)b, pRet)) E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + + if (a < 0) + { + aNegative = true; + a = (signed __int64)AbsValueHelper::method>::Abs(a); + } + + if (b < 0) + { + bNegative = true; + b = (signed __int32)AbsValueHelper::method>::Abs(b); + } + + LargeIntRegMultiply::template RegMultiplyThrow( + (unsigned __int64)a, (unsigned __int32)b, &tmp); + + // The unsigned multiplication didn't overflow + if (aNegative ^ bNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + static bool RegMultiply(signed __int32 a, const signed __int64& b, signed __int32* pRet) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + __int64 tmp; + + if (IntrinsicMultiplyInt64(a, b, &tmp)) + { + if (tmp > IntTraits::maxInt || tmp < IntTraits::minInt) + { + return false; + } + + *pRet = (__int32)tmp; + return true; + } + return false; +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int32 tmp; + __int64 b1 = b; + + if (a < 0) + { + aNegative = true; + a = (signed __int32)AbsValueHelper::method>::Abs(a); + } + + if (b1 < 0) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper::method>::Abs(b1); + } + + if (LargeIntRegMultiply::RegMultiply( + (unsigned __int32)a, (unsigned __int64)b1, &tmp)) + { + // The unsigned multiplication didn't overflow + if (aNegative ^ bNegative) + { + // Result must be negative + if (tmp <= (unsigned __int32)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return true; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int32)IntTraits::maxInt) + { + *pRet = (signed __int32)tmp; + return true; + } + } + } + + return false; +#endif + } + + template + static void RegMultiplyThrow(signed __int32 a, const signed __int64& b, signed __int32* pRet) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + __int64 tmp; + + if (IntrinsicMultiplyInt64(a, b, &tmp)) + { + if (tmp > IntTraits::maxInt || tmp < IntTraits::minInt) + { + E::SafeIntOnOverflow(); + } + + *pRet = (__int32)tmp; + return; + } + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int32 tmp; + signed __int64 b2 = b; + + if (a < 0) + { + aNegative = true; + a = (signed __int32)AbsValueHelper::method>::Abs(a); + } + + if (b < 0) + { + bNegative = true; + b2 = (signed __int64)AbsValueHelper::method>::Abs(b2); + } + + LargeIntRegMultiply::template RegMultiplyThrow( + (unsigned __int32)a, (unsigned __int64)b2, &tmp); + + // The unsigned multiplication didn't overflow + if (aNegative ^ bNegative) + { + // Result must be negative + if (tmp <= (unsigned __int32)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int32)IntTraits::maxInt) + { + *pRet = (signed __int32)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> +class LargeIntRegMultiply +{ +public: + // Leave this one as-is - will call unsigned intrinsic internally + static bool RegMultiply(const signed __int64& a, const unsigned __int64& b, signed __int64* pRet) SAFEINT_NOTHROW + { + bool aNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + if (LargeIntRegMultiply::RegMultiply( + (unsigned __int64)a1, (unsigned __int64)b, &tmp)) + { + // The unsigned multiplication didn't overflow + if (aNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return true; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; + } + + template + static void RegMultiplyThrow(const signed __int64& a, + const unsigned __int64& b, + signed __int64* pRet) SAFEINT_CPP_THROW + { + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if (a1 < 0) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper::method>::Abs(a1); + } + + if (LargeIntRegMultiply::RegMultiply( + (unsigned __int64)a1, (unsigned __int64)b, &tmp)) + { + // The unsigned multiplication didn't overflow + if (aNegative) + { + // Result must be negative + if (tmp <= (unsigned __int64)IntTraits::minInt) + { + *pRet = SignedNegation::Value(tmp); + return; + } + } + else + { + // Result must be positive + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + *pRet = (signed __int64)tmp; + return; + } + } + } + + E::SafeIntOnOverflow(); + } +}; + +// In all of the following functions where LargeIntRegMultiply methods are called, +// we need to properly transition types. The methods need __int64, __int32, etc. +// but the variables being passed to us could be long long, long int, or long, depending on +// the compiler. Microsoft compiler knows that long long is the same type as __int64, but gcc doesn't + +template +class MultiplicationHelper +{ +public: + // T, U are unsigned __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64 && IntTraits::isUint64); + unsigned __int64 t1 = t; + unsigned __int64 u1 = u; + return LargeIntRegMultiply::RegMultiply( + t1, u1, reinterpret_cast(&ret)); + } + + template + static void MultiplyThrow(const unsigned __int64& t, const unsigned __int64& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64 && IntTraits::isUint64); + unsigned __int64 t1 = t; + unsigned __int64 u1 = u; + LargeIntRegMultiply::template RegMultiplyThrow( + t1, u1, reinterpret_cast(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is unsigned __int64 + // U is any unsigned int 32-bit or less + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 t1 = t; + return LargeIntRegMultiply::RegMultiply( + t1, (unsigned __int32)u, reinterpret_cast(&ret)); + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 t1 = t; + LargeIntRegMultiply::template RegMultiplyThrow( + t1, (unsigned __int32)u, reinterpret_cast(&ret)); + } +}; + +// converse of the previous function +template +class MultiplicationHelper +{ +public: + // T is any unsigned int up to 32-bit + // U is unsigned __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 u1 = u; + unsigned __int32 tmp; + + if (LargeIntRegMultiply::RegMultiply(t, u1, &tmp) && + SafeCastHelper::method>::Cast(tmp, ret)) + { + return true; + } + + return false; + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 u1 = u; + unsigned __int32 tmp; + + LargeIntRegMultiply::template RegMultiplyThrow(t, u1, &tmp); + SafeCastHelper::method>::template CastThrow(tmp, + ret); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is unsigned __int64 + // U is any signed int, up to 64-bit + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 t1 = t; + return LargeIntRegMultiply::RegMultiply( + t1, (signed __int32)u, reinterpret_cast(&ret)); + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 t1 = t; + LargeIntRegMultiply::template RegMultiplyThrow( + t1, (signed __int32)u, reinterpret_cast(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is unsigned __int64 + // U is __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64 && IntTraits::isInt64); + unsigned __int64 t1 = t; + __int64 u1 = u; + return LargeIntRegMultiply::RegMultiply( + t1, u1, reinterpret_cast(&ret)); + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64 && IntTraits::isInt64); + unsigned __int64 t1 = t; + __int64 u1 = u; + LargeIntRegMultiply::template RegMultiplyThrow( + t1, u1, reinterpret_cast(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is unsigned up to 32-bit + // U is __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64); + __int64 u1 = u; + unsigned __int32 tmp; + + if (LargeIntRegMultiply::RegMultiply((unsigned __int32)t, u1, &tmp) && + SafeCastHelper::method>::Cast(tmp, ret)) + { + return true; + } + + return false; + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64); + __int64 u1 = u; + unsigned __int32 tmp; + + LargeIntRegMultiply::template RegMultiplyThrow((unsigned __int32)t, u1, &tmp); + SafeCastHelper::method>::template CastThrow(tmp, + ret); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is __int64 + // U is unsigned up to 32-bit + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64); + __int64 t1 = t; + return LargeIntRegMultiply<__int64, unsigned __int32>::RegMultiply( + t1, (unsigned __int32)u, reinterpret_cast<__int64*>(&ret)); + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64); + __int64 t1 = t; + LargeIntRegMultiply<__int64, unsigned __int32>::template RegMultiplyThrow( + t1, (unsigned __int32)u, reinterpret_cast<__int64*>(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T, U are __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isInt64); + __int64 t1 = t; + __int64 u1 = u; + return LargeIntRegMultiply<__int64, __int64>::RegMultiply(t1, u1, reinterpret_cast<__int64*>(&ret)); + } + + template + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isInt64); + __int64 t1 = t; + __int64 u1 = u; + LargeIntRegMultiply<__int64, __int64>::template RegMultiplyThrow(t1, u1, reinterpret_cast<__int64*>(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is __int64 + // U is signed up to 32-bit + static bool Multiply(const T& t, U u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64); + __int64 t1 = t; + return LargeIntRegMultiply<__int64, __int32>::RegMultiply(t1, (__int32)u, reinterpret_cast<__int64*>(&ret)); + } + + template + static void MultiplyThrow(const __int64& t, U u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64); + __int64 t1 = t; + LargeIntRegMultiply<__int64, __int32>::template RegMultiplyThrow( + t1, (__int32)u, reinterpret_cast<__int64*>(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is signed up to 32-bit + // U is unsigned __int64 + static bool Multiply(T t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 u1 = u; + __int32 tmp; + + if (LargeIntRegMultiply<__int32, unsigned __int64>::RegMultiply((__int32)t, u1, &tmp) && + SafeCastHelper::method>::Cast(tmp, ret)) + { + return true; + } + + return false; + } + + template + static void MultiplyThrow(T t, const unsigned __int64& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64); + unsigned __int64 u1 = u; + __int32 tmp; + + LargeIntRegMultiply<__int32, unsigned __int64>::template RegMultiplyThrow((__int32)t, u1, &tmp); + SafeCastHelper::method>::template CastThrow(tmp, ret); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is __int64 + // U is unsigned __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isUint64); + __int64 t1 = t; + unsigned __int64 u1 = u; + return LargeIntRegMultiply<__int64, unsigned __int64>::RegMultiply(t1, u1, reinterpret_cast<__int64*>(&ret)); + } + + template + static void MultiplyThrow(const __int64& t, const unsigned __int64& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isUint64); + __int64 t1 = t; + unsigned __int64 u1 = u; + LargeIntRegMultiply<__int64, unsigned __int64>::template RegMultiplyThrow( + t1, u1, reinterpret_cast<__int64*>(&ret)); + } +}; + +template +class MultiplicationHelper +{ +public: + // T is signed, up to 32-bit + // U is __int64 + static bool Multiply(T t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64); + __int64 u1 = u; + __int32 tmp; + + if (LargeIntRegMultiply<__int32, __int64>::RegMultiply((__int32)t, u1, &tmp) && + SafeCastHelper::method>::Cast(tmp, ret)) + { + return true; + } + + return false; + } + + template + static void MultiplyThrow(T t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64); + __int64 u1 = u; + __int32 tmp; + + LargeIntRegMultiply<__int32, __int64>::template RegMultiplyThrow((__int32)t, u1, &tmp); + SafeCastHelper::method>::template CastThrow(tmp, ret); + } +}; + +enum DivisionState +{ + DivisionState_OK, + DivisionState_UnsignedSigned, + DivisionState_SignedUnsigned32, + DivisionState_SignedUnsigned64, + DivisionState_SignedUnsigned, + DivisionState_SignedSigned +}; + +template +class DivisionMethod +{ +public: + enum + { + method = + (SafeIntCompare::isBothUnsigned + ? DivisionState_OK + : (!IntTraits::isSigned && IntTraits::isSigned) + ? DivisionState_UnsignedSigned + : (IntTraits::isSigned && IntTraits::isUint32 && IntTraits::isLT64Bit) + ? DivisionState_SignedUnsigned32 + : (IntTraits::isSigned && IntTraits::isUint64) + ? DivisionState_SignedUnsigned64 + : (IntTraits::isSigned && !IntTraits::isSigned) ? DivisionState_SignedUnsigned + : DivisionState_SignedSigned) + }; +}; + +template +class DivisionHelper; + +template +class DivisionHelper +{ +public: + static SafeIntError Divide(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + if (t == 0) + { + result = 0; + return SafeIntNoError; + } + + result = (T)(t / u); + return SafeIntNoError; + } + + template + static void DivideThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + if (t == 0) + { + result = 0; + return; + } + + result = (T)(t / u); + } +}; + +template +class DivisionHelper +{ +public: + static SafeIntError Divide(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + if (t == 0) + { + result = 0; + return SafeIntNoError; + } + + if (u > 0) + { + result = (T)(t / u); + return SafeIntNoError; + } + + // it is always an error to try and divide an unsigned number by a negative signed number + // unless u is bigger than t + if (AbsValueHelper::method>::Abs(u) > t) + { + result = 0; + return SafeIntNoError; + } + + return SafeIntArithmeticOverflow; + } + + template + static void DivideThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) E::SafeIntOnDivZero(); + + if (t == 0) + { + result = 0; + return; + } + + if (u > 0) + { + result = (T)(t / u); + return; + } + + // it is always an error to try and divide an unsigned number by a negative signed number + // unless u is bigger than t + if (AbsValueHelper::method>::Abs(u) > t) + { + result = 0; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class DivisionHelper +{ +public: + static SafeIntError Divide(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) return SafeIntDivideByZero; + + if (t == 0) + { + result = 0; + return SafeIntNoError; + } + + // Test for t > 0 + // If t < 0, must explicitly upcast, or implicit upcast to ulong will cause errors + // As it turns out, 32-bit division is about twice as fast, which justifies the extra conditional + + if (t > 0) + result = (T)(t / u); + else + result = (T)((__int64)t / (__int64)u); + + return SafeIntNoError; + } + + template + static void DivideThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) + { + E::SafeIntOnDivZero(); + } + + if (t == 0) + { + result = 0; + return; + } + + // Test for t > 0 + // If t < 0, must explicitly upcast, or implicit upcast to ulong will cause errors + // As it turns out, 32-bit division is about twice as fast, which justifies the extra conditional + + if (t > 0) + result = (T)(t / u); + else + result = (T)((__int64)t / (__int64)u); + } +}; + +template +class DivisionHelper +{ +public: + static SafeIntError Divide(const T& t, const unsigned __int64& u, T& result) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64); + + if (u == 0) + { + return SafeIntDivideByZero; + } + + if (t == 0) + { + result = 0; + return SafeIntNoError; + } + + if (u <= (unsigned __int64)IntTraits::maxInt) + { + // Else u can safely be cast to T + if (CompileConst::Value()) + result = (T)((int)t / (int)u); + else + result = (T)((__int64)t / (__int64)u); + } + else // Corner case + if (t == IntTraits::minInt && u == (unsigned __int64)IntTraits::minInt) + { + // Min int divided by it's own magnitude is -1 + result = -1; + } + else + { + result = 0; + } + return SafeIntNoError; + } + + template + static void DivideThrow(const T& t, const unsigned __int64& u, T& result) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64); + + if (u == 0) + { + E::SafeIntOnDivZero(); + } + + if (t == 0) + { + result = 0; + return; + } + + if (u <= (unsigned __int64)IntTraits::maxInt) + { + // Else u can safely be cast to T + if (CompileConst::Value()) + result = (T)((int)t / (int)u); + else + result = (T)((__int64)t / (__int64)u); + } + else // Corner case + if (t == IntTraits::minInt && u == (unsigned __int64)IntTraits::minInt) + { + // Min int divided by it's own magnitude is -1 + result = -1; + } + else + { + result = 0; + } + } +}; + +template +class DivisionHelper +{ +public: + // T is any signed, U is unsigned and smaller than 32-bit + // In this case, standard operator casting is correct + static SafeIntError Divide(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) + { + return SafeIntDivideByZero; + } + + if (t == 0) + { + result = 0; + return SafeIntNoError; + } + + result = (T)(t / u); + return SafeIntNoError; + } + + template + static void DivideThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) + { + E::SafeIntOnDivZero(); + } + + if (t == 0) + { + result = 0; + return; + } + + result = (T)(t / u); + } +}; + +template +class DivisionHelper +{ +public: + static SafeIntError Divide(const T& t, const U& u, T& result) SAFEINT_NOTHROW + { + if (u == 0) + { + return SafeIntDivideByZero; + } + + if (t == 0) + { + result = 0; + return SafeIntNoError; + } + + // Must test for corner case + if (t == IntTraits::minInt && u == (U)-1) return SafeIntArithmeticOverflow; + + result = (T)(t / u); + return SafeIntNoError; + } + + template + static void DivideThrow(const T& t, const U& u, T& result) SAFEINT_CPP_THROW + { + if (u == 0) + { + E::SafeIntOnDivZero(); + } + + if (t == 0) + { + result = 0; + return; + } + + // Must test for corner case + if (t == IntTraits::minInt && u == (U)-1) E::SafeIntOnOverflow(); + + result = (T)(t / u); + } +}; + +enum AdditionState +{ + AdditionState_CastIntCheckMax, + AdditionState_CastUintCheckOverflow, + AdditionState_CastUintCheckOverflowMax, + AdditionState_CastUint64CheckOverflow, + AdditionState_CastUint64CheckOverflowMax, + AdditionState_CastIntCheckSafeIntMinMax, + AdditionState_CastInt64CheckSafeIntMinMax, + AdditionState_CastInt64CheckMax, + AdditionState_CastUint64CheckSafeIntMinMax, + AdditionState_CastUint64CheckSafeIntMinMax2, + AdditionState_CastInt64CheckOverflow, + AdditionState_CastInt64CheckOverflowSafeIntMinMax, + AdditionState_CastInt64CheckOverflowMax, + AdditionState_ManualCheckInt64Uint64, + AdditionState_ManualCheck, + AdditionState_Error +}; + +template +class AdditionMethod +{ +public: + enum + { + // unsigned-unsigned + method = + (IntRegion::IntZone_UintLT32_UintLT32 + ? AdditionState_CastIntCheckMax + : (IntRegion::IntZone_Uint32_UintLT64) + ? AdditionState_CastUintCheckOverflow + : (IntRegion::IntZone_UintLT32_Uint32) + ? AdditionState_CastUintCheckOverflowMax + : (IntRegion::IntZone_Uint64_Uint) + ? AdditionState_CastUint64CheckOverflow + : (IntRegion::IntZone_UintLT64_Uint64) + ? AdditionState_CastUint64CheckOverflowMax + : + // unsigned-signed + (IntRegion::IntZone_UintLT32_IntLT32) + ? AdditionState_CastIntCheckSafeIntMinMax + : (IntRegion::IntZone_Uint32_IntLT64 || + IntRegion::IntZone_UintLT32_Int32) + ? AdditionState_CastInt64CheckSafeIntMinMax + : (IntRegion::IntZone_Uint64_Int || + IntRegion::IntZone_Uint64_Int64) + ? AdditionState_CastUint64CheckSafeIntMinMax + : (IntRegion::IntZone_UintLT64_Int64) + ? AdditionState_CastUint64CheckSafeIntMinMax2 + : + // signed-signed + (IntRegion::IntZone_IntLT32_IntLT32) + ? AdditionState_CastIntCheckSafeIntMinMax + : (IntRegion::IntZone_Int32_IntLT64 || + IntRegion::IntZone_IntLT32_Int32) + ? AdditionState_CastInt64CheckSafeIntMinMax + : (IntRegion::IntZone_Int64_Int || + IntRegion::IntZone_Int64_Int64) + ? AdditionState_CastInt64CheckOverflow + : (IntRegion::IntZone_IntLT64_Int64) + ? AdditionState_CastInt64CheckOverflowSafeIntMinMax + : + // signed-unsigned + (IntRegion:: + IntZone_IntLT32_UintLT32) + ? AdditionState_CastIntCheckMax + : (IntRegion:: + IntZone_Int32_UintLT32 || + IntRegion:: + IntZone_IntLT64_Uint32) + ? AdditionState_CastInt64CheckMax + : (IntRegion:: + IntZone_Int64_UintLT64) + ? AdditionState_CastInt64CheckOverflowMax + : (IntRegion:: + IntZone_Int64_Uint64) + ? AdditionState_ManualCheckInt64Uint64 + : (IntRegion< + T, + U>:: + IntZone_Int_Uint64) + ? AdditionState_ManualCheck + : AdditionState_Error) + }; +}; + +template +class AdditionHelper; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // 16-bit or less unsigned addition + __int32 tmp = lhs + rhs; + + if (tmp <= (__int32)IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // 16-bit or less unsigned addition + __int32 tmp = lhs + rhs; + + if (tmp <= (__int32)IntTraits::maxInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // we added didn't get smaller + if (tmp >= lhs) + { + result = (T)tmp; + return true; + } + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // we added didn't get smaller + if (tmp >= lhs) + { + result = (T)tmp; + return; + } + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // We added and it didn't get smaller or exceed maxInt + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // We added and it didn't get smaller or exceed maxInt + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if (tmp >= lhs) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if (tmp >= lhs) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // 16-bit or less - one or both are signed + __int32 tmp = lhs + rhs; + + if (tmp <= (__int32)IntTraits::maxInt && tmp >= (__int32)IntTraits::minInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // 16-bit or less - one or both are signed + __int32 tmp = lhs + rhs; + + if (tmp <= (__int32)IntTraits::maxInt && tmp >= (__int32)IntTraits::minInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // 32-bit or less - one or both are signed + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if (tmp <= (__int64)IntTraits::maxInt && tmp >= (__int64)IntTraits::minInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // 32-bit or less - one or both are signed + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if (tmp <= (__int64)IntTraits::maxInt && tmp >= (__int64)IntTraits::minInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // 32-bit or less - lhs signed, rhs unsigned + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // 32-bit or less - lhs signed, rhs unsigned + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is unsigned __int64, rhs signed + unsigned __int64 tmp; + + if (rhs < 0) + { + // So we're effectively subtracting + tmp = AbsValueHelper::method>::Abs(rhs); + + if (tmp <= lhs) + { + result = lhs - tmp; + return true; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it did not become smaller + if (tmp >= lhs) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is unsigned __int64, rhs signed + unsigned __int64 tmp; + + if (rhs < 0) + { + // So we're effectively subtracting + tmp = AbsValueHelper::method>::Abs(rhs); + + if (tmp <= lhs) + { + result = lhs - tmp; + return; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it did not become smaller + if (tmp >= lhs) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is unsigned and < 64-bit, rhs signed __int64 + if (rhs < 0) + { + if (lhs >= ~(unsigned __int64)(rhs) + 1) // negation is safe, since rhs is 64-bit + { + result = (T)(lhs + rhs); + return true; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff + // it is not possible for the operation above to overflow, so just check max + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is unsigned and < 64-bit, rhs signed __int64 + if (rhs < 0) + { + if (lhs >= ~(unsigned __int64)(rhs) + 1) // negation is safe, since rhs is 64-bit + { + result = (T)(lhs + rhs); + return; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff + // it is not possible for the operation above to overflow, so just check max + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is signed __int64, rhs signed + __int64 tmp = (__int64)((unsigned __int64)lhs + (unsigned __int64)rhs); + + if (lhs >= 0) + { + // mixed sign cannot overflow + if (rhs >= 0 && tmp < lhs) return false; + } + else + { + // lhs negative + if (rhs < 0 && tmp > lhs) return false; + } + + result = (T)tmp; + return true; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is signed __int64, rhs signed + __int64 tmp = (__int64)((unsigned __int64)lhs + (unsigned __int64)rhs); + + if (lhs >= 0) + { + // mixed sign cannot overflow + if (rhs >= 0 && tmp < lhs) E::SafeIntOnOverflow(); + } + else + { + // lhs negative + if (rhs < 0 && tmp > lhs) E::SafeIntOnOverflow(); + } + + result = (T)tmp; + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // rhs is signed __int64, lhs signed + __int64 tmp; + + if (AdditionHelper<__int64, __int64, AdditionState_CastInt64CheckOverflow>::Addition( + (__int64)lhs, (__int64)rhs, tmp) && + tmp <= IntTraits::maxInt && tmp >= IntTraits::minInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // rhs is signed __int64, lhs signed + __int64 tmp; + + AdditionHelper<__int64, __int64, AdditionState_CastInt64CheckOverflow>::AdditionThrow( + (__int64)lhs, (__int64)rhs, tmp); + + if (tmp <= IntTraits::maxInt && tmp >= IntTraits::minInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is signed __int64, rhs unsigned < 64-bit + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + if ((__int64)tmp >= lhs) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is signed __int64, rhs unsigned < 64-bit + // Some compilers get optimization-happy, let's thwart them + + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + if ((__int64)tmp >= lhs) + { + result = (T)(__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const __int64& lhs, const unsigned __int64& rhs, __int64& result) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isUint64); + // rhs is unsigned __int64, lhs __int64 + // cast everything to unsigned, perform addition, then + // cast back for check - this is done to stop optimizers from removing the code + unsigned __int64 tmp = (unsigned __int64)lhs + rhs; + + if ((__int64)tmp >= lhs) + { + result = (__int64)tmp; + return true; + } + + return false; + } + + template + static void AdditionThrow(const __int64& lhs, const unsigned __int64& rhs, T& result) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isUint64); + // rhs is unsigned __int64, lhs __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + rhs; + + if ((__int64)tmp >= lhs) + { + result = (__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class AdditionHelper +{ +public: + static bool Addition(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // rhs is unsigned __int64, lhs signed, 32-bit or less + if ((unsigned __int32)(rhs >> 32) == 0) + { + // Now it just happens to work out that the standard behavior does what we want + // Adding explicit casts to show exactly what's happening here + // Note - this is tweaked to keep optimizers from tossing out the code. + unsigned __int32 tmp = (unsigned __int32)rhs + (unsigned __int32)lhs; + + if ((__int32)tmp >= lhs && + SafeCastHelper::method>::Cast((__int32)tmp, result)) + return true; + } + + return false; + } + + template + static void AdditionThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // rhs is unsigned __int64, lhs signed, 32-bit or less + + if ((unsigned __int32)(rhs >> 32) == 0) + { + // Now it just happens to work out that the standard behavior does what we want + // Adding explicit casts to show exactly what's happening here + unsigned __int32 tmp = (unsigned __int32)rhs + (unsigned __int32)lhs; + + if ((__int32)tmp >= lhs) + { + SafeCastHelper::method>::template CastThrow((__int32)tmp, + result); + return; + } + } + E::SafeIntOnOverflow(); + } +}; + +enum SubtractionState +{ + SubtractionState_BothUnsigned, + SubtractionState_CastIntCheckSafeIntMinMax, + SubtractionState_CastIntCheckMin, + SubtractionState_CastInt64CheckSafeIntMinMax, + SubtractionState_CastInt64CheckMin, + SubtractionState_Uint64Int, + SubtractionState_UintInt64, + SubtractionState_Int64Int, + SubtractionState_IntInt64, + SubtractionState_Int64Uint, + SubtractionState_IntUint64, + SubtractionState_Int64Uint64, + // states for SubtractionMethod2 + SubtractionState_BothUnsigned2, + SubtractionState_CastIntCheckSafeIntMinMax2, + SubtractionState_CastInt64CheckSafeIntMinMax2, + SubtractionState_Uint64Int2, + SubtractionState_UintInt642, + SubtractionState_Int64Int2, + SubtractionState_IntInt642, + SubtractionState_Int64Uint2, + SubtractionState_IntUint642, + SubtractionState_Int64Uint642, + SubtractionState_Error +}; + +template +class SubtractionMethod +{ +public: + enum + { + // unsigned-unsigned + method = + ((IntRegion::IntZone_UintLT32_UintLT32 || (IntRegion::IntZone_Uint32_UintLT64) || + (IntRegion::IntZone_UintLT32_Uint32) || (IntRegion::IntZone_Uint64_Uint) || + (IntRegion::IntZone_UintLT64_Uint64)) + ? SubtractionState_BothUnsigned + : + // unsigned-signed + (IntRegion::IntZone_UintLT32_IntLT32) + ? SubtractionState_CastIntCheckSafeIntMinMax + : (IntRegion::IntZone_Uint32_IntLT64 || IntRegion::IntZone_UintLT32_Int32) + ? SubtractionState_CastInt64CheckSafeIntMinMax + : (IntRegion::IntZone_Uint64_Int || IntRegion::IntZone_Uint64_Int64) + ? SubtractionState_Uint64Int + : (IntRegion::IntZone_UintLT64_Int64) ? SubtractionState_UintInt64 : + // signed-signed + (IntRegion::IntZone_IntLT32_IntLT32) + ? SubtractionState_CastIntCheckSafeIntMinMax + : (IntRegion::IntZone_Int32_IntLT64 || + IntRegion::IntZone_IntLT32_Int32) + ? SubtractionState_CastInt64CheckSafeIntMinMax + : (IntRegion::IntZone_Int64_Int || + IntRegion::IntZone_Int64_Int64) + ? SubtractionState_Int64Int + : (IntRegion::IntZone_IntLT64_Int64) + ? SubtractionState_IntInt64 + : + // signed-unsigned + (IntRegion::IntZone_IntLT32_UintLT32) + ? SubtractionState_CastIntCheckMin + : (IntRegion::IntZone_Int32_UintLT32 || + IntRegion::IntZone_IntLT64_Uint32) + ? SubtractionState_CastInt64CheckMin + : (IntRegion::IntZone_Int64_UintLT64) + ? SubtractionState_Int64Uint + : (IntRegion::IntZone_Int_Uint64) + ? SubtractionState_IntUint64 + : (IntRegion:: + IntZone_Int64_Uint64) + ? SubtractionState_Int64Uint64 + : SubtractionState_Error) + }; +}; + +// this is for the case of U - SafeInt< T, E > +template +class SubtractionMethod2 +{ +public: + enum + { + // unsigned-unsigned + method = + ((IntRegion::IntZone_UintLT32_UintLT32 || (IntRegion::IntZone_Uint32_UintLT64) || + (IntRegion::IntZone_UintLT32_Uint32) || (IntRegion::IntZone_Uint64_Uint) || + (IntRegion::IntZone_UintLT64_Uint64)) + ? SubtractionState_BothUnsigned2 + : + // unsigned-signed + (IntRegion::IntZone_UintLT32_IntLT32) + ? SubtractionState_CastIntCheckSafeIntMinMax2 + : (IntRegion::IntZone_Uint32_IntLT64 || IntRegion::IntZone_UintLT32_Int32) + ? SubtractionState_CastInt64CheckSafeIntMinMax2 + : (IntRegion::IntZone_Uint64_Int || IntRegion::IntZone_Uint64_Int64) + ? SubtractionState_Uint64Int2 + : (IntRegion::IntZone_UintLT64_Int64) ? SubtractionState_UintInt642 : + // signed-signed + (IntRegion::IntZone_IntLT32_IntLT32) + ? SubtractionState_CastIntCheckSafeIntMinMax2 + : (IntRegion::IntZone_Int32_IntLT64 || + IntRegion::IntZone_IntLT32_Int32) + ? SubtractionState_CastInt64CheckSafeIntMinMax2 + : (IntRegion::IntZone_Int64_Int || + IntRegion::IntZone_Int64_Int64) + ? SubtractionState_Int64Int2 + : (IntRegion::IntZone_IntLT64_Int64) + ? SubtractionState_IntInt642 + : + // signed-unsigned + (IntRegion::IntZone_IntLT32_UintLT32) + ? SubtractionState_CastIntCheckSafeIntMinMax2 + : (IntRegion::IntZone_Int32_UintLT32 || + IntRegion::IntZone_IntLT64_Uint32) + ? SubtractionState_CastInt64CheckSafeIntMinMax2 + : (IntRegion::IntZone_Int64_UintLT64) + ? SubtractionState_Int64Uint2 + : (IntRegion::IntZone_Int_Uint64) + ? SubtractionState_IntUint642 + : (IntRegion:: + IntZone_Int64_Uint64) + ? SubtractionState_Int64Uint642 + : SubtractionState_Error) + }; +}; + +template +class SubtractionHelper; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // both are unsigned - easy case + if (rhs <= lhs) + { + result = (T)(lhs - rhs); + return true; + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // both are unsigned - easy case + if (rhs <= lhs) + { + result = (T)(lhs - rhs); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, U& result) SAFEINT_NOTHROW + { + // both are unsigned - easy case + // Except we do have to check for overflow - lhs could be larger than result can hold + if (rhs <= lhs) + { + T tmp = (T)(lhs - rhs); + return SafeCastHelper::method>::Cast(tmp, result); + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, U& result) SAFEINT_CPP_THROW + { + // both are unsigned - easy case + if (rhs <= lhs) + { + T tmp = (T)(lhs - rhs); + SafeCastHelper::method>::template CastThrow(tmp, result); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + if (SafeCastHelper::method>::Cast(tmp, result)) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + SafeCastHelper::method>::template CastThrow(tmp, result); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + return SafeCastHelper::method>::Cast(tmp, result); + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + SafeCastHelper::method>::template CastThrow(tmp, result); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // both values are 16-bit or less + // rhs is unsigned - check only minimum + __int32 tmp = lhs - rhs; + + if (tmp >= (__int32)IntTraits::minInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // both values are 16-bit or less + // rhs is unsigned - check only minimum + __int32 tmp = lhs - rhs; + + if (tmp >= (__int32)IntTraits::minInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + return SafeCastHelper::method>::Cast(tmp, result); + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + SafeCastHelper::method>::template CastThrow(tmp, result); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + return SafeCastHelper::method>::Cast(tmp, result); + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + SafeCastHelper::method>::template CastThrow(tmp, result); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // both values are 32-bit or less + // rhs is unsigned - check only minimum + __int64 tmp = (__int64)lhs - (__int64)rhs; + + if (tmp >= (__int64)IntTraits::minInt) + { + result = (T)tmp; + return true; + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // both values are 32-bit or less + // rhs is unsigned - check only minimum + __int64 tmp = (__int64)lhs - (__int64)rhs; + + if (tmp >= (__int64)IntTraits::minInt) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is an unsigned __int64, rhs signed + // must first see if rhs is positive or negative + if (rhs >= 0) + { + if ((unsigned __int64)rhs <= lhs) + { + result = (T)(lhs - (unsigned __int64)rhs); + return true; + } + } + else + { + T tmp = lhs; + // we're now effectively adding + result = lhs + AbsValueHelper::method>::Abs(rhs); + + if (result >= tmp) return true; + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is an unsigned __int64, rhs signed + // must first see if rhs is positive or negative + if (rhs >= 0) + { + if ((unsigned __int64)rhs <= lhs) + { + result = (T)(lhs - (unsigned __int64)rhs); + return; + } + } + else + { + T tmp = lhs; + // we're now effectively adding + result = lhs + AbsValueHelper::method>::Abs(rhs); + + if (result >= tmp) return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // U is unsigned __int64, T is signed + if (rhs < 0) + { + // treat this as addition + unsigned __int64 tmp; + + tmp = lhs + (unsigned __int64)AbsValueHelper::method>::Abs(rhs); + + // must check for addition overflow and max + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + else if ((unsigned __int64)rhs > lhs) // now both are positive, so comparison always works + { + // result is negative + // implies that lhs must fit into T, and result cannot overflow + // Also allows us to drop to 32-bit math, which is faster on a 32-bit system + result = (T)lhs - (T)rhs; + return true; + } + else + { + // result is positive + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // U is unsigned __int64, T is signed + if (rhs < 0) + { + // treat this as addition + unsigned __int64 tmp; + + tmp = lhs + (unsigned __int64)AbsValueHelper::method>::Abs(rhs); + + // must check for addition overflow and max + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + else if ((unsigned __int64)rhs > lhs) // now both are positive, so comparison always works + { + // result is negative + // implies that lhs must fit into T, and result cannot overflow + // Also allows us to drop to 32-bit math, which is faster on a 32-bit system + result = (T)lhs - (T)rhs; + return; + } + else + { + // result is positive + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is an unsigned int32 or smaller, rhs signed __int64 + // must first see if rhs is positive or negative + if (rhs >= 0) + { + if ((unsigned __int64)rhs <= lhs) + { + result = (T)(lhs - (T)rhs); + return true; + } + } + else + { + // we're now effectively adding + // since lhs is 32-bit, and rhs cannot exceed 2^63 + // this addition cannot overflow + unsigned __int64 tmp = lhs + ~(unsigned __int64)(rhs) + 1; // negation safe + + // but we could exceed MaxInt + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is an unsigned int32 or smaller, rhs signed __int64 + // must first see if rhs is positive or negative + if (rhs >= 0) + { + if ((unsigned __int64)rhs <= lhs) + { + result = (T)(lhs - (T)rhs); + return; + } + } + else + { + // we're now effectively adding + // since lhs is 32-bit, and rhs cannot exceed 2^63 + // this addition cannot overflow + unsigned __int64 tmp = lhs + ~(unsigned __int64)(rhs) + 1; // negation safe + + // but we could exceed MaxInt + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // U unsigned 32-bit or less, T __int64 + if (rhs >= 0) + { + // overflow not possible + result = (T)((__int64)lhs - rhs); + return true; + } + else + { + // we effectively have an addition + // which cannot overflow internally + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)(-rhs); + + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // U unsigned 32-bit or less, T __int64 + if (rhs >= 0) + { + // overflow not possible + result = (T)((__int64)lhs - rhs); + return; + } + else + { + // we effectively have an addition + // which cannot overflow internally + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)(-rhs); + + if (tmp <= (unsigned __int64)IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is an __int64, rhs signed (up to 64-bit) + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + // Note - ideally, we can order these so that true conditionals + // lead to success, which enables better pipelining + // It isn't practical here + if ((lhs >= 0 && rhs < 0 && tmp < lhs) || // condition 2 + (rhs >= 0 && tmp > lhs)) // condition 3 + { + return false; + } + + result = (T)tmp; + return true; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is an __int64, rhs signed (up to 64-bit) + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + // Note - ideally, we can order these so that true conditionals + // lead to success, which enables better pipelining + // It isn't practical here + if ((lhs >= 0 && rhs < 0 && tmp < lhs) || // condition 2 + (rhs >= 0 && tmp > lhs)) // condition 3 + { + E::SafeIntOnOverflow(); + } + + result = (T)tmp; + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // lhs __int64, rhs any signed int (including __int64) + __int64 tmp = lhs - rhs; + + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible in tmp + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible in tmp + + if (lhs >= 0) + { + // if both positive, overflow to negative not possible + // which is why we'll explicitly check maxInt, and not call SafeCast + if ((IntTraits::isLT64Bit && tmp > IntTraits::maxInt) || (rhs < 0 && tmp < lhs)) + { + return false; + } + } + else + { + // lhs negative + if ((IntTraits::isLT64Bit && tmp < IntTraits::minInt) || (rhs >= 0 && tmp > lhs)) + { + return false; + } + } + + result = (T)tmp; + return true; + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs __int64, rhs any signed int (including __int64) + __int64 tmp = lhs - rhs; + + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible in tmp + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible in tmp + + if (lhs >= 0) + { + // if both positive, overflow to negative not possible + // which is why we'll explicitly check maxInt, and not call SafeCast + if ((CompileConst::isLT64Bit>::Value() && tmp > IntTraits::maxInt) || + (rhs < 0 && tmp < lhs)) + { + E::SafeIntOnOverflow(); + } + } + else + { + // lhs negative + if ((CompileConst::isLT64Bit>::Value() && tmp < IntTraits::minInt) || + (rhs >= 0 && tmp > lhs)) + { + E::SafeIntOnOverflow(); + } + } + + result = (T)tmp; + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is a 32-bit int or less, rhs __int64 + // we have essentially 4 cases: + // + // lhs positive, rhs positive - rhs could be larger than lhs can represent + // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int + // lhs negative, rhs positive - check tmp <= lhs and tmp < min int + // lhs negative, rhs negative - addition cannot internally overflow, check against max + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + if (lhs >= 0) + { + // first case + if (rhs >= 0) + { + if (tmp >= IntTraits::minInt) + { + result = (T)tmp; + return true; + } + } + else + { + // second case + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + } + else + { + // lhs < 0 + // third case + if (rhs >= 0) + { + if (tmp <= lhs && tmp >= IntTraits::minInt) + { + result = (T)tmp; + return true; + } + } + else + { + // fourth case + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return true; + } + } + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is a 32-bit int or less, rhs __int64 + // we have essentially 4 cases: + // + // lhs positive, rhs positive - rhs could be larger than lhs can represent + // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int + // lhs negative, rhs positive - check tmp <= lhs and tmp < min int + // lhs negative, rhs negative - addition cannot internally overflow, check against max + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + if (lhs >= 0) + { + // first case + if (rhs >= 0) + { + if (tmp >= IntTraits::minInt) + { + result = (T)tmp; + return; + } + } + else + { + // second case + if (tmp >= lhs && tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + } + else + { + // lhs < 0 + // third case + if (rhs >= 0) + { + if (tmp <= lhs && tmp >= IntTraits::minInt) + { + result = (T)tmp; + return; + } + } + else + { + // fourth case + if (tmp <= IntTraits::maxInt) + { + result = (T)tmp; + return; + } + } + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is any signed int32 or smaller, rhs is int64 + __int64 tmp = (__int64)lhs - rhs; + + if ((lhs >= 0 && rhs < 0 && tmp < lhs) || (rhs > 0 && tmp > lhs)) + { + return false; + // else OK + } + + result = (T)tmp; + return true; + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is any signed int32 or smaller, rhs is int64 + __int64 tmp = (__int64)lhs - rhs; + + if ((lhs >= 0 && rhs < 0 && tmp < lhs) || (rhs > 0 && tmp > lhs)) + { + E::SafeIntOnOverflow(); + // else OK + } + + result = (T)tmp; + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is a 64-bit int, rhs unsigned int32 or smaller + // perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if ((__int64)tmp <= lhs) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is a 64-bit int, rhs unsigned int32 or smaller + // perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if ((__int64)tmp <= lhs) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + // lhs is __int64, rhs is unsigned 32-bit or smaller + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // Do this as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if ((__int64)tmp <= IntTraits::maxInt && (__int64)tmp >= IntTraits::minInt) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // Do this as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if ((__int64)tmp <= IntTraits::maxInt && (__int64)tmp >= IntTraits::minInt) + { + result = (T)(__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const T& lhs, const U& rhs, T& result) SAFEINT_NOTHROW + { + // lhs is any signed int, rhs unsigned int64 + // check against available range + + // We need the absolute value of IntTraits< T >::minInt + // This will give it to us without extraneous compiler warnings + const unsigned __int64 AbsMinIntT = (unsigned __int64)IntTraits::maxInt + 1; + + if (lhs < 0) + { + if (rhs <= AbsMinIntT - AbsValueHelper::method>::Abs(lhs)) + { + result = (T)(lhs - rhs); + return true; + } + } + else + { + if (rhs <= AbsMinIntT + (unsigned __int64)lhs) + { + result = (T)(lhs - rhs); + return true; + } + } + + return false; + } + + template + static void SubtractThrow(const T& lhs, const U& rhs, T& result) SAFEINT_CPP_THROW + { + // lhs is any signed int, rhs unsigned int64 + // check against available range + + // We need the absolute value of IntTraits< T >::minInt + // This will give it to us without extraneous compiler warnings + const unsigned __int64 AbsMinIntT = (unsigned __int64)IntTraits::maxInt + 1; + + if (lhs < 0) + { + if (rhs <= AbsMinIntT - AbsValueHelper::method>::Abs(lhs)) + { + result = (T)(lhs - rhs); + return; + } + } + else + { + if (rhs <= AbsMinIntT + (unsigned __int64)lhs) + { + result = (T)(lhs - rhs); + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const U& lhs, const T& rhs, T& result) SAFEINT_NOTHROW + { + // We run into upcasting problems on comparison - needs 2 checks + if (lhs >= 0 && (T)lhs >= rhs) + { + result = (T)((U)lhs - (U)rhs); + return true; + } + + return false; + } + + template + static void SubtractThrow(const U& lhs, const T& rhs, T& result) SAFEINT_CPP_THROW + { + // We run into upcasting problems on comparison - needs 2 checks + if (lhs >= 0 && (T)lhs >= rhs) + { + result = (T)((U)lhs - (U)rhs); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + static bool Subtract(const __int64& lhs, const unsigned __int64& rhs, __int64& result) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isUint64); + // if we subtract, and it gets larger, there's a problem + // Perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - rhs; + + if ((__int64)tmp <= lhs) + { + result = (__int64)tmp; + return true; + } + return false; + } + + template + static void SubtractThrow(const __int64& lhs, const unsigned __int64& rhs, T& result) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isInt64 && IntTraits::isUint64); + // if we subtract, and it gets larger, there's a problem + // Perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - rhs; + + if ((__int64)tmp <= lhs) + { + result = (__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class SubtractionHelper +{ +public: + // If lhs is negative, immediate problem - return must be positive, and subtracting only makes it + // get smaller. If rhs > lhs, then it would also go negative, which is the other case + static bool Subtract(const __int64& lhs, const unsigned __int64& rhs, T& result) SAFEINT_NOTHROW + { + C_ASSERT(IntTraits::isUint64 && IntTraits::isInt64); + if (lhs >= 0 && (unsigned __int64)lhs >= rhs) + { + result = (unsigned __int64)lhs - rhs; + return true; + } + + return false; + } + + template + static void SubtractThrow(const __int64& lhs, const unsigned __int64& rhs, T& result) SAFEINT_CPP_THROW + { + C_ASSERT(IntTraits::isUint64 && IntTraits::isInt64); + if (lhs >= 0 && (unsigned __int64)lhs >= rhs) + { + result = (unsigned __int64)lhs - rhs; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +enum BinaryState +{ + BinaryState_OK, + BinaryState_Int8, + BinaryState_Int16, + BinaryState_Int32 +}; + +template +class BinaryMethod +{ +public: + enum + { + // If both operands are unsigned OR + // return type is smaller than rhs OR + // return type is larger and rhs is unsigned + // Then binary operations won't produce unexpected results + method = (sizeof(T) <= sizeof(U) || SafeIntCompare::isBothUnsigned || !IntTraits::isSigned) + ? BinaryState_OK + : IntTraits::isInt8 ? BinaryState_Int8 + : IntTraits::isInt16 ? BinaryState_Int16 : BinaryState_Int32 + }; +}; + +#ifdef SAFEINT_DISABLE_BINARY_ASSERT +#define BinaryAssert(x) +#else +#define BinaryAssert(x) SAFEINT_ASSERT(x) +#endif + +template +class BinaryAndHelper; + +template +class BinaryAndHelper +{ +public: + static T And(T lhs, U rhs) SAFEINT_NOTHROW { return (T)(lhs & rhs); } +}; + +template +class BinaryAndHelper +{ +public: + static T And(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs & rhs) == (lhs & (unsigned __int8)rhs)); + return (T)(lhs & (unsigned __int8)rhs); + } +}; + +template +class BinaryAndHelper +{ +public: + static T And(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs & rhs) == (lhs & (unsigned __int16)rhs)); + return (T)(lhs & (unsigned __int16)rhs); + } +}; + +template +class BinaryAndHelper +{ +public: + static T And(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs & rhs) == (lhs & (unsigned __int32)rhs)); + return (T)(lhs & (unsigned __int32)rhs); + } +}; + +template +class BinaryOrHelper; + +template +class BinaryOrHelper +{ +public: + static T Or(T lhs, U rhs) SAFEINT_NOTHROW { return (T)(lhs | rhs); } +}; + +template +class BinaryOrHelper +{ +public: + static T Or(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs | rhs) == (lhs | (unsigned __int8)rhs)); + return (T)(lhs | (unsigned __int8)rhs); + } +}; + +template +class BinaryOrHelper +{ +public: + static T Or(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs | rhs) == (lhs | (unsigned __int16)rhs)); + return (T)(lhs | (unsigned __int16)rhs); + } +}; + +template +class BinaryOrHelper +{ +public: + static T Or(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs | rhs) == (lhs | (unsigned __int32)rhs)); + return (T)(lhs | (unsigned __int32)rhs); + } +}; + +template +class BinaryXorHelper; + +template +class BinaryXorHelper +{ +public: + static T Xor(T lhs, U rhs) SAFEINT_NOTHROW { return (T)(lhs ^ rhs); } +}; + +template +class BinaryXorHelper +{ +public: + static T Xor(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs ^ rhs) == (lhs ^ (unsigned __int8)rhs)); + return (T)(lhs ^ (unsigned __int8)rhs); + } +}; + +template +class BinaryXorHelper +{ +public: + static T Xor(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs ^ rhs) == (lhs ^ (unsigned __int16)rhs)); + return (T)(lhs ^ (unsigned __int16)rhs); + } +}; + +template +class BinaryXorHelper +{ +public: + static T Xor(T lhs, U rhs) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert((lhs ^ rhs) == (lhs ^ (unsigned __int32)rhs)); + return (T)(lhs ^ (unsigned __int32)rhs); + } +}; + +/***************** External functions ****************************************/ + +// External functions that can be used where you only need to check one operation +// non-class helper function so that you can check for a cast's validity +// and handle errors how you like +template +inline bool SafeCast(const T From, U& To) SAFEINT_NOTHROW +{ + return SafeCastHelper::method>::Cast(From, To); +} + +template +inline bool SafeEquals(const T t, const U u) SAFEINT_NOTHROW +{ + return EqualityTest::method>::IsEquals(t, u); +} + +template +inline bool SafeNotEquals(const T t, const U u) SAFEINT_NOTHROW +{ + return !EqualityTest::method>::IsEquals(t, u); +} + +template +inline bool SafeGreaterThan(const T t, const U u) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan(t, u); +} + +template +inline bool SafeGreaterThanEquals(const T t, const U u) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan(u, t); +} + +template +inline bool SafeLessThan(const T t, const U u) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan(u, t); +} + +template +inline bool SafeLessThanEquals(const T t, const U u) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan(t, u); +} + +template +inline bool SafeModulus(const T& t, const U& u, T& result) SAFEINT_NOTHROW +{ + return (ModulusHelper::method>::Modulus(t, u, result) == SafeIntNoError); +} + +template +inline bool SafeMultiply(T t, U u, T& result) SAFEINT_NOTHROW +{ + return MultiplicationHelper::method>::Multiply(t, u, result); +} + +template +inline bool SafeDivide(T t, U u, T& result) SAFEINT_NOTHROW +{ + return (DivisionHelper::method>::Divide(t, u, result) == SafeIntNoError); +} + +template +inline bool SafeAdd(T t, U u, T& result) SAFEINT_NOTHROW +{ + return AdditionHelper::method>::Addition(t, u, result); +} + +template +inline bool SafeSubtract(T t, U u, T& result) SAFEINT_NOTHROW +{ + return SubtractionHelper::method>::Subtract(t, u, result); +} + +/***************** end external functions ************************************/ + +// Main SafeInt class +// Assumes exceptions can be thrown +template +class SafeInt +{ +public: + SafeInt() SAFEINT_NOTHROW + { + C_ASSERT(NumericType::isInt); + m_int = 0; + } + + // Having a constructor for every type of int + // avoids having the compiler evade our checks when doing implicit casts - + // e.g., SafeInt s = 0x7fffffff; + SafeInt(const T& i) SAFEINT_NOTHROW + { + C_ASSERT(NumericType::isInt); + // always safe + m_int = i; + } + + // provide explicit boolean converter + SafeInt(bool b) SAFEINT_NOTHROW + { + C_ASSERT(NumericType::isInt); + m_int = (T)(b ? 1 : 0); + } + + template + SafeInt(const SafeInt& u) SAFEINT_CPP_THROW + { + C_ASSERT(NumericType::isInt); + *this = SafeInt((U)u); + } + + template + SafeInt(const U& i) SAFEINT_CPP_THROW + { + C_ASSERT(NumericType::isInt); + // SafeCast will throw exceptions if i won't fit in type T + SafeCastHelper::method>::template CastThrow(i, m_int); + } + + // The destructor is intentionally commented out - no destructor + // vs. a do-nothing destructor makes a huge difference in + // inlining characteristics. It wasn't doing anything anyway. + // ~SafeInt(){}; + + // now start overloading operators + // assignment operator + // constructors exist for all int types and will ensure safety + + template + SafeInt& operator=(const U& rhs) SAFEINT_CPP_THROW + { + // use constructor to test size + // constructor is optimized to do minimal checking based + // on whether T can contain U + // note - do not change this + *this = SafeInt(rhs); + return *this; + } + + SafeInt& operator=(const T& rhs) SAFEINT_NOTHROW + { + m_int = rhs; + return *this; + } + + template + SafeInt& operator=(const SafeInt& rhs) SAFEINT_CPP_THROW + { + SafeCastHelper::method>::template CastThrow(rhs.Ref(), m_int); + return *this; + } + + SafeInt& operator=(const SafeInt& rhs) SAFEINT_NOTHROW + { + m_int = rhs.m_int; + return *this; + } + + // Casting operators + + operator bool() const SAFEINT_NOTHROW { return !!m_int; } + + operator char() const SAFEINT_CPP_THROW + { + char val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + operator signed char() const SAFEINT_CPP_THROW + { + signed char val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + operator unsigned char() const SAFEINT_CPP_THROW + { + unsigned char val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + operator __int16() const SAFEINT_CPP_THROW + { + __int16 val; + SafeCastHelper<__int16, T, GetCastMethod<__int16, T>::method>::template CastThrow(m_int, val); + return val; + } + + operator unsigned __int16() const SAFEINT_CPP_THROW + { + unsigned __int16 val; + SafeCastHelper::method>::template CastThrow(m_int, + val); + return val; + } + + operator __int32() const SAFEINT_CPP_THROW + { + __int32 val; + SafeCastHelper<__int32, T, GetCastMethod<__int32, T>::method>::template CastThrow(m_int, val); + return val; + } + + operator unsigned __int32() const SAFEINT_CPP_THROW + { + unsigned __int32 val; + SafeCastHelper::method>::template CastThrow(m_int, + val); + return val; + } + + // The compiler knows that int == __int32 + // but not that long == __int32 + operator long() const SAFEINT_CPP_THROW + { + long val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + operator unsigned long() const SAFEINT_CPP_THROW + { + unsigned long val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + operator __int64() const SAFEINT_CPP_THROW + { + __int64 val; + SafeCastHelper<__int64, T, GetCastMethod<__int64, T>::method>::template CastThrow(m_int, val); + return val; + } + + operator unsigned __int64() const SAFEINT_CPP_THROW + { + unsigned __int64 val; + SafeCastHelper::method>::template CastThrow(m_int, + val); + return val; + } + +#if defined SAFEINT_USE_WCHAR_T || defined _NATIVE_WCHAR_T_DEFINED + operator wchar_t() const SAFEINT_CPP_THROW + { + wchar_t val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } +#endif + +#ifdef SIZE_T_CAST_NEEDED + // We also need an explicit cast to size_t, or the compiler will complain + // Apparently, only SOME compilers complain, and cl 14.00.50727.42 isn't one of them + // Leave here in case we decide to backport this to an earlier compiler + operator size_t() const SAFEINT_CPP_THROW + { + size_t val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } +#endif + + // Also provide a cast operator for floating point types + operator float() const SAFEINT_CPP_THROW + { + float val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + operator double() const SAFEINT_CPP_THROW + { + double val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + operator long double() const SAFEINT_CPP_THROW + { + long double val; + SafeCastHelper::method>::template CastThrow(m_int, val); + return val; + } + + // If you need a pointer to the data + // this could be dangerous, but allows you to correctly pass + // instances of this class to APIs that take a pointer to an integer + // also see overloaded address-of operator below + T* Ptr() SAFEINT_NOTHROW { return &m_int; } + const T* Ptr() const SAFEINT_NOTHROW { return &m_int; } + const T& Ref() const SAFEINT_NOTHROW { return m_int; } + + // Or if SafeInt< T, E >::Ptr() is inconvenient, use the overload + // operator & + // This allows you to do unsafe things! + // It is meant to allow you to more easily + // pass a SafeInt into things like ReadFile + T* operator&() SAFEINT_NOTHROW { return &m_int; } + const T* operator&() const SAFEINT_NOTHROW { return &m_int; } + + // Unary operators + bool operator!() const SAFEINT_NOTHROW { return (!m_int) ? true : false; } + + // operator + (unary) + // note - normally, the '+' and '-' operators will upcast to a signed int + // for T < 32 bits. This class changes behavior to preserve type + const SafeInt& operator+() const SAFEINT_NOTHROW { return *this; } + + // unary - + + SafeInt operator-() const SAFEINT_CPP_THROW + { + // Note - unsigned still performs the bitwise manipulation + // will warn at level 2 or higher if the value is 32-bit or larger + return SafeInt(NegationHelper::isSigned>::template NegativeThrow(m_int)); + } + + // prefix increment operator + SafeInt& operator++() SAFEINT_CPP_THROW + { + if (m_int != IntTraits::maxInt) + { + ++m_int; + return *this; + } + E::SafeIntOnOverflow(); + } + + // prefix decrement operator + SafeInt& operator--() SAFEINT_CPP_THROW + { + if (m_int != IntTraits::minInt) + { + --m_int; + return *this; + } + E::SafeIntOnOverflow(); + } + + // note that postfix operators have inherently worse perf + // characteristics + + // postfix increment operator + SafeInt operator++(int) SAFEINT_CPP_THROW // dummy arg to comply with spec + { + if (m_int != IntTraits::maxInt) + { + SafeInt tmp(m_int); + + m_int++; + return tmp; + } + E::SafeIntOnOverflow(); + } + + // postfix decrement operator + SafeInt operator--(int) SAFEINT_CPP_THROW // dummy arg to comply with spec + { + if (m_int != IntTraits::minInt) + { + SafeInt tmp(m_int); + m_int--; + return tmp; + } + E::SafeIntOnOverflow(); + } + + // One's complement + // Note - this operator will normally change size to an int + // cast in return improves perf and maintains type + SafeInt operator~() const SAFEINT_NOTHROW { return SafeInt((T)~m_int); } + + // Binary operators + // + // arithmetic binary operators + // % modulus + // * multiplication + // / division + // + addition + // - subtraction + // + // For each of the arithmetic operators, you will need to + // use them as follows: + // + // SafeInt c = 2; + // SafeInt i = 3; + // + // SafeInt i2 = i op (char)c; + // OR + // SafeInt i2 = (int)i op c; + // + // The base problem is that if the lhs and rhs inputs are different SafeInt types + // it is not possible in this implementation to determine what type of SafeInt + // should be returned. You have to let the class know which of the two inputs + // need to be the return type by forcing the other value to the base integer type. + // + // Note - as per feedback from Scott Meyers, I'm exploring how to get around this. + // 3.0 update - I'm still thinking about this. It can be done with template metaprogramming, + // but it is tricky, and there's a perf vs. correctness tradeoff where the right answer + // is situational. + // + // The case of: + // + // SafeInt< T, E > i, j, k; + // i = j op k; + // + // works just fine and no unboxing is needed because the return type is not ambiguous. + + // Modulus + // Modulus has some convenient properties - + // first, the magnitude of the return can never be + // larger than the lhs operand, and it must be the same sign + // as well. It does, however, suffer from the same promotion + // problems as comparisons, division and other operations + template + SafeInt operator%(U rhs) const SAFEINT_CPP_THROW + { + T result; + ModulusHelper::method>::template ModulusThrow(m_int, rhs, result); + return SafeInt(result); + } + + SafeInt operator%(SafeInt rhs) const SAFEINT_CPP_THROW + { + T result; + ModulusHelper::method>::template ModulusThrow(m_int, rhs, result); + return SafeInt(result); + } + + // Modulus assignment + template + SafeInt& operator%=(U rhs) SAFEINT_CPP_THROW + { + ModulusHelper::method>::template ModulusThrow(m_int, rhs, m_int); + return *this; + } + + template + SafeInt& operator%=(SafeInt rhs) SAFEINT_CPP_THROW + { + ModulusHelper::method>::template ModulusThrow(m_int, (U)rhs, m_int); + return *this; + } + + // Multiplication + template + SafeInt operator*(U rhs) const SAFEINT_CPP_THROW + { + T ret(0); + MultiplicationHelper::method>::template MultiplyThrow(m_int, rhs, ret); + return SafeInt(ret); + } + + SafeInt operator*(SafeInt rhs) const SAFEINT_CPP_THROW + { + T ret(0); + MultiplicationHelper::method>::template MultiplyThrow(m_int, (T)rhs, ret); + return SafeInt(ret); + } + + // Multiplication assignment + SafeInt& operator*=(SafeInt rhs) SAFEINT_CPP_THROW + { + MultiplicationHelper::method>::template MultiplyThrow(m_int, (T)rhs, m_int); + return *this; + } + + template + SafeInt& operator*=(U rhs) SAFEINT_CPP_THROW + { + MultiplicationHelper::method>::template MultiplyThrow(m_int, rhs, m_int); + return *this; + } + + template + SafeInt& operator*=(SafeInt rhs) SAFEINT_CPP_THROW + { + MultiplicationHelper::method>::template MultiplyThrow( + m_int, rhs.Ref(), m_int); + return *this; + } + + // Division + template + SafeInt operator/(U rhs) const SAFEINT_CPP_THROW + { + T ret(0); + DivisionHelper::method>::template DivideThrow(m_int, rhs, ret); + return SafeInt(ret); + } + + SafeInt operator/(SafeInt rhs) const SAFEINT_CPP_THROW + { + T ret(0); + DivisionHelper::method>::template DivideThrow(m_int, (T)rhs, ret); + return SafeInt(ret); + } + + // Division assignment + SafeInt& operator/=(SafeInt i) SAFEINT_CPP_THROW + { + DivisionHelper::method>::template DivideThrow(m_int, (T)i, m_int); + return *this; + } + + template + SafeInt& operator/=(U i) SAFEINT_CPP_THROW + { + DivisionHelper::method>::template DivideThrow(m_int, i, m_int); + return *this; + } + + template + SafeInt& operator/=(SafeInt i) + { + DivisionHelper::method>::template DivideThrow(m_int, (U)i, m_int); + return *this; + } + + // For addition and subtraction + + // Addition + SafeInt operator+(SafeInt rhs) const SAFEINT_CPP_THROW + { + T ret(0); + AdditionHelper::method>::template AdditionThrow(m_int, (T)rhs, ret); + return SafeInt(ret); + } + + template + SafeInt operator+(U rhs) const SAFEINT_CPP_THROW + { + T ret(0); + AdditionHelper::method>::template AdditionThrow(m_int, rhs, ret); + return SafeInt(ret); + } + + // addition assignment + SafeInt& operator+=(SafeInt rhs) SAFEINT_CPP_THROW + { + AdditionHelper::method>::template AdditionThrow(m_int, (T)rhs, m_int); + return *this; + } + + template + SafeInt& operator+=(U rhs) SAFEINT_CPP_THROW + { + AdditionHelper::method>::template AdditionThrow(m_int, rhs, m_int); + return *this; + } + + template + SafeInt& operator+=(SafeInt rhs) SAFEINT_CPP_THROW + { + AdditionHelper::method>::template AdditionThrow(m_int, (U)rhs, m_int); + return *this; + } + + // Subtraction + template + SafeInt operator-(U rhs) const SAFEINT_CPP_THROW + { + T ret(0); + SubtractionHelper::method>::template SubtractThrow(m_int, rhs, ret); + return SafeInt(ret); + } + + SafeInt operator-(SafeInt rhs) const SAFEINT_CPP_THROW + { + T ret(0); + SubtractionHelper::method>::template SubtractThrow(m_int, (T)rhs, ret); + return SafeInt(ret); + } + + // Subtraction assignment + SafeInt& operator-=(SafeInt rhs) SAFEINT_CPP_THROW + { + SubtractionHelper::method>::template SubtractThrow(m_int, (T)rhs, m_int); + return *this; + } + + template + SafeInt& operator-=(U rhs) SAFEINT_CPP_THROW + { + SubtractionHelper::method>::template SubtractThrow(m_int, rhs, m_int); + return *this; + } + + template + SafeInt& operator-=(SafeInt rhs) SAFEINT_CPP_THROW + { + SubtractionHelper::method>::template SubtractThrow(m_int, (U)rhs, m_int); + return *this; + } + + // Shift operators + // Note - shift operators ALWAYS return the same type as the lhs + // specific version for SafeInt< T, E > not needed - + // code path is exactly the same as for SafeInt< U, E > as rhs + + // Left shift + // Also, shifting > bitcount is undefined - trap in debug +#ifdef SAFEINT_DISABLE_SHIFT_ASSERT +#define ShiftAssert(x) +#else +#define ShiftAssert(x) SAFEINT_ASSERT(x) +#endif + + template + SafeInt operator<<(U bits) const SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || bits >= 0); + ShiftAssert(bits < (int)IntTraits::bitCount); + + return SafeInt((T)(m_int << bits)); + } + + template + SafeInt operator<<(SafeInt bits) const SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || (U)bits >= 0); + ShiftAssert((U)bits < (int)IntTraits::bitCount); + + return SafeInt((T)(m_int << (U)bits)); + } + + // Left shift assignment + + template + SafeInt& operator<<=(U bits) SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || bits >= 0); + ShiftAssert(bits < (int)IntTraits::bitCount); + + m_int <<= bits; + return *this; + } + + template + SafeInt& operator<<=(SafeInt bits) SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || (U)bits >= 0); + ShiftAssert((U)bits < (int)IntTraits::bitCount); + + m_int <<= (U)bits; + return *this; + } + + // Right shift + template + SafeInt operator>>(U bits) const SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || bits >= 0); + ShiftAssert(bits < (int)IntTraits::bitCount); + + return SafeInt((T)(m_int >> bits)); + } + + template + SafeInt operator>>(SafeInt bits) const SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || (U)bits >= 0); + ShiftAssert(bits < (int)IntTraits::bitCount); + + return SafeInt((T)(m_int >> (U)bits)); + } + + // Right shift assignment + template + SafeInt& operator>>=(U bits) SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || bits >= 0); + ShiftAssert(bits < (int)IntTraits::bitCount); + + m_int >>= bits; + return *this; + } + + template + SafeInt& operator>>=(SafeInt bits) SAFEINT_NOTHROW + { + ShiftAssert(!IntTraits::isSigned || (U)bits >= 0); + ShiftAssert((U)bits < (int)IntTraits::bitCount); + + m_int >>= (U)bits; + return *this; + } + + // Bitwise operators + // This only makes sense if we're dealing with the same type and size + // demand a type T, or something that fits into a type T + + // Bitwise & + SafeInt operator&(SafeInt rhs) const SAFEINT_NOTHROW { return SafeInt(m_int & (T)rhs); } + + template + SafeInt operator&(U rhs) const SAFEINT_NOTHROW + { + // we want to avoid setting bits by surprise + // consider the case of lhs = int, value = 0xffffffff + // rhs = char, value = 0xff + // + // programmer intent is to get only the lower 8 bits + // normal behavior is to upcast both sides to an int + // which then sign extends rhs, setting all the bits + + // If you land in the assert, this is because the bitwise operator + // was causing unexpected behavior. Fix is to properly cast your inputs + // so that it works like you meant, not unexpectedly + + return SafeInt(BinaryAndHelper::method>::And(m_int, rhs)); + } + + // Bitwise & assignment + SafeInt& operator&=(SafeInt rhs) SAFEINT_NOTHROW + { + m_int &= (T)rhs; + return *this; + } + + template + SafeInt& operator&=(U rhs) SAFEINT_NOTHROW + { + m_int = BinaryAndHelper::method>::And(m_int, rhs); + return *this; + } + + template + SafeInt& operator&=(SafeInt rhs) SAFEINT_NOTHROW + { + m_int = BinaryAndHelper::method>::And(m_int, (U)rhs); + return *this; + } + + // XOR + SafeInt operator^(SafeInt rhs) const SAFEINT_NOTHROW { return SafeInt((T)(m_int ^ (T)rhs)); } + + template + SafeInt operator^(U rhs) const SAFEINT_NOTHROW + { + // If you land in the assert, this is because the bitwise operator + // was causing unexpected behavior. Fix is to properly cast your inputs + // so that it works like you meant, not unexpectedly + + return SafeInt(BinaryXorHelper::method>::Xor(m_int, rhs)); + } + + // XOR assignment + SafeInt& operator^=(SafeInt rhs) SAFEINT_NOTHROW + { + m_int ^= (T)rhs; + return *this; + } + + template + SafeInt& operator^=(U rhs) SAFEINT_NOTHROW + { + m_int = BinaryXorHelper::method>::Xor(m_int, rhs); + return *this; + } + + template + SafeInt& operator^=(SafeInt rhs) SAFEINT_NOTHROW + { + m_int = BinaryXorHelper::method>::Xor(m_int, (U)rhs); + return *this; + } + + // bitwise OR + SafeInt operator|(SafeInt rhs) const SAFEINT_NOTHROW { return SafeInt((T)(m_int | (T)rhs)); } + + template + SafeInt operator|(U rhs) const SAFEINT_NOTHROW + { + return SafeInt(BinaryOrHelper::method>::Or(m_int, rhs)); + } + + // bitwise OR assignment + SafeInt& operator|=(SafeInt rhs) SAFEINT_NOTHROW + { + m_int |= (T)rhs; + return *this; + } + + template + SafeInt& operator|=(U rhs) SAFEINT_NOTHROW + { + m_int = BinaryOrHelper::method>::Or(m_int, rhs); + return *this; + } + + template + SafeInt& operator|=(SafeInt rhs) SAFEINT_NOTHROW + { + m_int = BinaryOrHelper::method>::Or(m_int, (U)rhs); + return *this; + } + + // Miscellaneous helper functions + SafeInt Min(SafeInt test, const T floor = IntTraits::minInt) const SAFEINT_NOTHROW + { + T tmp = test < m_int ? (T)test : m_int; + return tmp < floor ? floor : tmp; + } + + SafeInt Max(SafeInt test, const T upper = IntTraits::maxInt) const SAFEINT_NOTHROW + { + T tmp = test > m_int ? (T)test : m_int; + return tmp > upper ? upper : tmp; + } + + void Swap(SafeInt& with) SAFEINT_NOTHROW + { + T temp(m_int); + m_int = with.m_int; + with.m_int = temp; + } + + static SafeInt SafeAtoI(const char* input) SAFEINT_CPP_THROW { return SafeTtoI(input); } + + static SafeInt SafeWtoI(const wchar_t* input) { return SafeTtoI(input); } + + enum alignBits + { + align2 = 1, + align4 = 2, + align8 = 3, + align16 = 4, + align32 = 5, + align64 = 6, + align128 = 7, + align256 = 8 + }; + + template + const SafeInt& Align() SAFEINT_CPP_THROW + { + // Zero is always aligned + if (m_int == 0) return *this; + + // We don't support aligning negative numbers at this time + // Can't align unsigned numbers on bitCount (e.g., 8 bits = 256, unsigned char max = 255) + // or signed numbers on bitCount-1 (e.g., 7 bits = 128, signed char max = 127). + // Also makes no sense to try to align on negative or no bits. + + ShiftAssert(((IntTraits::isSigned && bits < (int)IntTraits::bitCount - 1) || + (!IntTraits::isSigned && bits < (int)IntTraits::bitCount)) && + bits >= 0 && (!IntTraits::isSigned || m_int > 0)); + + const T AlignValue = ((T)1 << bits) - 1; + + m_int = (T)((m_int + AlignValue) & ~AlignValue); + + if (m_int <= 0) E::SafeIntOnOverflow(); + + return *this; + } + + // Commonly needed alignments: + const SafeInt& Align2() { return Align(); } + const SafeInt& Align4() { return Align(); } + const SafeInt& Align8() { return Align(); } + const SafeInt& Align16() { return Align(); } + const SafeInt& Align32() { return Align(); } + const SafeInt& Align64() { return Align(); } + +private: + // This is almost certainly not the best optimized version of atoi, + // but it does not display a typical bug where it isn't possible to set MinInt + // and it won't allow you to overflow your integer. + // This is here because it is useful, and it is an example of what + // can be done easily with SafeInt. + template + static SafeInt SafeTtoI(U* input) SAFEINT_CPP_THROW + { + U* tmp = input; + SafeInt s; + bool negative = false; + + // Bad input, or empty string + if (input == nullptr || input[0] == 0) E::SafeIntOnOverflow(); + + switch (*tmp) + { + case '-': + tmp++; + negative = true; + break; + case '+': tmp++; break; + } + + while (*tmp != 0) + { + if (*tmp < '0' || *tmp > '9') break; + + if ((T)s != 0) s *= (T)10; + + if (!negative) + s += (T)(*tmp - '0'); + else + s -= (T)(*tmp - '0'); + + tmp++; + } + + return s; + } + + T m_int; +}; + +// Helper function used to subtract pointers. +// Used to squelch warnings +template +SafeInt SafePtrDiff(const P* p1, const P* p2) SAFEINT_CPP_THROW +{ + return SafeInt(p1 - p2); +} + +// Comparison operators + +// Less than +template +bool operator<(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan((T)rhs, lhs); +} + +template +bool operator<(SafeInt lhs, U rhs) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan(rhs, (T)lhs); +} + +template +bool operator<(SafeInt lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan((T)rhs, (U)lhs); +} + +// Greater than +template +bool operator>(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan(lhs, (T)rhs); +} + +template +bool operator>(SafeInt lhs, U rhs) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan((T)lhs, rhs); +} + +template +bool operator>(SafeInt lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return GreaterThanTest::method>::GreaterThan((T)lhs, (U)rhs); +} + +// Greater than or equal +template +bool operator>=(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan((T)rhs, lhs); +} + +template +bool operator>=(SafeInt lhs, U rhs) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan(rhs, (T)lhs); +} + +template +bool operator>=(SafeInt lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan((U)rhs, (T)lhs); +} + +// Less than or equal +template +bool operator<=(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan(lhs, (T)rhs); +} + +template +bool operator<=(SafeInt lhs, U rhs) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan((T)lhs, rhs); +} + +template +bool operator<=(SafeInt lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return !GreaterThanTest::method>::GreaterThan((T)lhs, (U)rhs); +} + +// equality +// explicit overload for bool +template +bool operator==(bool lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return lhs == ((T)rhs == 0 ? false : true); +} + +template +bool operator==(SafeInt lhs, bool rhs) SAFEINT_NOTHROW +{ + return rhs == ((T)lhs == 0 ? false : true); +} + +template +bool operator==(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return EqualityTest::method>::IsEquals((T)rhs, lhs); +} + +template +bool operator==(SafeInt lhs, U rhs) SAFEINT_NOTHROW +{ + return EqualityTest::method>::IsEquals((T)lhs, rhs); +} + +template +bool operator==(SafeInt lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return EqualityTest::method>::IsEquals((T)lhs, (U)rhs); +} + +// not equals +template +bool operator!=(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return !EqualityTest::method>::IsEquals((T)rhs, lhs); +} + +template +bool operator!=(SafeInt lhs, U rhs) SAFEINT_NOTHROW +{ + return !EqualityTest::method>::IsEquals((T)lhs, rhs); +} + +template +bool operator!=(SafeInt lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return !EqualityTest::method>::IsEquals(lhs, rhs); +} + +template +bool operator!=(bool lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return ((T)rhs == 0 ? false : true) != lhs; +} + +template +bool operator!=(SafeInt lhs, bool rhs) SAFEINT_NOTHROW +{ + return ((T)lhs == 0 ? false : true) != rhs; +} + +template +class ModulusSimpleCaseHelper; + +template +class ModulusSignedCaseHelper; + +template +class ModulusSignedCaseHelper +{ +public: + static bool SignedCase(SafeInt rhs, SafeInt& result) SAFEINT_NOTHROW + { + if ((T)rhs == (T)-1) + { + result = 0; + return true; + } + return false; + } +}; + +template +class ModulusSignedCaseHelper +{ +public: + static bool SignedCase(SafeInt /*rhs*/, SafeInt& /*result*/) SAFEINT_NOTHROW { return false; } +}; + +template +class ModulusSimpleCaseHelper +{ +public: + static bool ModulusSimpleCase(U lhs, SafeInt rhs, SafeInt& result) SAFEINT_CPP_THROW + { + if (rhs != 0) + { + if (ModulusSignedCaseHelper::isSigned>::SignedCase(rhs, result)) return true; + + result = SafeInt((T)(lhs % (T)rhs)); + return true; + } + + E::SafeIntOnDivZero(); + } +}; + +template +class ModulusSimpleCaseHelper +{ +public: + static bool ModulusSimpleCase(U /*lhs*/, SafeInt /*rhs*/, SafeInt& /*result*/) SAFEINT_NOTHROW + { + return false; + } +}; + +// Modulus +template +SafeInt operator%(U lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + // Value of return depends on sign of lhs + // This one may not be safe - bounds check in constructor + // if lhs is negative and rhs is unsigned, this will throw an exception. + + // Fast-track the simple case + // same size and same sign + SafeInt result; + + if (ModulusSimpleCaseHelper < T, + U, + E, + sizeof(T) == sizeof(U) && + (bool)IntTraits::isSigned == (bool)IntTraits::isSigned > ::ModulusSimpleCase(lhs, rhs, result)) + return result; + + return SafeInt((SafeInt(lhs) % (T)rhs)); +} + +// Multiplication +template +SafeInt operator*(U lhs, SafeInt rhs)SAFEINT_CPP_THROW +{ + T ret(0); + MultiplicationHelper::method>::template MultiplyThrow((T)rhs, lhs, ret); + return SafeInt(ret); +} + +template +class DivisionNegativeCornerCaseHelper; + +template +class DivisionNegativeCornerCaseHelper +{ +public: + static bool NegativeCornerCase(U lhs, SafeInt rhs, SafeInt& result) SAFEINT_CPP_THROW + { + // Problem case - normal casting behavior changes meaning + // flip rhs to positive + // any operator casts now do the right thing + U tmp; + + if (CompileConst::Value()) + tmp = lhs / (U)(~(unsigned __int32)(T)rhs + 1); + else + tmp = lhs / (U)(~(unsigned __int64)(T)rhs + 1); + + if (tmp <= (U)IntTraits::maxInt) + { + result = SafeInt((T)(~(unsigned __int64)tmp + 1)); + return true; + } + + // Corner case + T maxT = IntTraits::maxInt; + if (tmp == (U)maxT + 1) + { + T minT = IntTraits::minInt; + result = SafeInt(minT); + return true; + } + + E::SafeIntOnOverflow(); + } +}; + +template +class DivisionNegativeCornerCaseHelper +{ +public: + static bool NegativeCornerCase(U /*lhs*/, SafeInt /*rhs*/, SafeInt& /*result*/) SAFEINT_NOTHROW + { + return false; + } +}; + +template +class DivisionCornerCaseHelper; + +template +class DivisionCornerCaseHelper +{ +public: + static bool DivisionCornerCase1(U lhs, SafeInt rhs, SafeInt& result) SAFEINT_CPP_THROW + { + if ((T)rhs > 0) + { + result = SafeInt(lhs / (T)rhs); + return true; + } + + // Now rhs is either negative, or zero + if ((T)rhs != 0) + { + if (DivisionNegativeCornerCaseHelper < T, + U, + E, + sizeof(U) >= 4 && sizeof(T) <= sizeof(U) > ::NegativeCornerCase(lhs, rhs, result)) + return true; + + result = SafeInt(lhs / (T)rhs); + return true; + } + + E::SafeIntOnDivZero(); + } +}; + +template +class DivisionCornerCaseHelper +{ +public: + static bool DivisionCornerCase1(U /*lhs*/, SafeInt /*rhs*/, SafeInt& /*result*/) SAFEINT_NOTHROW + { + return false; + } +}; + +template +class DivisionCornerCaseHelper2; + +template +class DivisionCornerCaseHelper2 +{ +public: + static bool DivisionCornerCase2(U lhs, SafeInt rhs, SafeInt& result) SAFEINT_CPP_THROW + { + if (lhs == IntTraits::minInt && (T)rhs == -1) + { + // corner case of a corner case - lhs = min int, rhs = -1, + // but rhs is the return type, so in essence, we can return -lhs + // if rhs is a larger type than lhs + // If types are wrong, throws + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(push) +// cast truncates constant value +#pragma warning(disable : 4310) +#endif + + if (CompileConst::Value()) + result = SafeInt((T)(-(T)IntTraits::minInt)); + else + E::SafeIntOnOverflow(); + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(pop) +#endif + + return true; + } + + return false; + } +}; + +template +class DivisionCornerCaseHelper2 +{ +public: + static bool DivisionCornerCase2(U /*lhs*/, SafeInt /*rhs*/, SafeInt& /*result*/) SAFEINT_NOTHROW + { + return false; + } +}; + +// Division +template +SafeInt operator/(U lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + // Corner case - has to be handled seperately + SafeInt result; + if (DivisionCornerCaseHelper::method == (int)DivisionState_UnsignedSigned>:: + DivisionCornerCase1(lhs, rhs, result)) + return result; + + if (DivisionCornerCaseHelper2::isBothSigned>::DivisionCornerCase2(lhs, rhs, result)) + return result; + + // Otherwise normal logic works with addition of bounds check when casting from U->T + U ret; + DivisionHelper::method>::template DivideThrow(lhs, (T)rhs, ret); + return SafeInt(ret); +} + +// Addition +template +SafeInt operator+(U lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + AdditionHelper::method>::template AdditionThrow((T)rhs, lhs, ret); + return SafeInt(ret); +} + +// Subtraction +template +SafeInt operator-(U lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + SubtractionHelper::method>::template SubtractThrow(lhs, rhs.Ref(), ret); + + return SafeInt(ret); +} + +// Overrides designed to deal with cases where a SafeInt is assigned out +// to a normal int - this at least makes the last operation safe +// += +template +T& operator+=(T& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + AdditionHelper::method>::template AdditionThrow(lhs, (U)rhs, ret); + lhs = ret; + return lhs; +} + +template +T& operator-=(T& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + SubtractionHelper::method>::template SubtractThrow(lhs, (U)rhs, ret); + lhs = ret; + return lhs; +} + +template +T& operator*=(T& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + MultiplicationHelper::method>::template MultiplyThrow(lhs, (U)rhs, ret); + lhs = ret; + return lhs; +} + +template +T& operator/=(T& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + DivisionHelper::method>::template DivideThrow(lhs, (U)rhs, ret); + lhs = ret; + return lhs; +} + +template +T& operator%=(T& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + T ret(0); + ModulusHelper::method>::template ModulusThrow(lhs, (U)rhs, ret); + lhs = ret; + return lhs; +} + +template +T& operator&=(T& lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + lhs = BinaryAndHelper::method>::And(lhs, (U)rhs); + return lhs; +} + +template +T& operator^=(T& lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + lhs = BinaryXorHelper::method>::Xor(lhs, (U)rhs); + return lhs; +} + +template +T& operator|=(T& lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + lhs = BinaryOrHelper::method>::Or(lhs, (U)rhs); + return lhs; +} + +template +T& operator<<=(T& lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + lhs = (T)(SafeInt(lhs) << (U)rhs); + return lhs; +} + +template +T& operator>>=(T& lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + lhs = (T)(SafeInt(lhs) >> (U)rhs); + return lhs; +} + +// Specific pointer overrides +// Note - this function makes no attempt to ensure +// that the resulting pointer is still in the buffer, only +// that no int overflows happened on the way to getting the new pointer +template +T*& operator+=(T*& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + // Cast the pointer to a number so we can do arithmetic + SafeInt ptr_val = reinterpret_cast(lhs); + // Check first that rhs is valid for the type of ptrdiff_t + // and that multiplying by sizeof( T ) doesn't overflow a ptrdiff_t + // Next, we need to add 2 SafeInts of different types, so unbox the ptr_diff + // Finally, cast the number back to a pointer of the correct type + lhs = reinterpret_cast((size_t)(ptr_val + (ptrdiff_t)(SafeInt(rhs) * sizeof(T)))); + return lhs; +} + +template +T*& operator-=(T*& lhs, SafeInt rhs) SAFEINT_CPP_THROW +{ + // Cast the pointer to a number so we can do arithmetic + SafeInt ptr_val = reinterpret_cast(lhs); + // See above for comments + lhs = reinterpret_cast((size_t)(ptr_val - (ptrdiff_t)(SafeInt(rhs) * sizeof(T)))); + return lhs; +} + +template +T*& operator*=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator/=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator%=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator&=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator^=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator|=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator<<=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +template +T*& operator>>=(T*& lhs, SafeInt) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT(sizeof(T) == 0); + return (lhs = NULL); +} + +// Shift operators +// NOTE - shift operators always return the type of the lhs argument + +// Left shift +template +SafeInt operator<<(U lhs, SafeInt bits) SAFEINT_NOTHROW +{ + ShiftAssert(!IntTraits::isSigned || (T)bits >= 0); + ShiftAssert((T)bits < (int)IntTraits::bitCount); + + return SafeInt((U)(lhs << (T)bits)); +} + +// Right shift +template +SafeInt operator>>(U lhs, SafeInt bits) SAFEINT_NOTHROW +{ + ShiftAssert(!IntTraits::isSigned || (T)bits >= 0); + ShiftAssert((T)bits < (int)IntTraits::bitCount); + + return SafeInt((U)(lhs >> (T)bits)); +} + +// Bitwise operators +// This only makes sense if we're dealing with the same type and size +// demand a type T, or something that fits into a type T. + +// Bitwise & +template +SafeInt operator&(U lhs, SafeInt rhs)SAFEINT_NOTHROW +{ + return SafeInt(BinaryAndHelper::method>::And((T)rhs, lhs)); +} + +// Bitwise XOR +template +SafeInt operator^(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return SafeInt(BinaryXorHelper::method>::Xor((T)rhs, lhs)); +} + +// Bitwise OR +template +SafeInt operator|(U lhs, SafeInt rhs) SAFEINT_NOTHROW +{ + return SafeInt(BinaryOrHelper::method>::Or((T)rhs, lhs)); +} + +#if SAFEINT_COMPILER == GCC_COMPILER +#pragma GCC diagnostic pop +#endif + +#if SAFEINT_COMPILER == CLANG_COMPILER +#pragma clang diagnostic pop +#endif + +#ifdef C_ASSERT_DEFINED_SAFEINT +#undef C_ASSERT +#undef C_ASSERT_DEFINED_SAFEINT +#endif // C_ASSERT_DEFINED_SAFEINT + +} // namespace safeint3 +} // namespace msl diff --git a/deps/cpprestsdk/include/cpprest/details/basic_types.h b/deps/cpprestsdk/include/cpprest/details/basic_types.h new file mode 100644 index 00000000000..d2ceb87189e --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/details/basic_types.h @@ -0,0 +1,131 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Platform-dependent type definitions + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#pragma once + +#include "cpprest/details/cpprest_compat.h" +#include +#include +#include +#include + +#ifndef _WIN32 +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#include +#else +#include +#endif + +#include "cpprest/details/SafeInt3.hpp" + +namespace utility +{ +#ifdef _WIN32 +#define _UTF16_STRINGS +#endif + +// We should be using a 64-bit size type for most situations that do +// not involve specifying the size of a memory allocation or buffer. +typedef uint64_t size64_t; + +#ifndef _WIN32 +typedef uint32_t HRESULT; // Needed for PPLX +#endif + +#ifdef _UTF16_STRINGS +// +// On Windows, all strings are wide +// +typedef wchar_t char_t; +typedef std::wstring string_t; +#define _XPLATSTR(x) L##x +typedef std::wostringstream ostringstream_t; +typedef std::wofstream ofstream_t; +typedef std::wostream ostream_t; +typedef std::wistream istream_t; +typedef std::wifstream ifstream_t; +typedef std::wistringstream istringstream_t; +typedef std::wstringstream stringstream_t; +#define ucout std::wcout +#define ucin std::wcin +#define ucerr std::wcerr +#else +// +// On POSIX platforms, all strings are narrow +// +typedef char char_t; +typedef std::string string_t; +#define _XPLATSTR(x) x +typedef std::ostringstream ostringstream_t; +typedef std::ofstream ofstream_t; +typedef std::ostream ostream_t; +typedef std::istream istream_t; +typedef std::ifstream ifstream_t; +typedef std::istringstream istringstream_t; +typedef std::stringstream stringstream_t; +#define ucout std::cout +#define ucin std::cin +#define ucerr std::cerr +#endif // endif _UTF16_STRINGS + +#ifndef _TURN_OFF_PLATFORM_STRING +// The 'U' macro can be used to create a string or character literal of the platform type, i.e. utility::char_t. +// If you are using a library causing conflicts with 'U' macro, it can be turned off by defining the macro +// '_TURN_OFF_PLATFORM_STRING' before including the C++ REST SDK header files, and e.g. use '_XPLATSTR' instead. +#define U(x) _XPLATSTR(x) +#endif // !_TURN_OFF_PLATFORM_STRING + +} // namespace utility + +typedef char utf8char; +typedef std::string utf8string; +typedef std::stringstream utf8stringstream; +typedef std::ostringstream utf8ostringstream; +typedef std::ostream utf8ostream; +typedef std::istream utf8istream; +typedef std::istringstream utf8istringstream; + +#ifdef _UTF16_STRINGS +typedef wchar_t utf16char; +typedef std::wstring utf16string; +typedef std::wstringstream utf16stringstream; +typedef std::wostringstream utf16ostringstream; +typedef std::wostream utf16ostream; +typedef std::wistream utf16istream; +typedef std::wistringstream utf16istringstream; +#else +typedef char16_t utf16char; +typedef std::u16string utf16string; +typedef std::basic_stringstream utf16stringstream; +typedef std::basic_ostringstream utf16ostringstream; +typedef std::basic_ostream utf16ostream; +typedef std::basic_istream utf16istream; +typedef std::basic_istringstream utf16istringstream; +#endif + +#if defined(_WIN32) +// Include on everything except Windows Desktop ARM, unless explicitly excluded. +#if !defined(CPPREST_EXCLUDE_WEBSOCKETS) +#if defined(WINAPI_FAMILY) +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && defined(_M_ARM) +#define CPPREST_EXCLUDE_WEBSOCKETS +#endif +#else +#if defined(_M_ARM) +#define CPPREST_EXCLUDE_WEBSOCKETS +#endif +#endif +#endif +#endif diff --git a/deps/cpprestsdk/include/cpprest/details/cpprest_compat.h b/deps/cpprestsdk/include/cpprest/details/cpprest_compat.h new file mode 100644 index 00000000000..c0c55bebc36 --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/details/cpprest_compat.h @@ -0,0 +1,91 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Standard macros and definitions. + * This header has minimal dependency on windows headers and is safe for use in the public API + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#pragma once + +#if defined(_WIN32) + +#if _MSC_VER >= 1900 +#define CPPREST_NOEXCEPT noexcept +#define CPPREST_CONSTEXPR constexpr +#else +#define CPPREST_NOEXCEPT +#define CPPREST_CONSTEXPR const +#endif // _MSC_VER >= 1900 + +#define CASABLANCA_UNREFERENCED_PARAMETER(x) (x) + +#include + +#else // ^^^ _WIN32 ^^^ // vvv !_WIN32 vvv + +#define __declspec(x) __attribute__((x)) +#define dllimport +#define novtable /* no novtable equivalent */ +#define __assume(x) \ + do \ + { \ + if (!(x)) __builtin_unreachable(); \ + } while (false) +#define CASABLANCA_UNREFERENCED_PARAMETER(x) (void)x +#define CPPREST_NOEXCEPT noexcept +#define CPPREST_CONSTEXPR constexpr + +#include +#define _ASSERTE(x) assert(x) + +// No SAL on non Windows platforms +#include "cpprest/details/nosal.h" + +#if !defined(__cdecl) +#if defined(cdecl) +#define __cdecl __attribute__((cdecl)) +#else // ^^^ defined cdecl ^^^ // vvv !defined cdecl vvv +#define __cdecl +#endif // defined cdecl +#endif // not defined __cdecl + +#if defined(__ANDROID__) +// This is needed to disable the use of __thread inside the boost library. +// Android does not support thread local storage -- if boost is included +// without this macro defined, it will create references to __tls_get_addr +// which (while able to link) will not be available at runtime and prevent +// the .so from loading. +#if not defined BOOST_ASIO_DISABLE_THREAD_KEYWORD_EXTENSION +#define BOOST_ASIO_DISABLE_THREAD_KEYWORD_EXTENSION +#endif // not defined BOOST_ASIO_DISABLE_THREAD_KEYWORD_EXTENSION +#endif // defined(__ANDROID__) + +#ifdef __clang__ +#include +#endif // __clang__ +#endif // _WIN32 + +#define _NO_ASYNCRTIMP + +#ifdef _NO_ASYNCRTIMP +#define _ASYNCRTIMP +#else // ^^^ _NO_ASYNCRTIMP ^^^ // vvv !_NO_ASYNCRTIMP vvv +#ifdef _ASYNCRT_EXPORT +#define _ASYNCRTIMP __declspec(dllexport) +#else // ^^^ _ASYNCRT_EXPORT ^^^ // vvv !_ASYNCRT_EXPORT vvv +#define _ASYNCRTIMP __declspec(dllimport) +#endif // _ASYNCRT_EXPORT +#endif // _NO_ASYNCRTIMP + +#ifdef CASABLANCA_DEPRECATION_NO_WARNINGS +#define CASABLANCA_DEPRECATED(x) +#else +#define CASABLANCA_DEPRECATED(x) __declspec(deprecated(x)) +#endif // CASABLANCA_DEPRECATION_NO_WARNINGS diff --git a/deps/cpprestsdk/include/cpprest/details/web_utilities.h b/deps/cpprestsdk/include/cpprest/details/web_utilities.h new file mode 100644 index 00000000000..8b99d94aa2b --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/details/web_utilities.h @@ -0,0 +1,223 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * utility classes used by the different web:: clients + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ +#pragma once + +#include "cpprest/asyncrt_utils.h" +#include "cpprest/uri.h" + +namespace web +{ +namespace details +{ +class zero_memory_deleter +{ +public: + _ASYNCRTIMP void operator()(::utility::string_t* data) const; +}; +typedef std::unique_ptr<::utility::string_t, zero_memory_deleter> plaintext_string; + +#if defined(_WIN32) && !defined(CPPREST_TARGET_XP) +#if defined(__cplusplus_winrt) +class winrt_encryption +{ +public: + winrt_encryption() {} + _ASYNCRTIMP winrt_encryption(const std::wstring& data); + _ASYNCRTIMP plaintext_string decrypt() const; + +private: + ::pplx::task m_buffer; +}; +#else +class win32_encryption +{ +public: + win32_encryption() {} + _ASYNCRTIMP win32_encryption(const std::wstring& data); + _ASYNCRTIMP ~win32_encryption(); + _ASYNCRTIMP plaintext_string decrypt() const; + +private: + std::vector m_buffer; + size_t m_numCharacters; +}; +#endif +#endif +} // namespace details + +/// +/// Represents a set of user credentials (user name and password) to be used +/// for authentication. +/// +class credentials +{ +public: + /// + /// Constructs an empty set of credentials without a user name or password. + /// + credentials() {} + + /// + /// Constructs credentials from given user name and password. + /// + /// User name as a string. + /// Password as a string. + credentials(utility::string_t username, const utility::string_t& password) + : m_username(std::move(username)), m_password(password) + { + } + + /// + /// The user name associated with the credentials. + /// + /// A string containing the user name. + const utility::string_t& username() const { return m_username; } + + /// + /// The password for the user name associated with the credentials. + /// + /// A string containing the password. + CASABLANCA_DEPRECATED( + "This API is deprecated for security reasons to avoid unnecessary password copies stored in plaintext.") + utility::string_t password() const + { +#if defined(_WIN32) && !defined(CPPREST_TARGET_XP) + return utility::string_t(*m_password.decrypt()); +#else + return m_password; +#endif + } + + /// + /// Checks if credentials have been set + /// + /// true if user name and password is set, false otherwise. + bool is_set() const { return !m_username.empty(); } + + details::plaintext_string _internal_decrypt() const + { + // Encryption APIs not supported on XP +#if defined(_WIN32) && !defined(CPPREST_TARGET_XP) + return m_password.decrypt(); +#else + return details::plaintext_string(new ::utility::string_t(m_password)); +#endif + } + +private: + ::utility::string_t m_username; + +#if defined(_WIN32) && !defined(CPPREST_TARGET_XP) +#if defined(__cplusplus_winrt) + details::winrt_encryption m_password; +#else + details::win32_encryption m_password; +#endif +#else + ::utility::string_t m_password; +#endif +}; + +/// +/// web_proxy represents the concept of the web proxy, which can be auto-discovered, +/// disabled, or specified explicitly by the user. +/// +class web_proxy +{ + enum web_proxy_mode_internal + { + use_default_, + use_auto_discovery_, + disabled_, + user_provided_ + }; + +public: + enum web_proxy_mode + { + use_default = use_default_, + use_auto_discovery = use_auto_discovery_, + disabled = disabled_ + }; + + /// + /// Constructs a proxy with the default settings. + /// + web_proxy() : m_address(_XPLATSTR("")), m_mode(use_default_) {} + + /// + /// Creates a proxy with specified mode. + /// + /// Mode to use. + web_proxy(web_proxy_mode mode) : m_address(_XPLATSTR("")), m_mode(static_cast(mode)) {} + + /// + /// Creates a proxy explicitly with provided address. + /// + /// Proxy URI to use. + web_proxy(uri address) : m_address(address), m_mode(user_provided_) {} + + /// + /// Gets this proxy's URI address. Returns an empty URI if not explicitly set by user. + /// + /// A reference to this proxy's URI. + const uri& address() const { return m_address; } + + /// + /// Gets the credentials used for authentication with this proxy. + /// + /// Credentials to for this proxy. + const web::credentials& credentials() const { return m_credentials; } + + /// + /// Sets the credentials to use for authentication with this proxy. + /// + /// Credentials to use for this proxy. + void set_credentials(web::credentials cred) + { + if (m_mode == disabled_) + { + throw std::invalid_argument("Cannot attach credentials to a disabled proxy"); + } + m_credentials = std::move(cred); + } + + /// + /// Checks if this proxy was constructed with default settings. + /// + /// True if default, false otherwise. + bool is_default() const { return m_mode == use_default_; } + + /// + /// Checks if using a proxy is disabled. + /// + /// True if disabled, false otherwise. + bool is_disabled() const { return m_mode == disabled_; } + + /// + /// Checks if the auto discovery protocol, WPAD, is to be used. + /// + /// True if auto discovery enabled, false otherwise. + bool is_auto_discovery() const { return m_mode == use_auto_discovery_; } + + /// + /// Checks if a proxy address is explicitly specified by the user. + /// + /// True if a proxy address was explicitly specified, false otherwise. + bool is_specified() const { return m_mode == user_provided_; } + +private: + web::uri m_address; + web_proxy_mode_internal m_mode; + web::credentials m_credentials; +}; + +} // namespace web diff --git a/deps/cpprestsdk/include/cpprest/json.h b/deps/cpprestsdk/include/cpprest/json.h new file mode 100644 index 00000000000..4095be50ea3 --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/json.h @@ -0,0 +1,1786 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * HTTP Library: JSON parser and writer + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ +#pragma once + +#ifndef CASA_JSON_H +#define CASA_JSON_H + +#include "cpprest/asyncrt_utils.h" +#include "cpprest/details/basic_types.h" +#include +#include +#include +#include +#include +#include + +namespace web +{ +/// Library for parsing and serializing JSON values to and from C++ types. +namespace json +{ +// Various forward declarations. +namespace details +{ +class _Value; +class _Number; +class _Null; +class _Boolean; +class _String; +class _Object; +class _Array; +template +class JSON_Parser; +} // namespace details + +namespace details +{ +extern bool g_keep_json_object_unsorted; +} + +/// +/// Preserve the order of the name/value pairs when parsing a JSON object. +/// The default is false, which can yield better performance. +/// +/// true if ordering should be preserved when parsing, false otherwise. +/// Note this is a global setting and affects all JSON parsing done. +void _ASYNCRTIMP __cdecl keep_object_element_order(bool keep_order); + +#ifdef _WIN32 +#ifdef _DEBUG +#define ENABLE_JSON_VALUE_VISUALIZER +#endif +#endif + +class number; +class array; +class object; + +/// +/// A JSON value represented as a C++ class. +/// +class value +{ +public: + /// + /// This enumeration represents the various kinds of JSON values. + /// + enum value_type + { + /// Number value + Number, + /// Boolean value + Boolean, + /// String value + String, + /// Object value + Object, + /// Array value + Array, + /// Null value + Null + }; + + /// + /// Constructor creating a null value + /// + _ASYNCRTIMP value(); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(int32_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(uint32_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(int64_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(uint64_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(double value); + + /// + /// Constructor creating a JSON Boolean value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP explicit value(bool value); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character + /// width This constructor has O(n) performance because it tries to determine if specified string + /// has characters that should be properly escaped in JSON. + _ASYNCRTIMP explicit value(utility::string_t value); + + /// + /// Constructor creating a JSON string value specifying if the string contains characters to escape + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character + /// width Whether contains characters that should + /// be escaped in JSON value This constructor has O(1) performance. + /// + _ASYNCRTIMP explicit value(utility::string_t value, bool has_escape_chars); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character + /// width This constructor has O(n) performance because it tries to determine if specified + /// string has characters that should be properly escaped in JSON. + /// + /// + /// This constructor exists in order to avoid string literals matching another constructor, + /// as is very likely. For example, conversion to bool does not require a user-defined conversion, + /// and will therefore match first, which means that the JSON value turns up as a boolean. + /// + /// + _ASYNCRTIMP explicit value(const utility::char_t* value); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character + /// width Whether contains characters + /// + /// This overload has O(1) performance. + /// + /// + /// This constructor exists in order to avoid string literals matching another constructor, + /// as is very likely. For example, conversion to bool does not require a user-defined conversion, + /// and will therefore match first, which means that the JSON value turns up as a boolean. + /// + /// + _ASYNCRTIMP explicit value(const utility::char_t* value, bool has_escape_chars); + + /// + /// Copy constructor + /// + _ASYNCRTIMP value(const value&); + + /// + /// Move constructor + /// + _ASYNCRTIMP value(value&&) CPPREST_NOEXCEPT; + + /// + /// Assignment operator. + /// + /// The JSON value object that contains the result of the assignment. + _ASYNCRTIMP value& operator=(const value&); + + /// + /// Move assignment operator. + /// + /// The JSON value object that contains the result of the assignment. + _ASYNCRTIMP value& operator=(value&&) CPPREST_NOEXCEPT; + + // Static factories + + /// + /// Creates a null value + /// + /// A JSON null value + static _ASYNCRTIMP value __cdecl null(); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(double value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(int32_t value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(uint32_t value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(int64_t value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(uint64_t value); + + /// + /// Creates a Boolean value + /// + /// The C++ value to create a JSON value from + /// A JSON Boolean value + static _ASYNCRTIMP value __cdecl boolean(bool value); + + /// + /// Creates a string value + /// + /// The C++ value to create a JSON value from + /// A JSON string value + /// + /// This overload has O(n) performance because it tries to determine if + /// specified string has characters that should be properly escaped in JSON. + /// + static _ASYNCRTIMP value __cdecl string(utility::string_t value); + + /// + /// Creates a string value specifying if the string contains characters to escape + /// + /// The C++ value to create a JSON value from + /// Whether contains characters + /// that should be escaped in JSON value + /// A JSON string value + /// + /// This overload has O(1) performance. + /// + static _ASYNCRTIMP value __cdecl string(utility::string_t value, bool has_escape_chars); + +#ifdef _WIN32 +private: + // Only used internally by JSON parser. + static _ASYNCRTIMP value __cdecl string(const std::string& value); + +public: +#endif + + /// + /// Creates an object value + /// + /// Whether to preserve the original order of the fields + /// An empty JSON object value + static _ASYNCRTIMP json::value __cdecl object(bool keep_order = false); + + /// + /// Creates an object value from a collection of field/values + /// + /// Field names associated with JSON values + /// Whether to preserve the original order of the fields + /// A non-empty JSON object value + static _ASYNCRTIMP json::value __cdecl object(std::vector> fields, + bool keep_order = false); + + /// + /// Creates an empty JSON array + /// + /// An empty JSON array value + static _ASYNCRTIMP json::value __cdecl array(); + + /// + /// Creates a JSON array + /// + /// The initial number of elements of the JSON value + /// A JSON array value + static _ASYNCRTIMP json::value __cdecl array(size_t size); + + /// + /// Creates a JSON array + /// + /// A vector of JSON values + /// A JSON array value + static _ASYNCRTIMP json::value __cdecl array(std::vector elements); + + /// + /// Accesses the type of JSON value the current value instance is + /// + /// The value's type + _ASYNCRTIMP json::value::value_type type() const; + + /// + /// Is the current value a null value? + /// + /// true if the value is a null value, false otherwise + bool is_null() const { return type() == Null; }; + + /// + /// Is the current value a number value? + /// + /// true if the value is a number value, false otherwise + bool is_number() const { return type() == Number; } + + /// + /// Is the current value represented as an integer number value? + /// + /// + /// Note that if a json value is a number but represented as a double it can still + /// be retrieved as a integer using as_integer(), however the value will be truncated. + /// + /// true if the value is an integer value, false otherwise. + _ASYNCRTIMP bool is_integer() const; + + /// + /// Is the current value represented as an double number value? + /// + /// + /// Note that if a json value is a number but represented as a int it can still + /// be retrieved as a double using as_double(). + /// + /// true if the value is an double value, false otherwise. + _ASYNCRTIMP bool is_double() const; + + /// + /// Is the current value a Boolean value? + /// + /// true if the value is a Boolean value, false otherwise + bool is_boolean() const { return type() == Boolean; } + + /// + /// Is the current value a string value? + /// + /// true if the value is a string value, false otherwise + bool is_string() const { return type() == String; } + + /// + /// Is the current value an array? + /// + /// true if the value is an array, false otherwise + bool is_array() const { return type() == Array; } + + /// + /// Is the current value an object? + /// + /// true if the value is an object, false otherwise + bool is_object() const { return type() == Object; } + + /// + /// Gets the number of children of the value. + /// + /// The number of children. 0 for all non-composites. + size_t size() const; + + /// + /// Parses a string and construct a JSON value. + /// + /// The C++ value to create a JSON value from, a C++ STL double-byte string + _ASYNCRTIMP static value __cdecl parse(const utility::string_t& value); + + /// + /// Attempts to parse a string and construct a JSON value. + /// + /// The C++ value to create a JSON value from, a C++ STL double-byte string + /// If parsing fails, the error code is greater than 0 + /// The parsed object. Returns web::json::value::null if failed + _ASYNCRTIMP static value __cdecl parse(const utility::string_t& value, std::error_code& errorCode); + + /// + /// Serializes the current JSON value to a C++ string. + /// + /// A string representation of the value + _ASYNCRTIMP utility::string_t serialize() const; + + /// + /// Serializes the current JSON value to a C++ string. + /// + /// A string representation of the value + CASABLANCA_DEPRECATED("This API is deprecated and has been renamed to avoid confusion with as_string(), use " + "::web::json::value::serialize() instead.") + _ASYNCRTIMP utility::string_t to_string() const; + + /// + /// Parses a JSON value from the contents of an input stream using the native platform character width. + /// + /// The stream to read the JSON value from + /// The JSON value object created from the input stream. + _ASYNCRTIMP static value __cdecl parse(utility::istream_t& input); + + /// + /// Parses a JSON value from the contents of an input stream using the native platform character width. + /// + /// The stream to read the JSON value from + /// If parsing fails, the error code is greater than 0 + /// The parsed object. Returns web::json::value::null if failed + _ASYNCRTIMP static value __cdecl parse(utility::istream_t& input, std::error_code& errorCode); + + /// + /// Writes the current JSON value to a stream with the native platform character width. + /// + /// The stream that the JSON string representation should be written to. + _ASYNCRTIMP void serialize(utility::ostream_t& stream) const; + +#ifdef _WIN32 + /// + /// Parses a JSON value from the contents of a single-byte (UTF8) stream. + /// + /// The stream to read the JSON value from + _ASYNCRTIMP static value __cdecl parse(std::istream& stream); + + /// + /// Parses a JSON value from the contents of a single-byte (UTF8) stream. + /// + /// The stream to read the JSON value from + /// If parsing fails, the error code is greater than 0 + /// The parsed object. Returns web::json::value::null if failed + _ASYNCRTIMP static value __cdecl parse(std::istream& stream, std::error_code& error); + + /// + /// Serializes the content of the value into a single-byte (UTF8) stream. + /// + /// The stream that the JSON string representation should be written to. + _ASYNCRTIMP void serialize(std::ostream& stream) const; +#endif + + /// + /// Converts the JSON value to a C++ double, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// A double representation of the value + _ASYNCRTIMP double as_double() const; + + /// + /// Converts the JSON value to a C++ integer, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// An integer representation of the value + _ASYNCRTIMP int as_integer() const; + + /// + /// Converts the JSON value to a number class, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// An instance of number class + _ASYNCRTIMP const json::number& as_number() const; + + /// + /// Converts the JSON value to a C++ bool, if and only if it is a Boolean value. + /// + /// A C++ bool representation of the value + _ASYNCRTIMP bool as_bool() const; + + /// + /// Converts the JSON value to a json array, if and only if it is an array value. + /// + /// The returned json::array should have the same or shorter lifetime as this + /// An array representation of the value + _ASYNCRTIMP json::array& as_array(); + + /// + /// Converts the JSON value to a json array, if and only if it is an array value. + /// + /// The returned json::array should have the same or shorter lifetime as this + /// An array representation of the value + _ASYNCRTIMP const json::array& as_array() const; + + /// + /// Converts the JSON value to a json object, if and only if it is an object value. + /// + /// An object representation of the value + _ASYNCRTIMP json::object& as_object(); + + /// + /// Converts the JSON value to a json object, if and only if it is an object value. + /// + /// An object representation of the value + _ASYNCRTIMP const json::object& as_object() const; + + /// + /// Converts the JSON value to a C++ STL string, if and only if it is a string value. + /// + /// A C++ STL string representation of the value + _ASYNCRTIMP const utility::string_t& as_string() const; + + /// + /// Compares two JSON values for equality. + /// + /// The JSON value to compare with. + /// True if the values are equal. + _ASYNCRTIMP bool operator==(const value& other) const; + + /// + /// Compares two JSON values for inequality. + /// + /// The JSON value to compare with. + /// True if the values are unequal. + bool operator!=(const value& other) const { return !((*this) == other); } + + /// + /// Tests for the presence of a field. + /// + /// The name of the field + /// True if the field exists, false otherwise. + bool has_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of a number field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_number_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of an integer field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_integer_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of a double field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_double_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of a boolean field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_boolean_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of a string field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_string_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of an array field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_array_field(const utility::string_t& key) const; + + /// + /// Tests for the presence of an object field + /// + /// The name of the field + /// True if the field exists, false otherwise. + _ASYNCRTIMP bool has_object_field(const utility::string_t& key) const; + + /// + /// Accesses a field of a JSON object. + /// + /// The name of the field + /// The value kept in the field; null if the field does not exist + CASABLANCA_DEPRECATED( + "This API is deprecated and will be removed in a future release, use json::value::at() instead.") + value get(const utility::string_t& key) const; + + /// + /// Erases an element of a JSON array. Throws if index is out of bounds. + /// + /// The index of the element to erase in the JSON array. + _ASYNCRTIMP void erase(size_t index); + + /// + /// Erases an element of a JSON object. Throws if the key doesn't exist. + /// + /// The key of the element to erase in the JSON object. + _ASYNCRTIMP void erase(const utility::string_t& key); + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value. + _ASYNCRTIMP json::value& at(size_t index); + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value. + _ASYNCRTIMP const json::value& at(size_t index) const; + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value. + _ASYNCRTIMP json::value& at(const utility::string_t& key); + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value. + _ASYNCRTIMP const json::value& at(const utility::string_t& key) const; + + /// + /// Accesses a field of a JSON object. + /// + /// The name of the field + /// A reference to the value kept in the field. + _ASYNCRTIMP value& operator[](const utility::string_t& key); + +#ifdef _WIN32 +private: + // Only used internally by JSON parser + _ASYNCRTIMP value& operator[](const std::string& key) + { + // JSON object stores its field map as a unordered_map of string_t, so this conversion is hard to avoid + return operator[](utility::conversions::to_string_t(key)); + } + +public: +#endif + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array + /// The value kept at the array index; null if outside the boundaries of the array + CASABLANCA_DEPRECATED( + "This API is deprecated and will be removed in a future release, use json::value::at() instead.") + value get(size_t index) const; + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + _ASYNCRTIMP value& operator[](size_t index); + +private: + friend class web::json::details::_Object; + friend class web::json::details::_Array; + template + friend class web::json::details::JSON_Parser; + +#ifdef _WIN32 + /// + /// Writes the current JSON value as a double-byte string to a string instance. + /// + /// The string that the JSON representation should be written to. + _ASYNCRTIMP void format(std::basic_string& string) const; +#endif + /// + /// Serializes the content of the value into a string instance in UTF8 format + /// + /// The string that the JSON representation should be written to + _ASYNCRTIMP void format(std::basic_string& string) const; + +#ifdef ENABLE_JSON_VALUE_VISUALIZER + explicit value(std::unique_ptr v, value_type kind) : m_value(std::move(v)), m_kind(kind) +#else + explicit value(std::unique_ptr v) : m_value(std::move(v)) +#endif + { + } + + std::unique_ptr m_value; +#ifdef ENABLE_JSON_VALUE_VISUALIZER + value_type m_kind; +#endif +}; + +/// +/// A single exception type to represent errors in parsing, converting, and accessing +/// elements of JSON values. +/// +class json_exception : public std::exception +{ +private: + std::string _message; + +public: + json_exception(const char* const message) : _message(message) {} +#ifdef _UTF16_STRINGS + json_exception(const wchar_t* const message) : _message(utility::conversions::utf16_to_utf8(message)) {} +#endif // _UTF16_STRINGS + json_exception(std::string&& message) : _message(std::move(message)) {} + + // Must be narrow string because it derives from std::exception + const char* what() const CPPREST_NOEXCEPT { return _message.c_str(); } +}; + +namespace details +{ +enum json_error +{ + left_over_character_in_stream = 1, + malformed_array_literal, + malformed_comment, + malformed_literal, + malformed_object_literal, + malformed_numeric_literal, + malformed_string_literal, + malformed_token, + mismatched_brances, + nesting, + unexpected_token +}; + +class json_error_category_impl : public std::error_category +{ +public: + virtual const char* name() const CPPREST_NOEXCEPT override { return "json"; } + + virtual std::string message(int ev) const override + { + switch (ev) + { + case json_error::left_over_character_in_stream: + return "Left-over characters in stream after parsing a JSON value"; + case json_error::malformed_array_literal: return "Malformed array literal"; + case json_error::malformed_comment: return "Malformed comment"; + case json_error::malformed_literal: return "Malformed literal"; + case json_error::malformed_object_literal: return "Malformed object literal"; + case json_error::malformed_numeric_literal: return "Malformed numeric literal"; + case json_error::malformed_string_literal: return "Malformed string literal"; + case json_error::malformed_token: return "Malformed token"; + case json_error::mismatched_brances: return "Mismatched braces"; + case json_error::nesting: return "Nesting too deep"; + case json_error::unexpected_token: return "Unexpected token"; + default: return "Unknown json error"; + } + } +}; + +const json_error_category_impl& json_error_category(); +} // namespace details + +/// +/// A JSON array represented as a C++ class. +/// +class array +{ + typedef std::vector storage_type; + +public: + typedef storage_type::iterator iterator; + typedef storage_type::const_iterator const_iterator; + typedef storage_type::reverse_iterator reverse_iterator; + typedef storage_type::const_reverse_iterator const_reverse_iterator; + typedef storage_type::size_type size_type; + +private: + array() : m_elements() {} + array(size_type size) : m_elements(size) {} + array(storage_type elements) : m_elements(std::move(elements)) {} + +public: + /// + /// Gets the beginning iterator element of the array + /// + /// An iterator to the beginning of the JSON array. + iterator begin() { return m_elements.begin(); } + + /// + /// Gets the beginning const iterator element of the array. + /// + /// A const_iterator to the beginning of the JSON array. + const_iterator begin() const { return m_elements.cbegin(); } + + /// + /// Gets the end iterator element of the array + /// + /// An iterator to the end of the JSON array. + iterator end() { return m_elements.end(); } + + /// + /// Gets the end const iterator element of the array. + /// + /// A const_iterator to the end of the JSON array. + const_iterator end() const { return m_elements.cend(); } + + /// + /// Gets the beginning reverse iterator element of the array + /// + /// An reverse_iterator to the beginning of the JSON array. + reverse_iterator rbegin() { return m_elements.rbegin(); } + + /// + /// Gets the beginning const reverse iterator element of the array + /// + /// An const_reverse_iterator to the beginning of the JSON array. + const_reverse_iterator rbegin() const { return m_elements.rbegin(); } + + /// + /// Gets the end reverse iterator element of the array + /// + /// An reverse_iterator to the end of the JSON array. + reverse_iterator rend() { return m_elements.rend(); } + + /// + /// Gets the end const reverse iterator element of the array + /// + /// An const_reverse_iterator to the end of the JSON array. + const_reverse_iterator rend() const { return m_elements.crend(); } + + /// + /// Gets the beginning const iterator element of the array. + /// + /// A const_iterator to the beginning of the JSON array. + const_iterator cbegin() const { return m_elements.cbegin(); } + + /// + /// Gets the end const iterator element of the array. + /// + /// A const_iterator to the end of the JSON array. + const_iterator cend() const { return m_elements.cend(); } + + /// + /// Gets the beginning const reverse iterator element of the array. + /// + /// A const_reverse_iterator to the beginning of the JSON array. + const_reverse_iterator crbegin() const { return m_elements.crbegin(); } + + /// + /// Gets the end const reverse iterator element of the array. + /// + /// A const_reverse_iterator to the end of the JSON array. + const_reverse_iterator crend() const { return m_elements.crend(); } + + /// + /// Deletes an element of the JSON array. + /// + /// A const_iterator to the element to delete. + /// Iterator to the new location of the element following the erased element. + /// GCC doesn't support erase with const_iterator on vector yet. In the future this should be + /// changed. + iterator erase(iterator position) { return m_elements.erase(position); } + + /// + /// Deletes the element at an index of the JSON array. + /// + /// The index of the element to delete. + void erase(size_type index) + { + if (index >= m_elements.size()) + { + throw json_exception("index out of bounds"); + } + m_elements.erase(m_elements.begin() + index); + } + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + json::value& at(size_type index) + { + if (index >= m_elements.size()) throw json_exception("index out of bounds"); + + return m_elements[index]; + } + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + const json::value& at(size_type index) const + { + if (index >= m_elements.size()) throw json_exception("index out of bounds"); + + return m_elements[index]; + } + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + json::value& operator[](size_type index) + { + msl::safeint3::SafeInt nMinSize(index); + nMinSize += 1; + msl::safeint3::SafeInt nlastSize(m_elements.size()); + if (nlastSize < nMinSize) m_elements.resize(nMinSize); + + return m_elements[index]; + } + + /// + /// Gets the number of elements of the array. + /// + /// The number of elements. + size_type size() const { return m_elements.size(); } + +private: + storage_type m_elements; + + friend class details::_Array; + template + friend class json::details::JSON_Parser; +}; + +/// +/// A JSON object represented as a C++ class. +/// +class object +{ + typedef std::vector> storage_type; + +public: + typedef storage_type::iterator iterator; + typedef storage_type::const_iterator const_iterator; + typedef storage_type::reverse_iterator reverse_iterator; + typedef storage_type::const_reverse_iterator const_reverse_iterator; + typedef storage_type::size_type size_type; + +private: + object(bool keep_order = false) : m_elements(), m_keep_order(keep_order) {} + object(storage_type elements, bool keep_order = false) : m_elements(std::move(elements)), m_keep_order(keep_order) + { + if (!keep_order) + { + sort(m_elements.begin(), m_elements.end(), compare_pairs); + } + } + +public: + /// + /// Gets the beginning iterator element of the object + /// + /// An iterator to the beginning of the JSON object. + iterator begin() { return m_elements.begin(); } + + /// + /// Gets the beginning const iterator element of the object. + /// + /// A const_iterator to the beginning of the JSON object. + const_iterator begin() const { return m_elements.cbegin(); } + + /// + /// Gets the end iterator element of the object + /// + /// An iterator to the end of the JSON object. + iterator end() { return m_elements.end(); } + + /// + /// Gets the end const iterator element of the object. + /// + /// A const_iterator to the end of the JSON object. + const_iterator end() const { return m_elements.cend(); } + + /// + /// Gets the beginning reverse iterator element of the object + /// + /// An reverse_iterator to the beginning of the JSON object. + reverse_iterator rbegin() { return m_elements.rbegin(); } + + /// + /// Gets the beginning const reverse iterator element of the object + /// + /// An const_reverse_iterator to the beginning of the JSON object. + const_reverse_iterator rbegin() const { return m_elements.rbegin(); } + + /// + /// Gets the end reverse iterator element of the object + /// + /// An reverse_iterator to the end of the JSON object. + reverse_iterator rend() { return m_elements.rend(); } + + /// + /// Gets the end const reverse iterator element of the object + /// + /// An const_reverse_iterator to the end of the JSON object. + const_reverse_iterator rend() const { return m_elements.crend(); } + + /// + /// Gets the beginning const iterator element of the object. + /// + /// A const_iterator to the beginning of the JSON object. + const_iterator cbegin() const { return m_elements.cbegin(); } + + /// + /// Gets the end const iterator element of the object. + /// + /// A const_iterator to the end of the JSON object. + const_iterator cend() const { return m_elements.cend(); } + + /// + /// Gets the beginning const reverse iterator element of the object. + /// + /// A const_reverse_iterator to the beginning of the JSON object. + const_reverse_iterator crbegin() const { return m_elements.crbegin(); } + + /// + /// Gets the end const reverse iterator element of the object. + /// + /// A const_reverse_iterator to the end of the JSON object. + const_reverse_iterator crend() const { return m_elements.crend(); } + + /// + /// Deletes an element of the JSON object. + /// + /// A const_iterator to the element to delete. + /// Iterator to the new location of the element following the erased element. + /// GCC doesn't support erase with const_iterator on vector yet. In the future this should be + /// changed. + iterator erase(iterator position) { return m_elements.erase(position); } + + /// + /// Deletes an element of the JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + void erase(const utility::string_t& key) + { + auto iter = find_by_key(key); + if (iter == m_elements.end()) + { + throw web::json::json_exception("Key not found"); + } + + m_elements.erase(iter); + } + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field. + json::value& at(const utility::string_t& key) + { + auto iter = find_by_key(key); + if (iter == m_elements.end()) + { + throw web::json::json_exception("Key not found"); + } + + return iter->second; + } + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field. + const json::value& at(const utility::string_t& key) const + { + auto iter = find_by_key(key); + if (iter == m_elements.end()) + { + throw web::json::json_exception("Key not found"); + } + + return iter->second; + } + + /// + /// Accesses an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field, otherwise a newly created null value + /// that will be stored for the given key. + json::value& operator[](const utility::string_t& key) + { + auto iter = find_insert_location(key); + + if (iter == m_elements.end() || key != iter->first) + { + return m_elements.insert(iter, std::pair(key, value()))->second; + } + + return iter->second; + } + + /// + /// Gets an iterator to an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// A const iterator to the value kept in the field. + const_iterator find(const utility::string_t& key) const { return find_by_key(key); } + + /// + /// Gets the number of elements of the object. + /// + /// The number of elements. + size_type size() const { return m_elements.size(); } + + /// + /// Checks if there are any elements in the JSON object. + /// + /// True if empty. + bool empty() const { return m_elements.empty(); } + +private: + static bool compare_pairs(const std::pair& p1, + const std::pair& p2) + { + return p1.first < p2.first; + } + static bool compare_with_key(const std::pair& p1, const utility::string_t& key) + { + return p1.first < key; + } + + storage_type::iterator find_insert_location(const utility::string_t& key) + { + if (m_keep_order) + { + return std::find_if(m_elements.begin(), + m_elements.end(), + [&key](const std::pair& p) { return p.first == key; }); + } + else + { + return std::lower_bound(m_elements.begin(), m_elements.end(), key, compare_with_key); + } + } + + storage_type::const_iterator find_by_key(const utility::string_t& key) const + { + if (m_keep_order) + { + return std::find_if(m_elements.begin(), + m_elements.end(), + [&key](const std::pair& p) { return p.first == key; }); + } + else + { + auto iter = std::lower_bound(m_elements.begin(), m_elements.end(), key, compare_with_key); + if (iter != m_elements.end() && key != iter->first) + { + return m_elements.end(); + } + return iter; + } + } + + storage_type::iterator find_by_key(const utility::string_t& key) + { + auto iter = find_insert_location(key); + if (iter != m_elements.end() && key != iter->first) + { + return m_elements.end(); + } + return iter; + } + + storage_type m_elements; + bool m_keep_order; + friend class details::_Object; + + template + friend class json::details::JSON_Parser; +}; + +/// +/// A JSON number represented as a C++ class. +/// +class number +{ + // Note that these constructors make sure that only negative integers are stored as signed int64 (while others + // convert to unsigned int64). This helps handling number objects e.g. comparing two numbers. + + number(double value) : m_value(value), m_type(double_type) {} + number(int32_t value) : m_intval(value), m_type(value < 0 ? signed_type : unsigned_type) {} + number(uint32_t value) : m_intval(value), m_type(unsigned_type) {} + number(int64_t value) : m_intval(value), m_type(value < 0 ? signed_type : unsigned_type) {} + number(uint64_t value) : m_uintval(value), m_type(unsigned_type) {} + +public: + /// + /// Does the number fit into int32? + /// + /// true if the number fits into int32, false otherwise + _ASYNCRTIMP bool is_int32() const; + + /// + /// Does the number fit into unsigned int32? + /// + /// true if the number fits into unsigned int32, false otherwise + _ASYNCRTIMP bool is_uint32() const; + + /// + /// Does the number fit into int64? + /// + /// true if the number fits into int64, false otherwise + _ASYNCRTIMP bool is_int64() const; + + /// + /// Does the number fit into unsigned int64? + /// + /// true if the number fits into unsigned int64, false otherwise + bool is_uint64() const + { + switch (m_type) + { + case signed_type: return m_intval >= 0; + case unsigned_type: return true; + case double_type: + default: return false; + } + } + + /// + /// Converts the JSON number to a C++ double. + /// + /// A double representation of the number + double to_double() const + { + switch (m_type) + { + case double_type: return m_value; + case signed_type: return static_cast(m_intval); + case unsigned_type: return static_cast(m_uintval); + default: return false; + } + } + + /// + /// Converts the JSON number to int32. + /// + /// An int32 representation of the number + int32_t to_int32() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to unsigned int32. + /// + /// An unsigned int32 representation of the number + uint32_t to_uint32() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to int64. + /// + /// An int64 representation of the number + int64_t to_int64() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to unsigned int64. + /// + /// An unsigned int64 representation of the number + uint64_t to_uint64() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Is the number represented internally as an integral type? + /// + /// true if the number is represented as an integral type, false otherwise + bool is_integral() const { return m_type != double_type; } + + /// + /// Compares two JSON numbers for equality. + /// + /// The JSON number to compare with. + /// True if the numbers are equal. + bool operator==(const number& other) const + { + if (m_type != other.m_type) return false; + + switch (m_type) + { + case json::number::type::signed_type: return m_intval == other.m_intval; + case json::number::type::unsigned_type: return m_uintval == other.m_uintval; + case json::number::type::double_type: return m_value == other.m_value; + } + __assume(0); + // Absence of this return statement provokes a warning from Intel + // compiler, but its presence results in a warning from MSVC, so + // we have to resort to conditional compilation to keep both happy. +#ifdef __INTEL_COMPILER + return false; +#endif + } + +private: + union { + int64_t m_intval; + uint64_t m_uintval; + double m_value; + }; + + enum type + { + signed_type = 0, + unsigned_type, + double_type + } m_type; + + friend class details::_Number; +}; + +namespace details +{ +class _Value +{ +public: + virtual std::unique_ptr<_Value> _copy_value() = 0; + + virtual bool has_field(const utility::string_t&) const { return false; } + virtual value get_field(const utility::string_t&) const { throw json_exception("not an object"); } + virtual value get_element(array::size_type) const { throw json_exception("not an array"); } + + virtual value& index(const utility::string_t&) { throw json_exception("not an object"); } + virtual value& index(array::size_type) { throw json_exception("not an array"); } + + virtual const value& cnst_index(const utility::string_t&) const { throw json_exception("not an object"); } + virtual const value& cnst_index(array::size_type) const { throw json_exception("not an array"); } + + // Common function used for serialization to strings and streams. + virtual void serialize_impl(std::string& str) const { format(str); } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const { format(str); } +#endif + + virtual utility::string_t to_string() const + { + utility::string_t str; + serialize_impl(str); + return str; + } + + virtual json::value::value_type type() const { return json::value::Null; } + + virtual bool is_integer() const { throw json_exception("not a number"); } + virtual bool is_double() const { throw json_exception("not a number"); } + + virtual const json::number& as_number() { throw json_exception("not a number"); } + virtual double as_double() const { throw json_exception("not a number"); } + virtual int as_integer() const { throw json_exception("not a number"); } + virtual bool as_bool() const { throw json_exception("not a boolean"); } + virtual json::array& as_array() { throw json_exception("not an array"); } + virtual const json::array& as_array() const { throw json_exception("not an array"); } + virtual json::object& as_object() { throw json_exception("not an object"); } + virtual const json::object& as_object() const { throw json_exception("not an object"); } + virtual const utility::string_t& as_string() const { throw json_exception("not a string"); } + + virtual size_t size() const { return 0; } + + virtual ~_Value() {} + +protected: + _Value() {} + + virtual void format(std::basic_string& stream) const { stream.append("null"); } +#ifdef _WIN32 + virtual void format(std::basic_string& stream) const { stream.append(L"null"); } +#endif +private: + friend class web::json::value; +}; + +class _Null : public _Value +{ +public: + virtual std::unique_ptr<_Value> _copy_value() { return utility::details::make_unique<_Null>(); } + virtual json::value::value_type type() const { return json::value::Null; } +}; + +class _Number : public _Value +{ +public: + _Number(double value) : m_number(value) {} + _Number(int32_t value) : m_number(value) {} + _Number(uint32_t value) : m_number(value) {} + _Number(int64_t value) : m_number(value) {} + _Number(uint64_t value) : m_number(value) {} + + virtual std::unique_ptr<_Value> _copy_value() { return utility::details::make_unique<_Number>(*this); } + + virtual json::value::value_type type() const { return json::value::Number; } + + virtual bool is_integer() const { return m_number.is_integral(); } + virtual bool is_double() const { return !m_number.is_integral(); } + + virtual double as_double() const { return m_number.to_double(); } + + virtual int as_integer() const { return m_number.to_int32(); } + + virtual const number& as_number() { return m_number; } + +protected: + virtual void format(std::basic_string& stream) const; +#ifdef _WIN32 + virtual void format(std::basic_string& stream) const; +#endif +private: + template + friend class json::details::JSON_Parser; + + json::number m_number; +}; + +class _Boolean : public _Value +{ +public: + _Boolean(bool value) : m_value(value) {} + + virtual std::unique_ptr<_Value> _copy_value() { return utility::details::make_unique<_Boolean>(*this); } + + virtual json::value::value_type type() const { return json::value::Boolean; } + + virtual bool as_bool() const { return m_value; } + +protected: + virtual void format(std::basic_string& stream) const { stream.append(m_value ? "true" : "false"); } + +#ifdef _WIN32 + virtual void format(std::basic_string& stream) const { stream.append(m_value ? L"true" : L"false"); } +#endif +private: + template + friend class json::details::JSON_Parser; + bool m_value; +}; + +class _String : public _Value +{ +public: + _String(utility::string_t value) : m_string(std::move(value)) { m_has_escape_char = has_escape_chars(*this); } + _String(utility::string_t value, bool escaped_chars) : m_string(std::move(value)), m_has_escape_char(escaped_chars) + { + } + +#ifdef _WIN32 + _String(std::string&& value) : m_string(utility::conversions::to_utf16string(std::move(value))) + { + m_has_escape_char = has_escape_chars(*this); + } + _String(std::string&& value, bool escape_chars) + : m_string(utility::conversions::to_utf16string(std::move(value))), m_has_escape_char(escape_chars) + { + } +#endif + + virtual std::unique_ptr<_Value> _copy_value() { return utility::details::make_unique<_String>(*this); } + + virtual json::value::value_type type() const { return json::value::String; } + + virtual const utility::string_t& as_string() const; + + virtual void serialize_impl(std::string& str) const { serialize_impl_char_type(str); } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const { serialize_impl_char_type(str); } +#endif + +protected: + virtual void format(std::basic_string& str) const; +#ifdef _WIN32 + virtual void format(std::basic_string& str) const; +#endif + +private: + friend class _Object; + friend class _Array; + + size_t get_reserve_size() const { return m_string.size() + 2; } + + template + void serialize_impl_char_type(std::basic_string& str) const + { + // To avoid repeated allocations reserve some space all up front. + // size of string + 2 for quotes + str.reserve(get_reserve_size()); + format(str); + } + + std::string as_utf8_string() const; + utf16string as_utf16_string() const; + + utility::string_t m_string; + + // There are significant performance gains that can be made by knowing whether + // or not a character that requires escaping is present. + bool m_has_escape_char; + static bool has_escape_chars(const _String& str); +}; + +template +_ASYNCRTIMP void append_escape_string(std::basic_string& str, const std::basic_string& escaped); + +void format_string(const utility::string_t& key, utility::string_t& str); + +#ifdef _WIN32 +void format_string(const utility::string_t& key, std::string& str); +#endif + +class _Object : public _Value +{ +public: + _Object(bool keep_order) : m_object(keep_order) {} + _Object(object::storage_type fields, bool keep_order) : m_object(std::move(fields), keep_order) {} + + virtual std::unique_ptr<_Value> _copy_value() { return utility::details::make_unique<_Object>(*this); } + + virtual json::object& as_object() { return m_object; } + + virtual const json::object& as_object() const { return m_object; } + + virtual json::value::value_type type() const { return json::value::Object; } + + virtual bool has_field(const utility::string_t&) const; + + virtual json::value& index(const utility::string_t& key); + + bool is_equal(const _Object* other) const + { + if (m_object.size() != other->m_object.size()) return false; + + return std::equal(std::begin(m_object), std::end(m_object), std::begin(other->m_object)); + } + + virtual void serialize_impl(std::string& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#endif + size_t size() const { return m_object.size(); } + +protected: + virtual void format(std::basic_string& str) const { format_impl(str); } +#ifdef _WIN32 + virtual void format(std::basic_string& str) const { format_impl(str); } +#endif + +private: + json::object m_object; + + template + friend class json::details::JSON_Parser; + + template + void format_impl(std::basic_string& str) const + { + str.push_back('{'); + if (!m_object.empty()) + { + auto lastElement = m_object.end() - 1; + for (auto iter = m_object.begin(); iter != lastElement; ++iter) + { + format_string(iter->first, str); + str.push_back(':'); + iter->second.format(str); + str.push_back(','); + } + format_string(lastElement->first, str); + str.push_back(':'); + lastElement->second.format(str); + } + str.push_back('}'); + } + + size_t get_reserve_size() const + { + // This is a heuristic we can tune more in the future: + // Basically size of string plus + // sum size of value if an object, array, or string. + size_t reserveSize = 2; // For brackets {} + for (auto iter = m_object.begin(); iter != m_object.end(); ++iter) + { + reserveSize += iter->first.length() + 2; // 2 for quotes + size_t valueSize = iter->second.size() * 20; // Multiply by each object/array element + if (valueSize == 0) + { + if (iter->second.type() == json::value::String) + { + valueSize = static_cast<_String*>(iter->second.m_value.get())->get_reserve_size(); + } + else + { + valueSize = 5; // true, false, or null + } + } + reserveSize += valueSize; + } + return reserveSize; + } +}; + +class _Array : public _Value +{ +public: + _Array() {} + _Array(array::size_type size) : m_array(size) {} + _Array(array::storage_type elements) : m_array(std::move(elements)) {} + + virtual std::unique_ptr<_Value> _copy_value() { return utility::details::make_unique<_Array>(*this); } + + virtual json::value::value_type type() const { return json::value::Array; } + + virtual json::array& as_array() { return m_array; } + virtual const json::array& as_array() const { return m_array; } + + virtual json::value& index(json::array::size_type index) { return m_array[index]; } + + bool is_equal(const _Array* other) const + { + if (m_array.size() != other->m_array.size()) return false; + + auto iterT = m_array.cbegin(); + auto iterO = other->m_array.cbegin(); + auto iterTe = m_array.cend(); + auto iterOe = other->m_array.cend(); + + for (; iterT != iterTe && iterO != iterOe; ++iterT, ++iterO) + { + if (*iterT != *iterO) return false; + } + + return true; + } + + virtual void serialize_impl(std::string& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#endif + size_t size() const { return m_array.size(); } + +protected: + virtual void format(std::basic_string& str) const { format_impl(str); } +#ifdef _WIN32 + virtual void format(std::basic_string& str) const { format_impl(str); } +#endif +private: + json::array m_array; + + template + friend class json::details::JSON_Parser; + + template + void format_impl(std::basic_string& str) const + { + str.push_back('['); + if (!m_array.m_elements.empty()) + { + auto lastElement = m_array.m_elements.end() - 1; + for (auto iter = m_array.m_elements.begin(); iter != lastElement; ++iter) + { + iter->format(str); + str.push_back(','); + } + lastElement->format(str); + } + str.push_back(']'); + } + + size_t get_reserve_size() const + { + // This is a heuristic we can tune more in the future: + // Basically sum size of each value if an object, array, or string by a multiplier. + size_t reserveSize = 2; // For brackets [] + for (auto iter = m_array.cbegin(); iter != m_array.cend(); ++iter) + { + size_t valueSize = iter->size() * 20; // Per each nested array/object + + if (valueSize == 0) valueSize = 5; // true, false, or null + + reserveSize += valueSize; + } + return reserveSize; + } +}; +} // namespace details + +/// +/// Gets the number of children of the value. +/// +/// The number of children. 0 for all non-composites. +inline size_t json::value::size() const { return m_value->size(); } + +/// +/// Test for the presence of a field. +/// +/// The name of the field +/// True if the field exists, false otherwise. +inline bool json::value::has_field(const utility::string_t& key) const { return m_value->has_field(key); } + +/// +/// Access a field of a JSON object. +/// +/// The name of the field +/// The value kept in the field; null if the field does not exist +inline json::value json::value::get(const utility::string_t& key) const { return m_value->get_field(key); } + +/// +/// Access an element of a JSON array. +/// +/// The index of an element in the JSON array +/// The value kept at the array index; null if outside the boundaries of the array +inline json::value json::value::get(size_t index) const { return m_value->get_element(index); } + +/// +/// A standard std::ostream operator to facilitate writing JSON values to streams. +/// +/// The output stream to write the JSON value to. +/// The JSON value to be written to the stream. +/// The output stream object +_ASYNCRTIMP utility::ostream_t& __cdecl operator<<(utility::ostream_t& os, const json::value& val); + +/// +/// A standard std::istream operator to facilitate reading JSON values from streams. +/// +/// The input stream to read the JSON value from. +/// The JSON value object read from the stream. +/// The input stream object. +_ASYNCRTIMP utility::istream_t& __cdecl operator>>(utility::istream_t& is, json::value& val); +} // namespace json +} // namespace web + +#endif diff --git a/deps/cpprestsdk/include/cpprest/uri.h b/deps/cpprestsdk/include/cpprest/uri.h new file mode 100644 index 00000000000..00f96e1488b --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/uri.h @@ -0,0 +1,21 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Protocol independent support for URIs. + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ +#pragma once + +#ifndef CASA_URI_H +#define CASA_URI_H + +#include "cpprest/base_uri.h" +#include "cpprest/uri_builder.h" + +#endif diff --git a/deps/cpprestsdk/include/cpprest/uri_builder.h b/deps/cpprestsdk/include/cpprest/uri_builder.h new file mode 100644 index 00000000000..8cc2a92231b --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/uri_builder.h @@ -0,0 +1,295 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Builder style class for creating URIs. + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#pragma once + +#include "cpprest/base_uri.h" +#include + +namespace web +{ +/// +/// Builder for constructing URIs incrementally. +/// +class uri_builder +{ +public: + /// + /// Creates a builder with an initially empty URI. + /// + uri_builder() = default; + + /// + /// Creates a builder with a existing URI object. + /// + /// Encoded string containing the URI. + uri_builder(const uri& uri_str) : m_uri(uri_str.m_components) {} + + /// + /// Get the scheme component of the URI as an encoded string. + /// + /// The URI scheme as a string. + const utility::string_t& scheme() const { return m_uri.m_scheme; } + + /// + /// Get the user information component of the URI as an encoded string. + /// + /// The URI user information as a string. + const utility::string_t& user_info() const { return m_uri.m_user_info; } + + /// + /// Get the host component of the URI as an encoded string. + /// + /// The URI host as a string. + const utility::string_t& host() const { return m_uri.m_host; } + + /// + /// Get the port component of the URI. Returns -1 if no port is specified. + /// + /// The URI port as an integer. + int port() const { return m_uri.m_port; } + + /// + /// Get the path component of the URI as an encoded string. + /// + /// The URI path as a string. + const utility::string_t& path() const { return m_uri.m_path; } + + /// + /// Get the query component of the URI as an encoded string. + /// + /// The URI query as a string. + const utility::string_t& query() const { return m_uri.m_query; } + + /// + /// Get the fragment component of the URI as an encoded string. + /// + /// The URI fragment as a string. + const utility::string_t& fragment() const { return m_uri.m_fragment; } + + /// + /// Set the scheme of the URI. + /// + /// Uri scheme. + /// A reference to this uri_builder to support chaining. + uri_builder& set_scheme(const utility::string_t& scheme) + { + m_uri.m_scheme = scheme; + return *this; + } + + /// + /// Set the user info component of the URI. + /// + /// User info as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder& set_user_info(const utility::string_t& user_info, bool do_encoding = false) + { + if (do_encoding) + { + m_uri.m_user_info = uri::encode_uri(user_info, uri::components::user_info); + } + else + { + m_uri.m_user_info = user_info; + } + + return *this; + } + + /// + /// Set the host component of the URI. + /// + /// Host as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder& set_host(const utility::string_t& host, bool do_encoding = false) + { + if (do_encoding) + { + m_uri.m_host = uri::encode_uri(host, uri::components::host); + } + else + { + m_uri.m_host = host; + } + + return *this; + } + + /// + /// Set the port component of the URI. + /// + /// Port as an integer. + /// A reference to this uri_builder to support chaining. + uri_builder& set_port(int port) + { + m_uri.m_port = port; + return *this; + } + + /// + /// Set the port component of the URI. + /// + /// Port as a string. + /// A reference to this uri_builder to support chaining. + /// When string can't be converted to an integer the port is left unchanged. + _ASYNCRTIMP uri_builder& set_port(const utility::string_t& port); + + /// + /// Set the path component of the URI. + /// + /// Path as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder& set_path(const utility::string_t& path, bool do_encoding = false) + { + if (do_encoding) + { + m_uri.m_path = uri::encode_uri(path, uri::components::path); + } + else + { + m_uri.m_path = path; + } + + return *this; + } + + /// + /// Set the query component of the URI. + /// + /// Query as a decoded string. + /// Specify whether apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder& set_query(const utility::string_t& query, bool do_encoding = false) + { + if (do_encoding) + { + m_uri.m_query = uri::encode_uri(query, uri::components::query); + } + else + { + m_uri.m_query = query; + } + + return *this; + } + + /// + /// Set the fragment component of the URI. + /// + /// Fragment as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder& set_fragment(const utility::string_t& fragment, bool do_encoding = false) + { + if (do_encoding) + { + m_uri.m_fragment = uri::encode_uri(fragment, uri::components::fragment); + } + else + { + m_uri.m_fragment = fragment; + } + + return *this; + } + + /// + /// Clears all components of the underlying URI in this uri_builder. + /// + void clear() { m_uri = details::uri_components(); } + + /// + /// Appends another path to the path of this uri_builder. + /// + /// Path to append as a already encoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + _ASYNCRTIMP uri_builder& append_path(const utility::string_t& path, bool do_encoding = false); + + /// + /// Appends the raw contents of the path argument to the path of this uri_builder with no separator de-duplication. + /// + /// + /// The path argument is appended after adding a '/' separator without regards to the contents of path. If an empty + /// string is provided, this function will immediately return without changes to the stored path value. For example: + /// if the current contents are "/abc" and path="/xyz", the result will be "/abc//xyz". + /// + /// Path to append as a already encoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + _ASYNCRTIMP uri_builder& append_path_raw(const utility::string_t& path, bool do_encoding = false); + + /// + /// Appends another query to the query of this uri_builder. + /// + /// Query to append as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + _ASYNCRTIMP uri_builder& append_query(const utility::string_t& query, bool do_encoding = false); + + /// + /// Appends an relative uri (Path, Query and fragment) at the end of the current uri. + /// + /// The relative uri to append. + /// A reference to this uri_builder to support chaining. + _ASYNCRTIMP uri_builder& append(const uri& relative_uri); + + /// + /// Appends another query to the query of this uri_builder, encoding it first. This overload is useful when building + /// a query segment of the form "element=10", where the right hand side of the query is stored as a type other than + /// a string, for instance, an integral type. + /// + /// The name portion of the query string + /// The value portion of the query string + /// A reference to this uri_builder to support chaining. + template + uri_builder& append_query(const utility::string_t& name, const T& value, bool do_encoding = true) + { + if (do_encoding) + append_query_encode_impl(name, utility::conversions::details::print_utf8string(value)); + else + append_query_no_encode_impl(name, utility::conversions::details::print_string(value)); + return *this; + } + + /// + /// Combine and validate the URI components into a encoded string. An exception will be thrown if the URI is + /// invalid. + /// + /// The created URI as a string. + _ASYNCRTIMP utility::string_t to_string() const; + + /// + /// Combine and validate the URI components into a URI class instance. An exception will be thrown if the URI is + /// invalid. + /// + /// The create URI as a URI class instance. + _ASYNCRTIMP uri to_uri() const; + + /// + /// Validate the generated URI from all existing components of this uri_builder. + /// + /// Whether the URI is valid. + _ASYNCRTIMP bool is_valid(); + +private: + _ASYNCRTIMP void append_query_encode_impl(const utility::string_t& name, const utf8string& value); + _ASYNCRTIMP void append_query_no_encode_impl(const utility::string_t& name, const utility::string_t& value); + + details::uri_components m_uri; +}; +} // namespace web diff --git a/deps/cpprestsdk/include/cpprest/version.h b/deps/cpprestsdk/include/cpprest/version.h new file mode 100644 index 00000000000..af1a6c7c1db --- /dev/null +++ b/deps/cpprestsdk/include/cpprest/version.h @@ -0,0 +1,10 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + */ +#define CPPREST_VERSION_MINOR 10 +#define CPPREST_VERSION_MAJOR 2 +#define CPPREST_VERSION_REVISION 13 + +#define CPPREST_VERSION (CPPREST_VERSION_MAJOR * 100000 + CPPREST_VERSION_MINOR * 100 + CPPREST_VERSION_REVISION) diff --git a/deps/cpprestsdk/license.txt b/deps/cpprestsdk/license.txt new file mode 100644 index 00000000000..9a6d567042b --- /dev/null +++ b/deps/cpprestsdk/license.txt @@ -0,0 +1,25 @@ +C++ REST SDK + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +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/deps/cpprestsdk/pch.cpp b/deps/cpprestsdk/pch.cpp new file mode 100644 index 00000000000..1d9f38c57d6 --- /dev/null +++ b/deps/cpprestsdk/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/deps/cpprestsdk/pch.h b/deps/cpprestsdk/pch.h new file mode 100644 index 00000000000..4b228b348ba --- /dev/null +++ b/deps/cpprestsdk/pch.h @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// cpprestsdk headers +#include "cpprest/details/basic_types.h" +#include "cpprest/details/cpprest_compat.h" +#include "cpprest/version.h" +// json +#include "cpprest/json.h" +// utilities +#include "cpprest/asyncrt_utils.h" +#include "cpprest/details/web_utilities.h" diff --git a/deps/cpprestsdk/src/json/json.cpp b/deps/cpprestsdk/src/json/json.cpp new file mode 100644 index 00000000000..2d9fcd65062 --- /dev/null +++ b/deps/cpprestsdk/src/json/json.cpp @@ -0,0 +1,475 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * HTTP Library: JSON parser and writer + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#include "pch.h" + +using namespace web; + +bool json::details::g_keep_json_object_unsorted = false; +void json::keep_object_element_order(bool keep_order) { json::details::g_keep_json_object_unsorted = keep_order; } + +utility::ostream_t& web::json::operator<<(utility::ostream_t& os, const web::json::value& val) +{ + val.serialize(os); + return os; +} + +utility::istream_t& web::json::operator>>(utility::istream_t& is, json::value& val) +{ + val = json::value::parse(is); + return is; +} + +web::json::value::value() + : m_value(utility::details::make_unique()) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Null) +#endif +{ +} + +web::json::value::value(int32_t value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Number) +#endif +{ +} + +web::json::value::value(uint32_t value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Number) +#endif +{ +} + +web::json::value::value(int64_t value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Number) +#endif +{ +} + +web::json::value::value(uint64_t value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Number) +#endif +{ +} + +web::json::value::value(double value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Number) +#endif +{ +} + +web::json::value::value(bool value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::Boolean) +#endif +{ +} + +web::json::value::value(utility::string_t value) + : m_value(utility::details::make_unique(std::move(value))) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::String) +#endif +{ +} + +web::json::value::value(utility::string_t value, bool has_escape_chars) + : m_value(utility::details::make_unique(std::move(value), has_escape_chars)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::String) +#endif +{ +} + +web::json::value::value(const utility::char_t* value) + : m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::String) +#endif +{ +} + +web::json::value::value(const utility::char_t* value, bool has_escape_chars) + : m_value(utility::details::make_unique(utility::string_t(value), has_escape_chars)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(value::String) +#endif +{ +} + +web::json::value::value(const value& other) + : m_value(other.m_value->_copy_value()) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , m_kind(other.m_kind) +#endif +{ +} + +web::json::value& web::json::value::operator=(const value& other) +{ + if (this != &other) + { + m_value = std::unique_ptr(other.m_value->_copy_value()); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = other.m_kind; +#endif + } + return *this; +} + +web::json::value::value(value&& other) CPPREST_NOEXCEPT : m_value(std::move(other.m_value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + m_kind(other.m_kind) +#endif +{ +} + +web::json::value& web::json::value::operator=(web::json::value&& other) CPPREST_NOEXCEPT +{ + if (this != &other) + { + m_value.swap(other.m_value); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = other.m_kind; +#endif + } + return *this; +} + +web::json::value web::json::value::null() { return web::json::value(); } + +web::json::value web::json::value::number(double value) { return web::json::value(value); } + +web::json::value web::json::value::number(int32_t value) { return web::json::value(value); } + +web::json::value web::json::value::number(uint32_t value) { return web::json::value(value); } + +web::json::value web::json::value::number(int64_t value) { return web::json::value(value); } + +web::json::value web::json::value::number(uint64_t value) { return web::json::value(value); } + +web::json::value web::json::value::boolean(bool value) { return web::json::value(value); } + +web::json::value web::json::value::string(utility::string_t value) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(value)); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::String +#endif + ); +} + +web::json::value web::json::value::string(utility::string_t value, bool has_escape_chars) +{ + std::unique_ptr ptr = + utility::details::make_unique(std::move(value), has_escape_chars); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::String +#endif + ); +} + +#ifdef _WIN32 +web::json::value web::json::value::string(const std::string& value) +{ + std::unique_ptr ptr = + utility::details::make_unique(utility::conversions::to_utf16string(value)); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::String +#endif + ); +} +#endif + +web::json::value web::json::value::object(bool keep_order) +{ + std::unique_ptr ptr = utility::details::make_unique(keep_order); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::Object +#endif + ); +} + +web::json::value web::json::value::object(std::vector> fields, bool keep_order) +{ + std::unique_ptr ptr = + utility::details::make_unique(std::move(fields), keep_order); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::Object +#endif + ); +} + +web::json::value web::json::value::array() +{ + std::unique_ptr ptr = utility::details::make_unique(); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::Array +#endif + ); +} + +web::json::value web::json::value::array(size_t size) +{ + std::unique_ptr ptr = utility::details::make_unique(size); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::Array +#endif + ); +} + +web::json::value web::json::value::array(std::vector elements) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(elements)); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + , + value::Array +#endif + ); +} + +const web::json::number& web::json::value::as_number() const { return m_value->as_number(); } + +double web::json::value::as_double() const { return m_value->as_double(); } + +int web::json::value::as_integer() const { return m_value->as_integer(); } + +bool web::json::value::as_bool() const { return m_value->as_bool(); } + +json::array& web::json::value::as_array() { return m_value->as_array(); } + +const json::array& web::json::value::as_array() const { return m_value->as_array(); } + +json::object& web::json::value::as_object() { return m_value->as_object(); } + +const json::object& web::json::value::as_object() const { return m_value->as_object(); } + +bool web::json::number::is_int32() const +{ + switch (m_type) + { + case signed_type: + return m_intval >= (std::numeric_limits::min)() && m_intval <= (std::numeric_limits::max)(); + case unsigned_type: return m_uintval <= (std::numeric_limits::max)(); + case double_type: + default: return false; + } +} + +bool web::json::number::is_uint32() const +{ + switch (m_type) + { + case signed_type: return m_intval >= 0 && m_intval <= (std::numeric_limits::max)(); + case unsigned_type: return m_uintval <= (std::numeric_limits::max)(); + case double_type: + default: return false; + } +} + +bool web::json::number::is_int64() const +{ + switch (m_type) + { + case signed_type: return true; + case unsigned_type: return m_uintval <= static_cast((std::numeric_limits::max)()); + case double_type: + default: return false; + } +} + +bool web::json::details::_String::has_escape_chars(const _String& str) +{ + return std::any_of(std::begin(str.m_string), std::end(str.m_string), [](utility::string_t::value_type const x) { + if (x <= 31) + { + return true; + } + if (x == '"') + { + return true; + } + if (x == '\\') + { + return true; + } + return false; + }); +} + +web::json::value::value_type json::value::type() const { return m_value->type(); } + +bool json::value::is_integer() const +{ + if (!is_number()) + { + return false; + } + return m_value->is_integer(); +} + +bool json::value::is_double() const +{ + if (!is_number()) + { + return false; + } + return m_value->is_double(); +} + +json::value& web::json::details::_Object::index(const utility::string_t& key) { return m_object[key]; } + +bool web::json::details::_Object::has_field(const utility::string_t& key) const +{ + return m_object.find(key) != m_object.end(); +} + +bool web::json::value::has_number_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_number(); +} + +bool web::json::value::has_integer_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_integer(); +} + +bool web::json::value::has_double_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_double(); +} + +bool web::json::value::has_boolean_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_boolean(); +} + +bool web::json::value::has_string_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_string(); +} + +bool web::json::value::has_array_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_array(); +} + +bool web::json::value::has_object_field(const utility::string_t& key) const +{ + return has_field(key) && at(key).is_object(); +} + +utility::string_t json::value::to_string() const +{ +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + return m_value->to_string(); +} + +bool json::value::operator==(const json::value& other) const +{ + if (this->m_value.get() == other.m_value.get()) return true; + if (this->type() != other.type()) return false; + + switch (this->type()) + { + case Null: return true; + case Number: return this->as_number() == other.as_number(); + case Boolean: return this->as_bool() == other.as_bool(); + case String: return this->as_string() == other.as_string(); + case Object: + return static_cast(this->m_value.get()) + ->is_equal(static_cast(other.m_value.get())); + case Array: + return static_cast(this->m_value.get()) + ->is_equal(static_cast(other.m_value.get())); + } + __assume(0); +} + +void web::json::value::erase(size_t index) { return this->as_array().erase(index); } + +void web::json::value::erase(const utility::string_t& key) { return this->as_object().erase(key); } + +// at() overloads +web::json::value& web::json::value::at(size_t index) { return this->as_array().at(index); } + +const web::json::value& web::json::value::at(size_t index) const { return this->as_array().at(index); } + +web::json::value& web::json::value::at(const utility::string_t& key) { return this->as_object().at(key); } + +const web::json::value& web::json::value::at(const utility::string_t& key) const { return this->as_object().at(key); } + +web::json::value& web::json::value::operator[](const utility::string_t& key) +{ + if (this->is_null()) + { + m_value.reset(new web::json::details::_Object(details::g_keep_json_object_unsorted)); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = value::Object; +#endif + } + return m_value->index(key); +} + +web::json::value& web::json::value::operator[](size_t index) +{ + if (this->is_null()) + { + m_value.reset(new web::json::details::_Array()); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = value::Array; +#endif + } + return m_value->index(index); +} + +// Remove once VS 2013 is no longer supported. +#if defined(_WIN32) && _MSC_VER < 1900 +static web::json::details::json_error_category_impl instance; +#endif +const web::json::details::json_error_category_impl& web::json::details::json_error_category() +{ +#if !defined(_WIN32) || _MSC_VER >= 1900 + static web::json::details::json_error_category_impl instance; +#endif + return instance; +} diff --git a/deps/cpprestsdk/src/json/json_parsing.cpp b/deps/cpprestsdk/src/json/json_parsing.cpp new file mode 100644 index 00000000000..1e7e8d332b6 --- /dev/null +++ b/deps/cpprestsdk/src/json/json_parsing.cpp @@ -0,0 +1,1279 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * HTTP Library: JSON parser + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#include "pch.h" + +#include + +#if defined(_MSC_VER) +#pragma warning(disable : 4127) // allow expressions like while(true) pass +#endif +using namespace web; +using namespace web::json; +using namespace utility; +using namespace utility::conversions; + +std::array _hexval = { + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}; + +namespace web +{ +namespace json +{ +namespace details +{ +// +// JSON Parsing +// + +template +#if defined(_WIN32) +__declspec(noreturn) +#else + __attribute__((noreturn)) +#endif + void CreateException(const Token& tk, const utility::string_t& message) +{ + std::string str("* Line "); + str += std::to_string(tk.start.m_line); + str += ", Column "; + str += std::to_string(tk.start.m_column); + str += " Syntax error: "; + str += utility::conversions::to_utf8string(message); + throw web::json::json_exception(std::move(str)); +} + +template +void SetErrorCode(Token& tk, json_error jsonErrorCode) +{ + tk.m_error = std::error_code(jsonErrorCode, json_error_category()); +} + +template +class JSON_Parser +{ +public: + JSON_Parser() : m_currentLine(1), m_currentColumn(1), m_currentParsingDepth(0) {} + + struct Location + { + size_t m_line; + size_t m_column; + }; + + struct Token + { + enum Kind + { + TKN_EOF, + + TKN_OpenBrace, + TKN_CloseBrace, + TKN_OpenBracket, + TKN_CloseBracket, + TKN_Comma, + TKN_Colon, + TKN_StringLiteral, + TKN_NumberLiteral, + TKN_IntegerLiteral, + TKN_BooleanLiteral, + TKN_NullLiteral, + TKN_Comment + }; + + Token() : kind(TKN_EOF) {} + + Kind kind; + std::basic_string string_val; + + typename JSON_Parser::Location start; + + union { + double double_val; + int64_t int64_val; + uint64_t uint64_val; + bool boolean_val; + bool has_unescape_symbol; + }; + + bool signed_number; + + std::error_code m_error; + }; + + void GetNextToken(Token&); + + web::json::value ParseValue(typename JSON_Parser::Token& first) + { +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + +#ifdef ENABLE_JSON_VALUE_VISUALIZER + auto _value = _ParseValue(first); + auto type = _value->type(); + return web::json::value(std::move(_value), type); +#else + return web::json::value(_ParseValue(first)); +#endif + } + +protected: + typedef typename std::char_traits::int_type int_type; + virtual int_type NextCharacter() = 0; + virtual int_type PeekCharacter() = 0; + + virtual bool CompleteComment(Token& token); + virtual bool CompleteStringLiteral(Token& token); + int convert_unicode_to_code_point(); + bool handle_unescape_char(Token& token); + +private: + bool CompleteNumberLiteral(CharType first, Token& token); + bool ParseInt64(CharType first, uint64_t& value); + bool CompleteKeywordTrue(Token& token); + bool CompleteKeywordFalse(Token& token); + bool CompleteKeywordNull(Token& token); + std::unique_ptr _ParseValue(typename JSON_Parser::Token& first); + std::unique_ptr _ParseObject(typename JSON_Parser::Token& tkn); + std::unique_ptr _ParseArray(typename JSON_Parser::Token& tkn); + + JSON_Parser& operator=(const JSON_Parser&); + + int_type EatWhitespace(); + + void CreateToken(typename JSON_Parser::Token& tk, typename Token::Kind kind, Location& start) + { + tk.kind = kind; + tk.start = start; + tk.string_val.clear(); + } + + void CreateToken(typename JSON_Parser::Token& tk, typename Token::Kind kind) + { + tk.kind = kind; + tk.start.m_line = m_currentLine; + tk.start.m_column = m_currentColumn; + tk.string_val.clear(); + } + +protected: + size_t m_currentLine; + size_t m_currentColumn; + size_t m_currentParsingDepth; + +// The DEBUG macro is defined in XCode but we don't in our CMakeList +// so for now we will keep the same on debug and release. In the future +// this can be increase on release if necessary. +#if defined(__APPLE__) + static const size_t maxParsingDepth = 32; +#else + static const size_t maxParsingDepth = 128; +#endif +}; + +// Replace with template alias once VS 2012 support is removed. +template +typename std::char_traits::int_type eof() +{ + return std::char_traits::eof(); +} + +template +class JSON_StreamParser : public JSON_Parser +{ +public: + JSON_StreamParser(std::basic_istream& stream) : m_streambuf(stream.rdbuf()) {} + +protected: + virtual typename JSON_Parser::int_type NextCharacter(); + virtual typename JSON_Parser::int_type PeekCharacter(); + +private: + typename std::basic_streambuf>* m_streambuf; +}; + +template +class JSON_StringParser : public JSON_Parser +{ +public: + JSON_StringParser(const std::basic_string& string) : m_position(&string[0]) + { + m_startpos = m_position; + m_endpos = m_position + string.size(); + } + +protected: + virtual typename JSON_Parser::int_type NextCharacter(); + virtual typename JSON_Parser::int_type PeekCharacter(); + + virtual bool CompleteComment(typename JSON_Parser::Token& token); + virtual bool CompleteStringLiteral(typename JSON_Parser::Token& token); + +private: + bool finish_parsing_string_with_unescape_char(typename JSON_Parser::Token& token); + const CharType* m_position; + const CharType* m_startpos; + const CharType* m_endpos; +}; + +template +typename JSON_Parser::int_type JSON_StreamParser::NextCharacter() +{ + auto ch = m_streambuf->sbumpc(); + + if (ch == '\n') + { + this->m_currentLine += 1; + this->m_currentColumn = 0; + } + else + { + this->m_currentColumn += 1; + } + + return ch; +} + +template +typename JSON_Parser::int_type JSON_StreamParser::PeekCharacter() +{ + return m_streambuf->sgetc(); +} + +template +typename JSON_Parser::int_type JSON_StringParser::NextCharacter() +{ + if (m_position == m_endpos) return eof(); + + CharType ch = *m_position; + m_position += 1; + + if (ch == '\n') + { + this->m_currentLine += 1; + this->m_currentColumn = 0; + } + else + { + this->m_currentColumn += 1; + } + + return ch; +} + +template +typename JSON_Parser::int_type JSON_StringParser::PeekCharacter() +{ + if (m_position == m_endpos) return eof(); + + return *m_position; +} + +// +// Consume whitespace characters and return the first non-space character or EOF +// +template +typename JSON_Parser::int_type JSON_Parser::EatWhitespace() +{ + auto ch = NextCharacter(); + + while (ch != eof() && iswspace(static_cast(ch))) + { + ch = NextCharacter(); + } + + return ch; +} + +template +bool JSON_Parser::CompleteKeywordTrue(Token& token) +{ + if (NextCharacter() != 'r') return false; + if (NextCharacter() != 'u') return false; + if (NextCharacter() != 'e') return false; + token.kind = Token::TKN_BooleanLiteral; + token.boolean_val = true; + return true; +} + +template +bool JSON_Parser::CompleteKeywordFalse(Token& token) +{ + if (NextCharacter() != 'a') return false; + if (NextCharacter() != 'l') return false; + if (NextCharacter() != 's') return false; + if (NextCharacter() != 'e') return false; + token.kind = Token::TKN_BooleanLiteral; + token.boolean_val = false; + return true; +} + +template +bool JSON_Parser::CompleteKeywordNull(Token& token) +{ + if (NextCharacter() != 'u') return false; + if (NextCharacter() != 'l') return false; + if (NextCharacter() != 'l') return false; + token.kind = Token::TKN_NullLiteral; + return true; +} + +// Returns false only on overflow +template +inline bool JSON_Parser::ParseInt64(CharType first, uint64_t& value) +{ + value = first - '0'; + auto ch = PeekCharacter(); + while (ch >= '0' && ch <= '9') + { + unsigned int next_digit = (unsigned int)(ch - '0'); + if (value > (ULLONG_MAX / 10) || (value == ULLONG_MAX / 10 && next_digit > ULLONG_MAX % 10)) return false; + + NextCharacter(); + + value *= 10; + value += next_digit; + ch = PeekCharacter(); + } + return true; +} + +// This namespace hides the x-plat helper functions +namespace +{ +#if defined(_WIN32) +static int print_llu(char* ptr, size_t n, uint64_t val64) +{ + return _snprintf_s_l(ptr, n, _TRUNCATE, "%I64u", utility::details::scoped_c_thread_locale::c_locale(), val64); +} + +static int print_llu(wchar_t* ptr, size_t n, uint64_t val64) +{ + return _snwprintf_s_l(ptr, n, _TRUNCATE, L"%I64u", utility::details::scoped_c_thread_locale::c_locale(), val64); +} +static double anystod(const char* str) +{ + return _strtod_l(str, nullptr, utility::details::scoped_c_thread_locale::c_locale()); +} +static double anystod(const wchar_t* str) +{ + return _wcstod_l(str, nullptr, utility::details::scoped_c_thread_locale::c_locale()); +} +#else +static int __attribute__((__unused__)) print_llu(char* ptr, size_t n, unsigned long long val64) +{ + return snprintf(ptr, n, "%llu", val64); +} +static int __attribute__((__unused__)) print_llu(char* ptr, size_t n, unsigned long val64) +{ + return snprintf(ptr, n, "%lu", val64); +} +static double __attribute__((__unused__)) anystod(const char* str) { return strtod(str, nullptr); } +static double __attribute__((__unused__)) anystod(const wchar_t* str) { return wcstod(str, nullptr); } +#endif +} // namespace + +template +bool JSON_Parser::CompleteNumberLiteral(CharType first, Token& token) +{ + bool minus_sign; + + if (first == '-') + { + minus_sign = true; + + // Safe to cast because the check after this if/else statement will cover EOF. + first = static_cast(NextCharacter()); + } + else + { + minus_sign = false; + } + + if (first < '0' || first > '9') return false; + + auto ch = PeekCharacter(); + + // Check for two (or more) zeros at the beginning + if (first == '0' && ch == '0') return false; + + // Parse the number assuming its integer + uint64_t val64; + bool complete = ParseInt64(first, val64); + + ch = PeekCharacter(); + if (complete && ch != '.' && ch != 'E' && ch != 'e') + { + if (minus_sign) + { + if (val64 > static_cast(1) << 63) + { + // It is negative and cannot be represented in int64, so we resort to double + token.double_val = 0 - static_cast(val64); + token.signed_number = true; + token.kind = JSON_Parser::Token::TKN_NumberLiteral; + return true; + } + + // It is negative, but fits into int64 + token.int64_val = 0 - static_cast(val64); + token.kind = JSON_Parser::Token::TKN_IntegerLiteral; + token.signed_number = true; + return true; + } + + // It is positive so we use unsigned int64 + token.uint64_val = val64; + token.kind = JSON_Parser::Token::TKN_IntegerLiteral; + token.signed_number = false; + return true; + } + + // Magic number 5 leaves room for decimal point, null terminator, etc (in most cases) + ::std::vector buf(::std::numeric_limits::digits10 + 5); + int count = print_llu(buf.data(), buf.size(), val64); + _ASSERTE(count >= 0); + _ASSERTE((size_t)count < buf.size()); + // Resize to cut off the null terminator + buf.resize(count); + + bool decimal = false; + + while (ch != eof()) + { + // Digit encountered? + if (ch >= '0' && ch <= '9') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // Decimal dot? + else if (ch == '.') + { + if (decimal) return false; + + decimal = true; + buf.push_back(static_cast(ch)); + + NextCharacter(); + ch = PeekCharacter(); + + // Check that the following char is a digit + if (ch < '0' || ch > '9') return false; + + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // Exponent? + else if (ch == 'E' || ch == 'e') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + + // Check for the exponent sign + if (ch == '+') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + else if (ch == '-') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // First number of the exponent + if (ch >= '0' && ch <= '9') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + else + return false; + + // The rest of the exponent + while (ch >= '0' && ch <= '9') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // The peeked character is not a number, so we can break from the loop and construct the number + break; + } + else + { + // Not expected number character? + break; + } + }; + + buf.push_back('\0'); + token.double_val = anystod(buf.data()); + if (minus_sign) + { + token.double_val = -token.double_val; + } + token.kind = (JSON_Parser::Token::TKN_NumberLiteral); + + return true; +} + +template +bool JSON_Parser::CompleteComment(Token& token) +{ + // We already found a '/' character as the first of a token -- what kind of comment is it? + + auto ch = NextCharacter(); + + if (ch == eof() || (ch != '/' && ch != '*')) return false; + + if (ch == '/') + { + // Line comment -- look for a newline or EOF to terminate. + + ch = NextCharacter(); + + while (ch != eof() && ch != '\n') + { + ch = NextCharacter(); + } + } + else + { + // Block comment -- look for a terminating "*/" sequence. + + ch = NextCharacter(); + + while (true) + { + if (ch == eof()) return false; + + if (ch == '*') + { + auto ch1 = PeekCharacter(); + + if (ch1 == eof()) return false; + + if (ch1 == '/') + { + // Consume the character + NextCharacter(); + break; + } + + ch = ch1; + } + + ch = NextCharacter(); + } + } + + token.kind = Token::TKN_Comment; + + return true; +} + +template +bool JSON_StringParser::CompleteComment(typename JSON_Parser::Token& token) +{ + // This function is specialized for the string parser, since we can be slightly more + // efficient in copying data from the input to the token: do a memcpy() rather than + // one character at a time. + + auto ch = JSON_StringParser::NextCharacter(); + + if (ch == eof() || (ch != '/' && ch != '*')) return false; + + if (ch == '/') + { + // Line comment -- look for a newline or EOF to terminate. + + ch = JSON_StringParser::NextCharacter(); + + while (ch != eof() && ch != '\n') + { + ch = JSON_StringParser::NextCharacter(); + } + } + else + { + // Block comment -- look for a terminating "*/" sequence. + + ch = JSON_StringParser::NextCharacter(); + + while (true) + { + if (ch == eof()) return false; + + if (ch == '*') + { + ch = JSON_StringParser::PeekCharacter(); + + if (ch == eof()) return false; + + if (ch == '/') + { + // Consume the character + JSON_StringParser::NextCharacter(); + break; + } + } + + ch = JSON_StringParser::NextCharacter(); + } + } + + token.kind = JSON_Parser::Token::TKN_Comment; + + return true; +} + +void convert_append_unicode_code_unit(JSON_Parser::Token& token, utf16string value) +{ + token.string_val.append(value); +} +void convert_append_unicode_code_unit(JSON_Parser::Token& token, utf16string value) +{ + token.string_val.append(::utility::conversions::utf16_to_utf8(value)); +} +void convert_append_unicode_code_unit(JSON_Parser::Token& token, utf16char value) +{ + token.string_val.push_back(value); +} +void convert_append_unicode_code_unit(JSON_Parser::Token& token, utf16char value) +{ + utf16string utf16(reinterpret_cast(&value), 1); + token.string_val.append(::utility::conversions::utf16_to_utf8(utf16)); +} + +template +int JSON_Parser::convert_unicode_to_code_point() +{ + // A four-hexdigit Unicode character. + // Transform into a 16 bit code point. + int decoded = 0; + for (int i = 0; i < 4; ++i) + { + auto ch = NextCharacter(); + int ch_int = static_cast(ch); + if (ch_int < 0 || ch_int > 127) return -1; +#ifdef _WIN32 + const int isxdigitResult = _isxdigit_l(ch_int, utility::details::scoped_c_thread_locale::c_locale()); +#else + const int isxdigitResult = isxdigit(ch_int); +#endif + if (!isxdigitResult) return -1; + + int val = _hexval[static_cast(ch_int)]; + + _ASSERTE(val != -1); + + // Add the input char to the decoded number + decoded |= (val << (4 * (3 - i))); + } + return decoded; +} + +#define H_SURROGATE_START 0xD800 +#define H_SURROGATE_END 0xDBFF + +template +inline bool JSON_Parser::handle_unescape_char(Token& token) +{ + token.has_unescape_symbol = true; + + // This function converts unescaped character pairs (e.g. "\t") into their ASCII or Unicode representations (e.g. + // tab sign) Also it handles \u + 4 hexadecimal digits + auto ch = NextCharacter(); + switch (ch) + { + case '\"': token.string_val.push_back('\"'); return true; + case '\\': token.string_val.push_back('\\'); return true; + case '/': token.string_val.push_back('/'); return true; + case 'b': token.string_val.push_back('\b'); return true; + case 'f': token.string_val.push_back('\f'); return true; + case 'r': token.string_val.push_back('\r'); return true; + case 'n': token.string_val.push_back('\n'); return true; + case 't': token.string_val.push_back('\t'); return true; + case 'u': + { + int decoded = convert_unicode_to_code_point(); + if (decoded == -1) + { + return false; + } + + // handle multi-block characters that start with a high-surrogate + if (decoded >= H_SURROGATE_START && decoded <= H_SURROGATE_END) + { + // skip escape character '\u' + if (NextCharacter() != '\\' || NextCharacter() != 'u') + { + return false; + } + int decoded2 = convert_unicode_to_code_point(); + + if (decoded2 == -1) + { + return false; + } + + utf16string compoundUTF16 = {static_cast(decoded), static_cast(decoded2)}; + convert_append_unicode_code_unit(token, compoundUTF16); + + return true; + } + + // Construct the character based on the decoded number + convert_append_unicode_code_unit(token, static_cast(decoded)); + + return true; + } + default: return false; + } +} + +template +bool JSON_Parser::CompleteStringLiteral(Token& token) +{ + token.has_unescape_symbol = false; + auto ch = NextCharacter(); + while (ch != '"') + { + if (ch == '\\') + { + handle_unescape_char(token); + } + else if (ch >= CharType(0x0) && ch < CharType(0x20)) + { + return false; + } + else + { + if (ch == eof()) return false; + + token.string_val.push_back(static_cast(ch)); + } + ch = NextCharacter(); + } + + if (ch == '"') + { + token.kind = Token::TKN_StringLiteral; + } + else + { + return false; + } + + return true; +} + +template +bool JSON_StringParser::CompleteStringLiteral(typename JSON_Parser::Token& token) +{ + // This function is specialized for the string parser, since we can be slightly more + // efficient in copying data from the input to the token: do a memcpy() rather than + // one character at a time. + + auto start = m_position; + token.has_unescape_symbol = false; + + auto ch = JSON_StringParser::NextCharacter(); + + while (ch != '"') + { + if (ch == eof()) return false; + + if (ch == '\\') + { + const size_t numChars = m_position - start - 1; + const size_t prevSize = token.string_val.size(); + token.string_val.resize(prevSize + numChars); + memcpy(const_cast(token.string_val.c_str() + prevSize), start, numChars * sizeof(CharType)); + + if (!JSON_StringParser::handle_unescape_char(token)) + { + return false; + } + + // Reset start position and continue. + start = m_position; + } + else if (ch >= CharType(0x0) && ch < CharType(0x20)) + { + return false; + } + + ch = JSON_StringParser::NextCharacter(); + } + + const size_t numChars = m_position - start - 1; + const size_t prevSize = token.string_val.size(); + token.string_val.resize(prevSize + numChars); + memcpy(const_cast(token.string_val.c_str() + prevSize), start, numChars * sizeof(CharType)); + + token.kind = JSON_Parser::Token::TKN_StringLiteral; + + return true; +} + +template +void JSON_Parser::GetNextToken(typename JSON_Parser::Token& result) +{ +try_again: + auto ch = EatWhitespace(); + + CreateToken(result, Token::TKN_EOF); + + if (ch == eof()) return; + + switch (ch) + { + case '{': + case '[': + { + if (++m_currentParsingDepth > JSON_Parser::maxParsingDepth) + { + SetErrorCode(result, json_error::nesting); + break; + } + + typename JSON_Parser::Token::Kind tk = ch == '{' ? Token::TKN_OpenBrace : Token::TKN_OpenBracket; + CreateToken(result, tk, result.start); + break; + } + case '}': + case ']': + { + if ((signed int)(--m_currentParsingDepth) < 0) + { + SetErrorCode(result, json_error::mismatched_brances); + break; + } + + typename JSON_Parser::Token::Kind tk = + ch == '}' ? Token::TKN_CloseBrace : Token::TKN_CloseBracket; + CreateToken(result, tk, result.start); + break; + } + case ',': CreateToken(result, Token::TKN_Comma, result.start); break; + + case ':': CreateToken(result, Token::TKN_Colon, result.start); break; + + case 't': + if (!CompleteKeywordTrue(result)) + { + SetErrorCode(result, json_error::malformed_literal); + } + break; + case 'f': + if (!CompleteKeywordFalse(result)) + { + SetErrorCode(result, json_error::malformed_literal); + } + break; + case 'n': + if (!CompleteKeywordNull(result)) + { + SetErrorCode(result, json_error::malformed_literal); + } + break; + case '/': + if (!CompleteComment(result)) + { + SetErrorCode(result, json_error::malformed_comment); + break; + } + // For now, we're ignoring comments. + goto try_again; + case '"': + if (!CompleteStringLiteral(result)) + { + SetErrorCode(result, json_error::malformed_string_literal); + } + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!CompleteNumberLiteral(static_cast(ch), result)) + { + SetErrorCode(result, json_error::malformed_numeric_literal); + } + break; + default: SetErrorCode(result, json_error::malformed_token); break; + } +} + +template +std::unique_ptr JSON_Parser::_ParseObject( + typename JSON_Parser::Token& tkn) +{ + auto obj = utility::details::make_unique(g_keep_json_object_unsorted); + auto& elems = obj->m_object.m_elements; + + GetNextToken(tkn); + if (tkn.m_error) goto error; + + if (tkn.kind != JSON_Parser::Token::TKN_CloseBrace) + { + while (true) + { + // State 1: New field or end of object, looking for field name or closing brace + std::basic_string fieldName; + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_StringLiteral: fieldName = std::move(tkn.string_val); break; + default: goto error; + } + + GetNextToken(tkn); + if (tkn.m_error) goto error; + + // State 2: Looking for a colon. + if (tkn.kind != JSON_Parser::Token::TKN_Colon) goto done; + + GetNextToken(tkn); + if (tkn.m_error) goto error; + + // State 3: Looking for an expression. +#ifdef ENABLE_JSON_VALUE_VISUALIZER + auto fieldValue = _ParseValue(tkn); + auto type = fieldValue->type(); + elems.emplace_back(utility::conversions::to_string_t(std::move(fieldName)), + json::value(std::move(fieldValue), type)); +#else + elems.emplace_back(utility::conversions::to_string_t(std::move(fieldName)), json::value(_ParseValue(tkn))); +#endif + if (tkn.m_error) goto error; + + // State 4: Looking for a comma or a closing brace + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_Comma: + GetNextToken(tkn); + if (tkn.m_error) goto error; + break; + case JSON_Parser::Token::TKN_CloseBrace: goto done; + default: goto error; + } + } + } + +done: + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + + if (!g_keep_json_object_unsorted) + { + ::std::sort(elems.begin(), elems.end(), json::object::compare_pairs); + } + + return std::unique_ptr(obj.release()); + +error: + if (!tkn.m_error) + { + SetErrorCode(tkn, json_error::malformed_object_literal); + } + return utility::details::make_unique(); +} + +template +std::unique_ptr JSON_Parser::_ParseArray( + typename JSON_Parser::Token& tkn) +{ + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + + auto result = utility::details::make_unique(); + + if (tkn.kind != JSON_Parser::Token::TKN_CloseBracket) + { + while (true) + { + // State 1: Looking for an expression. + result->m_array.m_elements.emplace_back(ParseValue(tkn)); + if (tkn.m_error) return utility::details::make_unique(); + + // State 4: Looking for a comma or a closing bracket + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_Comma: + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + break; + case JSON_Parser::Token::TKN_CloseBracket: + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(result); + default: + SetErrorCode(tkn, json_error::malformed_array_literal); + return utility::details::make_unique(); + } + } + } + + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + + return std::unique_ptr(result.release()); +} + +template +std::unique_ptr JSON_Parser::_ParseValue( + typename JSON_Parser::Token& tkn) +{ + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_OpenBrace: + { + return _ParseObject(tkn); + } + case JSON_Parser::Token::TKN_OpenBracket: + { + return _ParseArray(tkn); + } + case JSON_Parser::Token::TKN_StringLiteral: + { + auto value = utility::details::make_unique(std::move(tkn.string_val), + tkn.has_unescape_symbol); + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_IntegerLiteral: + { + std::unique_ptr value; + if (tkn.signed_number) + value = utility::details::make_unique(tkn.int64_val); + else + value = utility::details::make_unique(tkn.uint64_val); + + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_NumberLiteral: + { + auto value = utility::details::make_unique(tkn.double_val); + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_BooleanLiteral: + { + auto value = utility::details::make_unique(tkn.boolean_val); + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_NullLiteral: + { + GetNextToken(tkn); + // Returning a null value whether or not an error occurred. + return utility::details::make_unique(); + } + default: + { + SetErrorCode(tkn, json_error::malformed_token); + return utility::details::make_unique(); + } + } +} + +} // namespace details +} // namespace json +} // namespace web + +static web::json::value _parse_stream(utility::istream_t& stream) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + + auto value = parser.ParseValue(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + else if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::CreateException(tkn, + _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +static web::json::value _parse_stream(utility::istream_t& stream, std::error_code& error) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + error = std::move(tkn.m_error); + return web::json::value(); + } + + auto returnObject = parser.ParseValue(tkn); + if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::SetErrorCode(tkn, web::json::details::json_error::left_over_character_in_stream); + } + + error = std::move(tkn.m_error); + return returnObject; +} + +#ifdef _WIN32 +static web::json::value _parse_narrow_stream(std::istream& stream) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_StreamParser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + + auto value = parser.ParseValue(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + else if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::CreateException(tkn, + _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +static web::json::value _parse_narrow_stream(std::istream& stream, std::error_code& error) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_StreamParser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + error = std::move(tkn.m_error); + return web::json::value(); + } + + auto returnObject = parser.ParseValue(tkn); + if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + returnObject = web::json::value(); + web::json::details::SetErrorCode(tkn, web::json::details::json_error::left_over_character_in_stream); + } + + error = std::move(tkn.m_error); + return returnObject; +} +#endif + +web::json::value web::json::value::parse(const utility::string_t& str) +{ + web::json::details::JSON_StringParser parser(str); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + + auto value = parser.ParseValue(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + else if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::CreateException(tkn, + _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +web::json::value web::json::value::parse(const utility::string_t& str, std::error_code& error) +{ + web::json::details::JSON_StringParser parser(str); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + error = std::move(tkn.m_error); + return web::json::value(); + } + + auto returnObject = parser.ParseValue(tkn); + if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + returnObject = web::json::value(); + web::json::details::SetErrorCode(tkn, web::json::details::json_error::left_over_character_in_stream); + } + + error = std::move(tkn.m_error); + return returnObject; +} + +web::json::value web::json::value::parse(utility::istream_t& stream) { return _parse_stream(stream); } + +web::json::value web::json::value::parse(utility::istream_t& stream, std::error_code& error) +{ + return _parse_stream(stream, error); +} + +#ifdef _WIN32 +web::json::value web::json::value::parse(std::istream& stream) { return _parse_narrow_stream(stream); } + +web::json::value web::json::value::parse(std::istream& stream, std::error_code& error) +{ + return _parse_narrow_stream(stream, error); +} +#endif diff --git a/deps/cpprestsdk/src/json/json_serialization.cpp b/deps/cpprestsdk/src/json/json_serialization.cpp new file mode 100644 index 00000000000..6ff2e37d748 --- /dev/null +++ b/deps/cpprestsdk/src/json/json_serialization.cpp @@ -0,0 +1,254 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * HTTP Library: JSON parser and writer + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#include "pch.h" + +#include + +#ifndef _WIN32 +#define __STDC_FORMAT_MACROS +#include +#endif + +using namespace web; +using namespace web::json; +using namespace utility; +using namespace utility::conversions; + +// +// JSON Serialization +// + +#ifdef _WIN32 +void web::json::value::serialize(std::ostream& stream) const +{ + // This has better performance than writing directly to stream. + std::string str; + m_value->serialize_impl(str); + stream << str; +} +void web::json::value::format(std::basic_string& string) const { m_value->format(string); } +#endif + +void web::json::value::serialize(utility::ostream_t& stream) const +{ +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + + // This has better performance than writing directly to stream. + utility::string_t str; + m_value->serialize_impl(str); + stream << str; +} + +void web::json::value::format(std::basic_string& string) const { m_value->format(string); } + +template +void web::json::details::append_escape_string(std::basic_string& str, + const std::basic_string& escaped) +{ + for (const auto& ch : escaped) + { + switch (ch) + { + case '\"': + str += '\\'; + str += '\"'; + break; + case '\\': + str += '\\'; + str += '\\'; + break; + case '\b': + str += '\\'; + str += 'b'; + break; + case '\f': + str += '\\'; + str += 'f'; + break; + case '\r': + str += '\\'; + str += 'r'; + break; + case '\n': + str += '\\'; + str += 'n'; + break; + case '\t': + str += '\\'; + str += 't'; + break; + default: + + // If a control character then must unicode escaped. + if (ch >= 0 && ch <= 0x1F) + { + static const std::array intToHex = { + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}}; + str += '\\'; + str += 'u'; + str += '0'; + str += '0'; + str += intToHex[(ch & 0xF0) >> 4]; + str += intToHex[ch & 0x0F]; + } + else + { + str += ch; + } + } + } +} + +void web::json::details::format_string(const utility::string_t& key, utility::string_t& str) +{ + str.push_back('"'); + append_escape_string(str, key); + str.push_back('"'); +} + +#ifdef _WIN32 +void web::json::details::format_string(const utility::string_t& key, std::string& str) +{ + str.push_back('"'); + append_escape_string(str, utility::conversions::to_utf8string(key)); + str.push_back('"'); +} +#endif + +void web::json::details::_String::format(std::basic_string& str) const +{ + str.push_back('"'); + + if (m_has_escape_char) + { + append_escape_string(str, utility::conversions::to_utf8string(m_string)); + } + else + { + str.append(utility::conversions::to_utf8string(m_string)); + } + + str.push_back('"'); +} + +void web::json::details::_Number::format(std::basic_string& stream) const +{ + if (m_number.m_type != number::type::double_type) + { + // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator. + const size_t tempSize = std::numeric_limits::digits10 + 3; + char tempBuffer[tempSize]; + +#ifdef _WIN32 + // This can be improved performance-wise if we implement our own routine + if (m_number.m_type == number::type::signed_type) + _i64toa_s(m_number.m_intval, tempBuffer, tempSize, 10); + else + _ui64toa_s(m_number.m_uintval, tempBuffer, tempSize, 10); + + const auto numChars = strnlen_s(tempBuffer, tempSize); +#else + int numChars; + if (m_number.m_type == number::type::signed_type) + numChars = snprintf(tempBuffer, tempSize, "%" PRId64, m_number.m_intval); + else + numChars = snprintf(tempBuffer, tempSize, "%" PRIu64, m_number.m_uintval); +#endif + stream.append(tempBuffer, numChars); + } + else + { + // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null + // terminator + const size_t tempSize = std::numeric_limits::digits10 + 10; + char tempBuffer[tempSize]; +#ifdef _WIN32 + const auto numChars = _sprintf_s_l(tempBuffer, + tempSize, + "%.*g", + utility::details::scoped_c_thread_locale::c_locale(), + std::numeric_limits::digits10 + 2, + m_number.m_value); +#else + const auto numChars = + snprintf(tempBuffer, tempSize, "%.*g", std::numeric_limits::digits10 + 2, m_number.m_value); +#endif + stream.append(tempBuffer, numChars); + } +} + +#ifdef _WIN32 + +void web::json::details::_String::format(std::basic_string& str) const +{ + str.push_back(L'"'); + + if (m_has_escape_char) + { + append_escape_string(str, m_string); + } + else + { + str.append(m_string); + } + + str.push_back(L'"'); +} + +void web::json::details::_Number::format(std::basic_string& stream) const +{ + if (m_number.m_type != number::type::double_type) + { + // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator. + const size_t tempSize = std::numeric_limits::digits10 + 3; + wchar_t tempBuffer[tempSize]; + + if (m_number.m_type == number::type::signed_type) + _i64tow_s(m_number.m_intval, tempBuffer, tempSize, 10); + else + _ui64tow_s(m_number.m_uintval, tempBuffer, tempSize, 10); + + stream.append(tempBuffer, wcsnlen_s(tempBuffer, tempSize)); + } + else + { + // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null + // terminator + const size_t tempSize = std::numeric_limits::digits10 + 10; + wchar_t tempBuffer[tempSize]; + const int numChars = _swprintf_s_l(tempBuffer, + tempSize, + L"%.*g", + utility::details::scoped_c_thread_locale::c_locale(), + std::numeric_limits::digits10 + 2, + m_number.m_value); + stream.append(tempBuffer, numChars); + } +} + +#endif + +const utility::string_t& web::json::details::_String::as_string() const { return m_string; } + +const utility::string_t& web::json::value::as_string() const { return m_value->as_string(); } + +utility::string_t json::value::serialize() const +{ +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + return m_value->to_string(); +} diff --git a/deps/cpprestsdk/src/utilities/asyncrt_utils.cpp b/deps/cpprestsdk/src/utilities/asyncrt_utils.cpp new file mode 100644 index 00000000000..b9b03d82997 --- /dev/null +++ b/deps/cpprestsdk/src/utilities/asyncrt_utils.cpp @@ -0,0 +1,1490 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Utilities + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#include "pch.h" + +#include +#include +#include +#include +#include + +using namespace web; +using namespace utility; +using namespace utility::conversions; + +namespace +{ +struct to_lower_ch_impl +{ + char operator()(char c) const CPPREST_NOEXCEPT + { + if (c >= 'A' && c <= 'Z') return static_cast(c - 'A' + 'a'); + return c; + } + + wchar_t operator()(wchar_t c) const CPPREST_NOEXCEPT + { + if (c >= L'A' && c <= L'Z') return static_cast(c - L'A' + L'a'); + return c; + } +}; + +CPPREST_CONSTEXPR to_lower_ch_impl to_lower_ch {}; + +struct eq_lower_ch_impl +{ + template + inline CharT operator()(const CharT left, const CharT right) const CPPREST_NOEXCEPT + { + return to_lower_ch(left) == to_lower_ch(right); + } +}; + +CPPREST_CONSTEXPR eq_lower_ch_impl eq_lower_ch {}; + +struct lt_lower_ch_impl +{ + template + inline CharT operator()(const CharT left, const CharT right) const CPPREST_NOEXCEPT + { + return to_lower_ch(left) < to_lower_ch(right); + } +}; + +CPPREST_CONSTEXPR lt_lower_ch_impl lt_lower_ch {}; +} // namespace + +namespace utility +{ +namespace details +{ +_ASYNCRTIMP bool __cdecl str_iequal(const std::string& left, const std::string& right) CPPREST_NOEXCEPT +{ + return left.size() == right.size() && std::equal(left.cbegin(), left.cend(), right.cbegin(), eq_lower_ch); +} + +_ASYNCRTIMP bool __cdecl str_iequal(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT +{ + return left.size() == right.size() && std::equal(left.cbegin(), left.cend(), right.cbegin(), eq_lower_ch); +} + +_ASYNCRTIMP bool __cdecl str_iless(const std::string& left, const std::string& right) CPPREST_NOEXCEPT +{ + return std::lexicographical_compare(left.cbegin(), left.cend(), right.cbegin(), right.cend(), lt_lower_ch); +} + +_ASYNCRTIMP bool __cdecl str_iless(const std::wstring& left, const std::wstring& right) CPPREST_NOEXCEPT +{ + return std::lexicographical_compare(left.cbegin(), left.cend(), right.cbegin(), right.cend(), lt_lower_ch); +} + +_ASYNCRTIMP void __cdecl inplace_tolower(std::string& target) CPPREST_NOEXCEPT +{ + for (auto& ch : target) + { + ch = to_lower_ch(ch); + } +} + +_ASYNCRTIMP void __cdecl inplace_tolower(std::wstring& target) CPPREST_NOEXCEPT +{ + for (auto& ch : target) + { + ch = to_lower_ch(ch); + } +} + +#if !defined(ANDROID) && !defined(__ANDROID__) +std::once_flag g_c_localeFlag; +std::unique_ptr g_c_locale( + nullptr, [](scoped_c_thread_locale::xplat_locale*) {}); +scoped_c_thread_locale::xplat_locale scoped_c_thread_locale::c_locale() +{ + std::call_once(g_c_localeFlag, [&]() { + scoped_c_thread_locale::xplat_locale* clocale = new scoped_c_thread_locale::xplat_locale(); +#ifdef _WIN32 + *clocale = _create_locale(LC_ALL, "C"); + if (clocale == nullptr || *clocale == nullptr) + { + throw std::runtime_error("Unable to create 'C' locale."); + } + auto deleter = [](scoped_c_thread_locale::xplat_locale* clocale) { + _free_locale(*clocale); + delete clocale; + }; +#else + *clocale = newlocale(LC_ALL, "C", nullptr); + if (clocale == nullptr || *clocale == nullptr) + { + throw std::runtime_error("Unable to create 'C' locale."); + } + auto deleter = [](scoped_c_thread_locale::xplat_locale *clocale) + { + freelocale(*clocale); + delete clocale; + }; +#endif + g_c_locale = + std::unique_ptr( + clocale, deleter); + }); + return *g_c_locale; +} +#endif + +#ifdef _WIN32 +scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(), m_prevThreadSetting(-1) +{ + char* prevLocale = setlocale(LC_ALL, nullptr); + if (prevLocale == nullptr) + { + throw std::runtime_error("Unable to retrieve current locale."); + } + + if (std::strcmp(prevLocale, "C") != 0) + { + m_prevLocale = prevLocale; + m_prevThreadSetting = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + if (m_prevThreadSetting == -1) + { + throw std::runtime_error("Unable to enable per thread locale."); + } + if (setlocale(LC_ALL, "C") == nullptr) + { + _configthreadlocale(m_prevThreadSetting); + throw std::runtime_error("Unable to set locale"); + } + } +} + +scoped_c_thread_locale::~scoped_c_thread_locale() +{ + if (m_prevThreadSetting != -1) + { + setlocale(LC_ALL, m_prevLocale.c_str()); + _configthreadlocale(m_prevThreadSetting); + } +} +#elif (defined(ANDROID) || defined(__ANDROID__)) +scoped_c_thread_locale::scoped_c_thread_locale() {} +scoped_c_thread_locale::~scoped_c_thread_locale() {} +#else +scoped_c_thread_locale::scoped_c_thread_locale() : m_prevLocale(nullptr) +{ + char* prevLocale = setlocale(LC_ALL, nullptr); + if (prevLocale == nullptr) + { + throw std::runtime_error("Unable to retrieve current locale."); + } + + if (std::strcmp(prevLocale, "C") != 0) + { + m_prevLocale = uselocale(c_locale()); + if (m_prevLocale == nullptr) + { + throw std::runtime_error("Unable to set locale"); + } + } +} + +scoped_c_thread_locale::~scoped_c_thread_locale() +{ + if (m_prevLocale != nullptr) + { + uselocale(m_prevLocale); + } +} +#endif +} // namespace details + +namespace details +{ +const std::error_category& __cdecl platform_category() +{ +#ifdef _WIN32 + return windows_category(); +#else + return linux_category(); +#endif +} + +#ifdef _WIN32 + +// Remove once VS 2013 is no longer supported. +#if _MSC_VER < 1900 +static details::windows_category_impl instance; +#endif +const std::error_category& __cdecl windows_category() +{ +#if _MSC_VER >= 1900 + static details::windows_category_impl instance; +#endif + return instance; +} + +std::string windows_category_impl::message(int errorCode) const CPPREST_NOEXCEPT +{ + const size_t buffer_size = 4096; + DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM; + LPCVOID lpSource = NULL; + +#if !defined(__cplusplus_winrt) + if (errorCode >= 12000) + { + dwFlags = FORMAT_MESSAGE_FROM_HMODULE; + lpSource = GetModuleHandleA("winhttp.dll"); // this handle DOES NOT need to be freed + } +#endif + + std::wstring buffer(buffer_size, 0); + + const auto result = ::FormatMessageW(dwFlags, lpSource, errorCode, 0, &buffer[0], buffer_size, NULL); + + if (result == 0) + { + return "Unable to get an error message for error code: " + std::to_string(errorCode) + "."; + } + + // strip exceeding characters of the initial resize call + buffer.resize(result); + + return utility::conversions::to_utf8string(buffer); +} + +std::error_condition windows_category_impl::default_error_condition(int errorCode) const CPPREST_NOEXCEPT +{ + // First see if the STL implementation can handle the mapping for common cases. + const std::error_condition errCondition = std::system_category().default_error_condition(errorCode); + const std::string errConditionMsg = errCondition.message(); + if (!utility::details::str_iequal(errConditionMsg, "unknown error")) + { + return errCondition; + } + + switch (errorCode) + { + /* +#ifndef __cplusplus_winrt + + case ERROR_WINHTTP_TIMEOUT: return std::errc::timed_out; + case ERROR_WINHTTP_CANNOT_CONNECT: return std::errc::host_unreachable; + case ERROR_WINHTTP_CONNECTION_ERROR: return std::errc::connection_aborted; +#endif*/ + case INET_E_RESOURCE_NOT_FOUND: + case INET_E_CANNOT_CONNECT: return std::errc::host_unreachable; + case INET_E_CONNECTION_TIMEOUT: return std::errc::timed_out; + case INET_E_DOWNLOAD_FAILURE: return std::errc::connection_aborted; + default: break; + } + + return std::error_condition(errorCode, *this); +} + +#else + +const std::error_category& __cdecl linux_category() +{ + // On Linux we are using boost error codes which have the exact same + // mapping and are equivalent with std::generic_category error codes. + return std::generic_category(); +} + +#endif + +} // namespace details + +#define LOW_3BITS 0x7 +#define LOW_4BITS 0xF +#define LOW_5BITS 0x1F +#define LOW_6BITS 0x3F +#define BIT4 0x8 +#define BIT5 0x10 +#define BIT6 0x20 +#define BIT7 0x40 +#define BIT8 0x80 +#define L_SURROGATE_START 0xDC00 +#define L_SURROGATE_END 0xDFFF +#define H_SURROGATE_START 0xD800 +#define H_SURROGATE_END 0xDBFF +#define SURROGATE_PAIR_START 0x10000 + +// Create a dedicated type for characters to avoid the issue +// of different platforms defaulting char to be either signed +// or unsigned. +using UtilCharInternal_t = signed char; + +inline size_t count_utf8_to_utf16(const std::string& s) +{ + const size_t sSize = s.size(); + auto const sData = reinterpret_cast(s.data()); + size_t result {sSize}; + + for (size_t index = 0; index < sSize;) + { + if (sData[index] >= 0) + { + // use fast inner loop to skip single byte code points (which are + // expected to be the most frequent) + while ((++index < sSize) && (sData[index] >= 0)) + ; + + if (index >= sSize) break; + } + + // start special handling for multi-byte code points + const UtilCharInternal_t c {sData[index++]}; + + if ((c & BIT7) == 0) + { + throw std::range_error("UTF-8 string character can never start with 10xxxxxx"); + } + else if ((c & BIT6) == 0) // 2 byte character, 0x80 to 0x7FF + { + if (index == sSize) + { + throw std::range_error("UTF-8 string is missing bytes in character"); + } + + const UtilCharInternal_t c2 {sData[index++]}; + if ((c2 & 0xC0) != BIT8) + { + throw std::range_error("UTF-8 continuation byte is missing leading bit mask"); + } + + // can't require surrogates for 7FF + --result; + } + else if ((c & BIT5) == 0) // 3 byte character, 0x800 to 0xFFFF + { + if (sSize - index < 2) + { + throw std::range_error("UTF-8 string is missing bytes in character"); + } + + const UtilCharInternal_t c2 {sData[index++]}; + const UtilCharInternal_t c3 {sData[index++]}; + if (((c2 | c3) & 0xC0) != BIT8) + { + throw std::range_error("UTF-8 continuation byte is missing leading bit mask"); + } + + result -= 2; + } + else if ((c & BIT4) == 0) // 4 byte character, 0x10000 to 0x10FFFF + { + if (sSize - index < 3) + { + throw std::range_error("UTF-8 string is missing bytes in character"); + } + + const UtilCharInternal_t c2 {sData[index++]}; + const UtilCharInternal_t c3 {sData[index++]}; + const UtilCharInternal_t c4 {sData[index++]}; + if (((c2 | c3 | c4) & 0xC0) != BIT8) + { + throw std::range_error("UTF-8 continuation byte is missing leading bit mask"); + } + + const uint32_t codePoint = + ((c & LOW_3BITS) << 18) | ((c2 & LOW_6BITS) << 12) | ((c3 & LOW_6BITS) << 6) | (c4 & LOW_6BITS); + result -= (3 - (codePoint >= SURROGATE_PAIR_START)); + } + else + { + throw std::range_error("UTF-8 string has invalid Unicode code point"); + } + } + + return result; +} + +utf16string __cdecl conversions::utf8_to_utf16(const std::string& s) +{ + // Save repeated heap allocations, use the length of resulting sequence. + const size_t srcSize = s.size(); + auto const srcData = reinterpret_cast(s.data()); + utf16string dest(count_utf8_to_utf16(s), L'\0'); + utf16string::value_type* const destData = &dest[0]; + size_t destIndex = 0; + + for (size_t index = 0; index < srcSize; ++index) + { + UtilCharInternal_t src = srcData[index]; + switch (src & 0xF0) + { + case 0xF0: // 4 byte character, 0x10000 to 0x10FFFF + { + const UtilCharInternal_t c2 {srcData[++index]}; + const UtilCharInternal_t c3 {srcData[++index]}; + const UtilCharInternal_t c4 {srcData[++index]}; + uint32_t codePoint = + ((src & LOW_3BITS) << 18) | ((c2 & LOW_6BITS) << 12) | ((c3 & LOW_6BITS) << 6) | (c4 & LOW_6BITS); + if (codePoint >= SURROGATE_PAIR_START) + { + // In UTF-16 U+10000 to U+10FFFF are represented as two 16-bit code units, surrogate pairs. + // - 0x10000 is subtracted from the code point + // - high surrogate is 0xD800 added to the top ten bits + // - low surrogate is 0xDC00 added to the low ten bits + codePoint -= SURROGATE_PAIR_START; + destData[destIndex++] = static_cast((codePoint >> 10) | H_SURROGATE_START); + destData[destIndex++] = + static_cast((codePoint & 0x3FF) | L_SURROGATE_START); + } + else + { + // In UTF-16 U+0000 to U+D7FF and U+E000 to U+FFFF are represented exactly as the Unicode code point + // value. U+D800 to U+DFFF are not valid characters, for simplicity we assume they are not present + // but will encode them if encountered. + destData[destIndex++] = static_cast(codePoint); + } + } + break; + case 0xE0: // 3 byte character, 0x800 to 0xFFFF + { + const UtilCharInternal_t c2 {srcData[++index]}; + const UtilCharInternal_t c3 {srcData[++index]}; + destData[destIndex++] = static_cast( + ((src & LOW_4BITS) << 12) | ((c2 & LOW_6BITS) << 6) | (c3 & LOW_6BITS)); + } + break; + case 0xD0: // 2 byte character, 0x80 to 0x7FF + case 0xC0: + { + const UtilCharInternal_t c2 {srcData[++index]}; + destData[destIndex++] = + static_cast(((src & LOW_5BITS) << 6) | (c2 & LOW_6BITS)); + } + break; + default: // single byte character, 0x0 to 0x7F + // try to use a fast inner loop for following single byte characters, + // since they are quite probable + do + { + destData[destIndex++] = static_cast(srcData[index++]); + } while (index < srcSize && srcData[index] > 0); + // adjust index since it will be incremented by the for loop + --index; + } + } + return dest; +} + +inline size_t count_utf16_to_utf8(const utf16string& w) +{ + const utf16string::value_type* const srcData = &w[0]; + const size_t srcSize = w.size(); + size_t destSize(srcSize); + for (size_t index = 0; index < srcSize; ++index) + { + const utf16string::value_type ch(srcData[index]); + if (ch <= 0x7FF) + { + if (ch > 0x7F) // 2 bytes needed (11 bits used) + { + ++destSize; + } + } + // Check for high surrogate. + else if (ch >= H_SURROGATE_START && ch <= H_SURROGATE_END) // 4 bytes needed (21 bits used) + { + ++index; + if (index == srcSize) + { + throw std::range_error("UTF-16 string is missing low surrogate"); + } + + const auto lowSurrogate = srcData[index]; + if (lowSurrogate < L_SURROGATE_START || lowSurrogate > L_SURROGATE_END) + { + throw std::range_error("UTF-16 string has invalid low surrogate"); + } + + destSize += 2; + } + else // 3 bytes needed (16 bits used) + { + destSize += 2; + } + } + + return destSize; +} + +std::string __cdecl conversions::utf16_to_utf8(const utf16string& w) +{ + const size_t srcSize = w.size(); + const utf16string::value_type* const srcData = &w[0]; + std::string dest(count_utf16_to_utf8(w), '\0'); + std::string::value_type* const destData = &dest[0]; + size_t destIndex(0); + + for (size_t index = 0; index < srcSize; ++index) + { + const utf16string::value_type src = srcData[index]; + if (src <= 0x7FF) + { + if (src <= 0x7F) // single byte character + { + destData[destIndex++] = static_cast(src); + } + else // 2 bytes needed (11 bits used) + { + destData[destIndex++] = static_cast(char((src >> 6) | 0xC0)); // leading 5 bits + destData[destIndex++] = static_cast(char((src & LOW_6BITS) | BIT8)); // trailing 6 bits + } + } + // Check for high surrogate. + else if (src >= H_SURROGATE_START && src <= H_SURROGATE_END) + { + const auto highSurrogate = src; + const auto lowSurrogate = srcData[++index]; + + // To get from surrogate pair to Unicode code point: + // - subtract 0xD800 from high surrogate, this forms top ten bits + // - subtract 0xDC00 from low surrogate, this forms low ten bits + // - add 0x10000 + // Leaves a code point in U+10000 to U+10FFFF range. + uint32_t codePoint = highSurrogate - H_SURROGATE_START; + codePoint <<= 10; + codePoint |= lowSurrogate - L_SURROGATE_START; + codePoint += SURROGATE_PAIR_START; + + // 4 bytes needed (21 bits used) + destData[destIndex++] = static_cast((codePoint >> 18) | 0xF0); // leading 3 bits + destData[destIndex++] = static_cast(((codePoint >> 12) & LOW_6BITS) | BIT8); // next 6 bits + destData[destIndex++] = static_cast(((codePoint >> 6) & LOW_6BITS) | BIT8); // next 6 bits + destData[destIndex++] = static_cast((codePoint & LOW_6BITS) | BIT8); // trailing 6 bits + } + else // 3 bytes needed (16 bits used) + { + destData[destIndex++] = static_cast((src >> 12) | 0xE0); // leading 4 bits + destData[destIndex++] = static_cast(((src >> 6) & LOW_6BITS) | BIT8); // middle 6 bits + destData[destIndex++] = static_cast((src & LOW_6BITS) | BIT8); // trailing 6 bits + } + } + + return dest; +} + +utf16string __cdecl conversions::usascii_to_utf16(const std::string& s) +{ + // Ascii is a subset of UTF-8 so just convert to UTF-16 + return utf8_to_utf16(s); +} + +utf16string __cdecl conversions::latin1_to_utf16(const std::string& s) +{ + // Latin1 is the first 256 code points in Unicode. + // In UTF-16 encoding each of these is represented as exactly the numeric code point. + utf16string dest; + // Prefer resize combined with for-loop over constructor dest(s.begin(), s.end()) + // for faster assignment. + dest.resize(s.size()); + for (size_t i = 0; i < s.size(); ++i) + { + dest[i] = utf16char(static_cast(s[i])); + } + return dest; +} + +utf8string __cdecl conversions::latin1_to_utf8(const std::string& s) { return utf16_to_utf8(latin1_to_utf16(s)); } + +#ifndef _UTF16_STRINGS +utility::string_t __cdecl conversions::to_string_t(utf16string&& s) { return utf16_to_utf8(std::move(s)); } +#endif + +#ifdef _UTF16_STRINGS +utility::string_t __cdecl conversions::to_string_t(std::string&& s) { return utf8_to_utf16(std::move(s)); } +#endif + +#ifndef _UTF16_STRINGS +utility::string_t __cdecl conversions::to_string_t(const utf16string& s) { return utf16_to_utf8(s); } +#endif + +#ifdef _UTF16_STRINGS +utility::string_t __cdecl conversions::to_string_t(const std::string& s) { return utf8_to_utf16(s); } +#endif + +std::string __cdecl conversions::to_utf8string(const utf16string& value) { return utf16_to_utf8(value); } + +utf16string __cdecl conversions::to_utf16string(const std::string& value) { return utf8_to_utf16(value); } + +static const int64_t NtToUnixOffsetSeconds = 11644473600; // diff between windows and unix epochs (seconds) + +static bool year_is_leap_year(int year) { return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } + +static const int SecondsInMinute = 60; +static const int SecondsInHour = SecondsInMinute * 60; +static const int SecondsInDay = SecondsInHour * 24; + +static const int DaysInYear = 365; +static const int DaysIn4Years = DaysInYear * 4 + 1; +static const int DaysIn100Years = DaysIn4Years * 25 - 1; +static const int DaysIn400Years = DaysIn100Years * 4 + 1; + +static const int SecondsInYear = SecondsInDay * DaysInYear; +static const int SecondsIn4Years = SecondsInDay * DaysIn4Years; +static const int64_t SecondsIn100Years = static_cast(SecondsInDay) * DaysIn100Years; +static const int64_t SecondsIn400Years = static_cast(SecondsInDay) * DaysIn400Years; +static const int64_t SecondsFrom1900To2001 = INT64_C(3187296000); + +static const int64_t NtTo1900OffsetInterval = INT64_C(0x014F373BFDE04000); + +static int count_leap_years(const int yearsSince1900) +{ + int tmpYears = yearsSince1900 + 299; // shift into 1601, the first 400 year cycle including 1900 + + int year400 = tmpYears / 400; + tmpYears -= year400 * 400; + int result = year400 * 97; + + int year100 = tmpYears / 100; + tmpYears -= year100 * 100; + result += year100 * 24; + + result += tmpYears / 4; + + // subtract off leap years from 1601 + result -= 72; + + return result; +} + +// The following table assumes no leap year; leap year is added separately +static const unsigned short cumulative_days_to_month[12] = { + 0, // Jan + 31, // Feb + 59, // Mar + 90, // Apr + 120, // May + 151, // Jun + 181, // Jul + 212, // Aug + 243, // Sep + 273, // Oct + 304, // Nov + 334 // Dec +}; + +static const unsigned short cumulative_days_to_month_leap[12] = { + 0, // Jan + 31, // Feb + 60, // Mar + 91, // Apr + 121, // May + 152, // Jun + 182, // Jul + 213, // Aug + 244, // Sep + 274, // Oct + 305, // Nov + 335 // Dec +}; + +datetime __cdecl datetime::utc_now() +{ +#ifdef _WIN32 + ULARGE_INTEGER largeInt; + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + + largeInt.LowPart = fileTime.dwLowDateTime; + largeInt.HighPart = fileTime.dwHighDateTime; + + return datetime(largeInt.QuadPart); +#else // LINUX + struct timeval time; + gettimeofday(&time, nullptr); + int64_t result = NtToUnixOffsetSeconds + time.tv_sec; + result *= _secondTicks; // convert to 10e-7 + result += time.tv_usec * 10; // convert and add microseconds, 10e-6 to 10e-7 + return datetime(static_cast(result)); +#endif +} + +static const char dayNames[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat"; +static const char monthNames[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec"; + +struct compute_year_result +{ + int year; + int secondsLeftThisYear; +}; + +static const int64_t secondsFrom1601To1900 = INT64_C(9435484800); + +static compute_year_result compute_year(int64_t secondsSince1900) +{ + int64_t secondsLeft = secondsSince1900 + secondsFrom1601To1900; // shift to start of this 400 year cycle + + int year400 = static_cast(secondsLeft / SecondsIn400Years); + secondsLeft -= year400 * SecondsIn400Years; + + int year100 = static_cast(secondsLeft / SecondsIn100Years); + secondsLeft -= year100 * SecondsIn100Years; + + int year4 = static_cast(secondsLeft / SecondsIn4Years); + int secondsInt = static_cast(secondsLeft - year4 * SecondsIn4Years); + + int year1 = secondsInt / SecondsInYear; + secondsInt -= year1 * SecondsInYear; + + // shift back to 1900 base from 1601: + return {year400 * 400 + year100 * 100 + year4 * 4 + year1 - 299, secondsInt}; +} + +utility::string_t datetime::to_string(date_format format) const +{ + if (m_interval > INT64_C(2650467743990000000)) + { + throw std::out_of_range("The requested year exceeds the year 9999."); + } + + const int64_t epochAdjusted = static_cast(m_interval) - NtTo1900OffsetInterval; + const int64_t secondsSince1900 = epochAdjusted / _secondTicks; // convert to seconds + const int fracSec = static_cast(epochAdjusted % _secondTicks); + + const auto yearData = compute_year(secondsSince1900); + const int year = yearData.year; + const int yearDay = yearData.secondsLeftThisYear / SecondsInDay; + int leftover = yearData.secondsLeftThisYear % SecondsInDay; + const int hour = leftover / SecondsInHour; + leftover = leftover % SecondsInHour; + const int minute = leftover / SecondsInMinute; + leftover = leftover % SecondsInMinute; + + const auto& monthTable = year_is_leap_year(year) ? cumulative_days_to_month_leap : cumulative_days_to_month; + int month = 0; + while (month < 11 && monthTable[month + 1] <= yearDay) + { + ++month; + } + + const auto monthDay = yearDay - monthTable[month] + 1; + const auto weekday = static_cast((secondsSince1900 / SecondsInDay + 1) % 7); + + char outBuffer[38]; // Thu, 01 Jan 1970 00:00:00 GMT\0 + // 1970-01-01T00:00:00.1234567Z\0 + char* outCursor = outBuffer; + switch (format) + { + case RFC_1123: +#ifdef _MSC_VER + sprintf_s(outCursor, + 26, + "%s, %02d %s %04d %02d:%02d:%02d", + dayNames + 4 * weekday, + monthDay, + monthNames + 4 * month, + year + 1900, + hour, + minute, + leftover); +#else // ^^^ _MSC_VER // !_MSC_VER vvv + sprintf(outCursor, + "%s, %02d %s %04d %02d:%02d:%02d", + dayNames + 4 * weekday, + monthDay, + monthNames + 4 * month, + year + 1900, + hour, + minute, + leftover); +#endif // _MSC_VER + outCursor += 25; + memcpy(outCursor, " GMT", 4); + outCursor += 4; + return utility::string_t(outBuffer, outCursor); + case ISO_8601: +#ifdef _MSC_VER + sprintf_s(outCursor, + 20, + "%04d-%02d-%02dT%02d:%02d:%02d", + year + 1900, + month + 1, + monthDay, + hour, + minute, + leftover); +#else // ^^^ _MSC_VER // !_MSC_VER vvv + sprintf( + outCursor, "%04d-%02d-%02dT%02d:%02d:%02d", year + 1900, month + 1, monthDay, hour, minute, leftover); +#endif // _MSC_VER + outCursor += 19; + if (fracSec != 0) + { + // Append fractional second, which is a 7-digit value with no trailing zeros + // This way, '1200' becomes '00012' +#ifdef _MSC_VER + size_t appended = sprintf_s(outCursor, 9, ".%07d", fracSec); +#else // ^^^ _MSC_VER // !_MSC_VER vvv + size_t appended = sprintf(outCursor, ".%07d", fracSec); +#endif // _MSC_VER + while (outCursor[appended - 1] == '0') + { + --appended; // trim trailing zeros + } + + outCursor += appended; + } + + *outCursor = 'Z'; + ++outCursor; + return utility::string_t(outBuffer, outCursor); + default: throw std::invalid_argument("Unrecognized date format."); + } +} + +template +static bool string_starts_with(const CharT* haystack, const char* needle) +{ + while (*needle) + { + if (*haystack != static_cast(*needle)) + { + return false; + } + + ++haystack; + ++needle; + } + + return true; +} + +#define ascii_isdigit(c) ((unsigned char)((unsigned char)(c) - '0') <= 9) +#define ascii_isdigit6(c) ((unsigned char)((unsigned char)(c) - '0') <= 6) +#define ascii_isdigit5(c) ((unsigned char)((unsigned char)(c) - '0') <= 5) +#define ascii_isdigit3(c) ((unsigned char)((unsigned char)(c) - '0') <= 3) +#define ascii_isdigit2(c) ((unsigned char)((unsigned char)(c) - '0') <= 2) +#define ascii_isdigit1(c) ((unsigned char)((unsigned char)(c) - '0') <= 1) + +static const unsigned char max_days_in_month[12] = { + 31, // Jan + 00, // Feb, special handling for leap years + 31, // Mar + 30, // Apr + 31, // May + 30, // Jun + 31, // Jul + 31, // Aug + 30, // Sep + 31, // Oct + 30, // Nov + 31 // Dec +}; + +static bool validate_day_month(int day, int month, int year) +{ + int maxDaysThisMonth; + if (month == 1) + { // Feb needs leap year testing + maxDaysThisMonth = 28 + year_is_leap_year(year); + } + else + { + maxDaysThisMonth = max_days_in_month[month]; + } + + return day >= 1 && day <= maxDaysThisMonth; +} + +static int get_year_day(int month, int monthDay, int year) +{ + return cumulative_days_to_month[month] + monthDay + (year_is_leap_year(year) && month > 1) - 1; +} + +template +static int atoi2(const CharT* str) +{ + return (static_cast(str[0]) - '0') * 10 + (static_cast(str[1]) - '0'); +} + +static int64_t timezone_adjust(int64_t result, unsigned char chSign, int adjustHours, int adjustMinutes) +{ + if (adjustHours > 23) + { + return -1; + } + + // adjustMinutes > 59 is impossible due to digit 5 check + const int tzAdjust = adjustMinutes * 60 + adjustHours * 60 * 60; + if (chSign == '-') + { + if (INT64_MAX - result < tzAdjust) + { + return -1; + } + + result += tzAdjust; + } + else + { + if (tzAdjust > result) + { + return -1; + } + + result -= tzAdjust; + } + + return result; +} + +/* +https://tools.ietf.org/html/rfc822 +https://tools.ietf.org/html/rfc1123 + +date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + +day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + +date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 +RFC1123 changes this to: +date = 1*2DIGIT month 2*4DIGIT ; day month year + ; e.g. 20 Jun 1982 +This implementation only accepts 4 digit years. + +month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + +time = hour zone ; ANSI and Military + +hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + +zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + +// military time deleted by RFC 1123 + + / ( ("+" / "-") 4DIGIT ) ; Local differential + ; hours+min. (HHMM) +*/ + + +datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format) +{ + datetime result; + int64_t secondsSince1900; + uint64_t fracSec = 0; + auto str = dateString.c_str(); + if (format == RFC_1123) + { + int parsedWeekday = 0; + for (; parsedWeekday < 7; ++parsedWeekday) + { + if (string_starts_with(str, dayNames + parsedWeekday * 4) && str[3] == _XPLATSTR(',') && + str[4] == _XPLATSTR(' ')) + { + str += 5; // parsed day of week + break; + } + } + + int monthDay; + if (ascii_isdigit3(str[0]) && ascii_isdigit(str[1]) && str[2] == _XPLATSTR(' ')) + { + monthDay = atoi2(str); // validity checked later + str += 3; // parsed day + } + else if (ascii_isdigit(str[0]) && str[1] == _XPLATSTR(' ')) + { + monthDay = str[0] - _XPLATSTR('0'); + str += 2; // parsed day + } + else + { + return result; + } + + if (monthDay == 0) + { + return result; + } + + int month = 0; + for (;;) + { + if (string_starts_with(str, monthNames + month * 4)) + { + break; + } + + ++month; + if (month == 12) + { + return result; + } + } + + if (str[3] != _XPLATSTR(' ')) + { + return result; + } + + str += 4; // parsed month + + if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3]) || + str[4] != ' ') + { + return result; + } + + int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 + + (str[3] - _XPLATSTR('0')); + if (year < 1900) + { + return result; + } + + // days in month validity check + if (!validate_day_month(monthDay, month, year)) + { + return result; + } + + str += 5; // parsed year + const int yearDay = get_year_day(month, monthDay, year); + + if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1]) || str[2] != _XPLATSTR(':') || !ascii_isdigit5(str[3]) || + !ascii_isdigit(str[4])) + { + return result; + } + + const int hour = atoi2(str); + if (hour > 23) + { + return result; + } + + str += 3; // parsed hour + const int minute = atoi2(str); + str += 2; // parsed mins + + int sec; + if (str[0] == ':') + { + if (!ascii_isdigit6(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(' ')) + { + return result; + } + + sec = atoi2(str + 1); + str += 4; // parsed seconds + } + else if (str[0] == _XPLATSTR(' ')) + { + sec = 0; + str += 1; // parsed seconds + } + else + { + return result; + } + + if (sec > 60) + { // 60 to allow leap seconds + return result; + } + + year -= 1900; + int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay; + + if (parsedWeekday != 7) + { + const int actualWeekday = (daysSince1900 + 1) % 7; + + if (parsedWeekday != actualWeekday) + { + return result; + } + } + + secondsSince1900 = + static_cast(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; + + if (!string_starts_with(str, "GMT") && !string_starts_with(str, "UT")) + { + // some timezone adjustment necessary + auto tzCh = _XPLATSTR('-'); + int tzHours; + int tzMinutes = 0; + if (string_starts_with(str, "EDT")) + { + tzHours = 4; + } + else if (string_starts_with(str, "EST") || string_starts_with(str, "CDT")) + { + tzHours = 5; + } + else if (string_starts_with(str, "CST") || string_starts_with(str, "MDT")) + { + tzHours = 6; + } + else if (string_starts_with(str, "MST") || string_starts_with(str, "PDT")) + { + tzHours = 7; + } + else if (string_starts_with(str, "PST")) + { + tzHours = 8; + } + else if ((tzCh == _XPLATSTR('+') || tzCh == _XPLATSTR('-')) && ascii_isdigit2(str[1]) && + ascii_isdigit(str[2]) && ascii_isdigit5(str[3]) && ascii_isdigit(str[4])) + { + tzCh = str[0]; + tzHours = atoi2(str + 1); + tzMinutes = atoi2(str + 3); + } + else + { + return result; + } + + secondsSince1900 = timezone_adjust(secondsSince1900, static_cast(tzCh), tzHours, tzMinutes); + if (secondsSince1900 < 0) + { + return result; + } + } + } + else if (format == ISO_8601) + { + // parse year + if (!ascii_isdigit(str[0]) || !ascii_isdigit(str[1]) || !ascii_isdigit(str[2]) || !ascii_isdigit(str[3])) + { + return result; + } + + int year = (str[0] - _XPLATSTR('0')) * 1000 + (str[1] - _XPLATSTR('0')) * 100 + (str[2] - _XPLATSTR('0')) * 10 + + (str[3] - _XPLATSTR('0')); + if (year < 1900) + { + return result; + } + + str += 4; + if (*str == _XPLATSTR('-')) + { + ++str; + } + + // parse month + if (!ascii_isdigit1(str[0]) || !ascii_isdigit(str[1])) + { + return result; + } + + int month = atoi2(str); + if (month < 1 || month > 12) + { + return result; + } + + month -= 1; + str += 2; + + if (*str == _XPLATSTR('-')) + { + ++str; + } + + // parse day + if (!ascii_isdigit3(str[0]) || !ascii_isdigit(str[1])) + { + return result; + } + + int monthDay = atoi2(str); + if (!validate_day_month(monthDay, month, year)) + { + return result; + } + + const int yearDay = get_year_day(month, monthDay, year); + + str += 2; + year -= 1900; + int daysSince1900 = year * DaysInYear + count_leap_years(year) + yearDay; + + if (str[0] != _XPLATSTR('T') && str[0] != _XPLATSTR('t')) + { + // No time + secondsSince1900 = static_cast(daysSince1900) * SecondsInDay; + + result.m_interval = + static_cast(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval); + return result; + } + + ++str; // skip 'T' + + // parse hour + if (!ascii_isdigit2(str[0]) || !ascii_isdigit(str[1])) + { + return result; + } + + const int hour = atoi2(str); + str += 2; + if (hour > 23) + { + return result; + } + + if (*str == _XPLATSTR(':')) + { + ++str; + } + + // parse minute + if (!ascii_isdigit5(str[0]) || !ascii_isdigit(str[1])) + { + return result; + } + + const int minute = atoi2(str); + // minute > 59 is impossible because we checked that the first digit is <= 5 in the basic format + // check above + + str += 2; + + if (*str == _XPLATSTR(':')) + { + ++str; + } + + // parse seconds + if (!ascii_isdigit6(str[0]) || !ascii_isdigit(str[1])) + { + return result; + } + + const int sec = atoi2(str); + // We allow 60 to account for leap seconds + if (sec > 60) + { + return result; + } + + str += 2; + if (str[0] == _XPLATSTR('.') && ascii_isdigit(str[1])) + { + ++str; + int digits = 7; + for (;;) + { + fracSec *= 10; + fracSec += *str - _XPLATSTR('0'); + --digits; + ++str; + if (digits == 0) + { + while (ascii_isdigit(*str)) + { + // consume remaining fractional second digits we can't use + ++str; + } + + break; + } + + if (!ascii_isdigit(*str)) + { + // no more digits in the input, do the remaining multiplies we need + for (; digits != 0; --digits) + { + fracSec *= 10; + } + + break; + } + } + } + + secondsSince1900 = + static_cast(daysSince1900) * SecondsInDay + hour * SecondsInHour + minute * SecondsInMinute + sec; + + if (str[0] == _XPLATSTR('Z') || str[0] == _XPLATSTR('z')) + { + // no adjustment needed for zulu time + } + else if (str[0] == _XPLATSTR('+') || str[0] == _XPLATSTR('-')) + { + const unsigned char offsetDirection = static_cast(str[0]); + if (!ascii_isdigit2(str[1]) || !ascii_isdigit(str[2]) || str[3] != _XPLATSTR(':') || + !ascii_isdigit5(str[4]) || !ascii_isdigit(str[5])) + { + return result; + } + + secondsSince1900 = timezone_adjust(secondsSince1900, offsetDirection, atoi2(str + 1), atoi2(str + 4)); + if (secondsSince1900 < 0) + { + return result; + } + } + else + { + // the timezone is malformed, but cpprestsdk currently accepts this as no timezone + } + } + else + { + throw std::invalid_argument("unrecognized date format"); + } + + result.m_interval = static_cast(secondsSince1900 * _secondTicks + fracSec + NtTo1900OffsetInterval); + return result; +} + +/// +/// Converts a timespan/interval in seconds to xml duration string as specified by +/// http://www.w3.org/TR/xmlschema-2/#duration +/// +utility::string_t __cdecl timespan::seconds_to_xml_duration(utility::seconds durationSecs) +{ + auto numSecs = durationSecs.count(); + + // Find the number of minutes + auto numMins = numSecs / 60; + if (numMins > 0) + { + numSecs = numSecs % 60; + } + + // Hours + auto numHours = numMins / 60; + if (numHours > 0) + { + numMins = numMins % 60; + } + + // Days + auto numDays = numHours / 24; + if (numDays > 0) + { + numHours = numHours % 24; + } + + // The format is: + // PdaysDThoursHminutesMsecondsS + utility::string_t result; + // (approximate mins/hours/secs as 2 digits each + 1 prefix character) + 1 for P prefix + 1 for T + size_t baseReserveSize = ((numHours > 0) + (numMins > 0) + (numSecs > 0)) * 3 + 1; + if (numDays > 0) + { + utility::string_t daysStr = utility::conversions::details::to_string_t(numDays); + result.reserve(baseReserveSize + daysStr.size() + 1); + result += _XPLATSTR('P'); + result += daysStr; + result += _XPLATSTR('D'); + } + else + { + result.reserve(baseReserveSize); + result += _XPLATSTR('P'); + } + + result += _XPLATSTR('T'); + + if (numHours > 0) + { + result += utility::conversions::details::to_string_t(numHours); + result += _XPLATSTR('H'); + } + + if (numMins > 0) + { + result += utility::conversions::details::to_string_t(numMins); + result += _XPLATSTR('M'); + } + + if (numSecs > 0) + { + result += utility::conversions::details::to_string_t(numSecs); + result += _XPLATSTR('S'); + } + + return result; +} + +utility::seconds __cdecl timespan::xml_duration_to_seconds(const utility::string_t& timespanString) +{ + // The format is: + // PnDTnHnMnS + // if n == 0 then the field could be omitted + // The final S could be omitted + + int64_t numSecs = 0; + auto cursor = timespanString.c_str(); + auto c = *cursor++; // skip 'P' + while (c) + { + int val = 0; + c = *cursor++; + + while (ascii_isdigit(c)) + { + val = val * 10 + (c - _XPLATSTR('0')); + c = *cursor++; + + if (c == _XPLATSTR('.')) + { + // decimal point is not handled + do + { + c = *cursor++; + } while (ascii_isdigit(c)); + } + } + + if (c == _XPLATSTR('D')) numSecs += val * 24 * 3600; // days + if (c == _XPLATSTR('H')) numSecs += val * 3600; // Hours + if (c == _XPLATSTR('M')) numSecs += val * 60; // Minutes + if (c == _XPLATSTR('S') || c == _XPLATSTR('\0')) + { + numSecs += val; // seconds + break; + } + } + + return utility::seconds(numSecs); +} + +static const char c_allowed_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +static const int chars_count = static_cast(sizeof(c_allowed_chars) - 1); + +utility::string_t nonce_generator::generate() +{ + std::uniform_int_distribution<> distr(0, chars_count - 1); + utility::string_t result; + result.reserve(length()); + std::generate_n(std::back_inserter(result), length(), [&] { + return static_cast(c_allowed_chars[distr(m_random)]); + }); + return result; +} + +} // namespace utility diff --git a/deps/cpprestsdk/src/utilities/base64.cpp b/deps/cpprestsdk/src/utilities/base64.cpp new file mode 100644 index 00000000000..cff4e84fd44 --- /dev/null +++ b/deps/cpprestsdk/src/utilities/base64.cpp @@ -0,0 +1,260 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ +#include "pch.h" + +using namespace web; +using namespace utility; + +std::vector _from_base64(const utility::string_t& str); +utility::string_t _to_base64(const unsigned char* ptr, size_t size); + +std::vector __cdecl conversions::from_base64(const utility::string_t& str) { return _from_base64(str); } + +utility::string_t __cdecl conversions::to_base64(const std::vector& input) +{ + if (input.size() == 0) + { + // return empty string + return utility::string_t(); + } + + return _to_base64(&input[0], input.size()); +} + +utility::string_t __cdecl conversions::to_base64(uint64_t input) +{ + return _to_base64(reinterpret_cast(&input), sizeof(input)); +} + +static const char* _base64_enctbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +const std::array _base64_dectbl = { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, + 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 254, 255, 255, 255, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255}}; + +struct _triple_byte +{ + unsigned char _1_1 : 2; + unsigned char _0 : 6; + unsigned char _2_1 : 4; + unsigned char _1_2 : 4; + unsigned char _3 : 6; + unsigned char _2_2 : 2; +}; + +struct _double_byte +{ + unsigned char _1_1 : 2; + unsigned char _0 : 6; + unsigned char _2_1 : 4; + unsigned char _1_2 : 4; +}; + +struct _single_byte +{ + unsigned char _1_1 : 2; + unsigned char _0 : 6; +}; + +// +// A note on the implementation of BASE64 encoding and decoding: +// +// This is a fairly basic and naive implementation; there is probably a lot of room for +// performance improvement, as well as for adding options such as support for URI-safe base64, +// ignoring CRLF, relaxed validation rules, etc. The decoder is currently pretty strict. +// + +#ifdef __GNUC__ +// gcc is concerned about the bitfield uses in the code, something we simply need to ignore. +#pragma GCC diagnostic ignored "-Wconversion" +#endif +std::vector _from_base64(const utility::string_t& input) +{ + std::vector result; + + if (input.empty()) return result; + + size_t padding = 0; + + // Validation + { + auto size = input.size(); + + if ((size % 4) != 0) + { + throw std::runtime_error("length of base64 string is not an even multiple of 4"); + } + + for (auto iter = input.begin(); iter != input.end(); ++iter, --size) + { + const size_t ch_sz = static_cast(*iter); + if (ch_sz >= _base64_dectbl.size() || _base64_dectbl[ch_sz] == 255) + { + throw std::runtime_error("invalid character found in base64 string"); + } + if (_base64_dectbl[ch_sz] == 254) + { + padding++; + // padding only at the end + if (size > 2) + { + throw std::runtime_error("invalid padding character found in base64 string"); + } + if (size == 2) + { + const size_t ch2_sz = static_cast(*(iter + 1)); + if (ch2_sz >= _base64_dectbl.size() || _base64_dectbl[ch2_sz] != 254) + { + throw std::runtime_error("invalid padding character found in base64 string"); + } + } + } + } + } + + auto size = input.size(); + const char_t* ptr = &input[0]; + + auto outsz = (size / 4) * 3; + outsz -= padding; + + result.resize(outsz); + + size_t idx = 0; + for (; size > 4; ++idx) + { + unsigned char target[3]; + memset(target, 0, sizeof(target)); + _triple_byte* record = reinterpret_cast<_triple_byte*>(target); + + unsigned char val0 = _base64_dectbl[ptr[0]]; + unsigned char val1 = _base64_dectbl[ptr[1]]; + unsigned char val2 = _base64_dectbl[ptr[2]]; + unsigned char val3 = _base64_dectbl[ptr[3]]; + + record->_0 = val0; + record->_1_1 = val1 >> 4; + result[idx] = target[0]; + + record->_1_2 = val1 & 0xF; + record->_2_1 = val2 >> 2; + result[++idx] = target[1]; + + record->_2_2 = val2 & 0x3; + record->_3 = val3 & 0x3F; + result[++idx] = target[2]; + + ptr += 4; + size -= 4; + } + + // Handle the last four bytes separately, to avoid having the conditional statements + // in all the iterations (a performance issue). + + { + unsigned char target[3]; + memset(target, 0, sizeof(target)); + _triple_byte* record = reinterpret_cast<_triple_byte*>(target); + + unsigned char val0 = _base64_dectbl[ptr[0]]; + unsigned char val1 = _base64_dectbl[ptr[1]]; + unsigned char val2 = _base64_dectbl[ptr[2]]; + unsigned char val3 = _base64_dectbl[ptr[3]]; + + record->_0 = val0; + record->_1_1 = val1 >> 4; + result[idx] = target[0]; + + record->_1_2 = val1 & 0xF; + if (val2 != 254) + { + record->_2_1 = val2 >> 2; + result[++idx] = target[1]; + } + else + { + // There shouldn't be any information (ones) in the unused bits, + if (record->_1_2 != 0) + { + throw std::runtime_error("Invalid end of base64 string"); + } + return result; + } + + record->_2_2 = val2 & 0x3; + if (val3 != 254) + { + record->_3 = val3 & 0x3F; + result[++idx] = target[2]; + } + else + { + // There shouldn't be any information (ones) in the unused bits. + if (record->_2_2 != 0) + { + throw std::runtime_error("Invalid end of base64 string"); + } + return result; + } + } + + return result; +} + +utility::string_t _to_base64(const unsigned char* ptr, size_t size) +{ + utility::string_t result; + + for (; size >= 3;) + { + const _triple_byte* record = reinterpret_cast(ptr); + unsigned char idx0 = record->_0; + unsigned char idx1 = (record->_1_1 << 4) | record->_1_2; + unsigned char idx2 = (record->_2_1 << 2) | record->_2_2; + unsigned char idx3 = record->_3; + result.push_back(char_t(_base64_enctbl[idx0])); + result.push_back(char_t(_base64_enctbl[idx1])); + result.push_back(char_t(_base64_enctbl[idx2])); + result.push_back(char_t(_base64_enctbl[idx3])); + size -= 3; + ptr += 3; + } + switch (size) + { + case 1: + { + const _single_byte* record = reinterpret_cast(ptr); + unsigned char idx0 = record->_0; + unsigned char idx1 = (record->_1_1 << 4); + result.push_back(char_t(_base64_enctbl[idx0])); + result.push_back(char_t(_base64_enctbl[idx1])); + result.push_back('='); + result.push_back('='); + break; + } + case 2: + { + const _double_byte* record = reinterpret_cast(ptr); + unsigned char idx0 = record->_0; + unsigned char idx1 = (record->_1_1 << 4) | record->_1_2; + unsigned char idx2 = (record->_2_1 << 2); + result.push_back(char_t(_base64_enctbl[idx0])); + result.push_back(char_t(_base64_enctbl[idx1])); + result.push_back(char_t(_base64_enctbl[idx2])); + result.push_back('='); + break; + } + } + return result; +} diff --git a/deps/cpprestsdk/src/utilities/web_utilities.cpp b/deps/cpprestsdk/src/utilities/web_utilities.cpp new file mode 100644 index 00000000000..811b390ab54 --- /dev/null +++ b/deps/cpprestsdk/src/utilities/web_utilities.cpp @@ -0,0 +1,157 @@ +/*** + * Copyright (C) Microsoft. All rights reserved. + * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. + * + * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * + * Credential and proxy utilities. + * + * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk + * + * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + ****/ + +#include "pch.h" + +#include + +#if defined(_WIN32) && !defined(__cplusplus_winrt) +#include +#endif + +#if defined(__cplusplus_winrt) +#include +#endif + +namespace web +{ +namespace details +{ +#if defined(_WIN32) && !defined(CPPREST_TARGET_XP) +#if defined(__cplusplus_winrt) + +// Helper function to zero out memory of an IBuffer. +void winrt_secure_zero_buffer(Windows::Storage::Streams::IBuffer ^ buffer) +{ + Microsoft::WRL::ComPtr bufferInspectable(reinterpret_cast(buffer)); + Microsoft::WRL::ComPtr bufferByteAccess; + bufferInspectable.As(&bufferByteAccess); + + // This shouldn't happen but if can't get access to the raw bytes for some reason + // then we can't zero out. + byte* rawBytes; + if (bufferByteAccess->Buffer(&rawBytes) == S_OK) + { + SecureZeroMemory(rawBytes, buffer->Length); + } +} + +winrt_encryption::winrt_encryption(const std::wstring& data) +{ + auto provider = ref new Windows::Security::Cryptography::DataProtection::DataProtectionProvider( + ref new Platform::String(L"Local=user")); + + // Create buffer containing plain text password. + Platform::ArrayReference arrayref( + reinterpret_cast(const_cast(data.c_str())), + static_cast(data.size()) * sizeof(std::wstring::value_type)); + Windows::Storage::Streams::IBuffer ^ plaintext = + Windows::Security::Cryptography::CryptographicBuffer::CreateFromByteArray(arrayref); + m_buffer = pplx::create_task(provider->ProtectAsync(plaintext)); + m_buffer.then( + [plaintext](pplx::task) { winrt_secure_zero_buffer(plaintext); }); +} + +plaintext_string winrt_encryption::decrypt() const +{ + // To fully guarantee asynchrony would require significant impact on existing code. This code path + // is never run on a user's thread and is only done once when setting up a connection. + auto encrypted = m_buffer.get(); + auto provider = ref new Windows::Security::Cryptography::DataProtection::DataProtectionProvider(); + auto plaintext = pplx::create_task(provider->UnprotectAsync(encrypted)).get(); + + // Get access to raw bytes in plain text buffer. + Microsoft::WRL::ComPtr bufferInspectable(reinterpret_cast(plaintext)); + Microsoft::WRL::ComPtr bufferByteAccess; + bufferInspectable.As(&bufferByteAccess); + byte* rawPlaintext; + const auto& result = bufferByteAccess->Buffer(&rawPlaintext); + if (result != S_OK) + { + throw ::utility::details::create_system_error(result); + } + + // Construct string and zero out memory from plain text buffer. + auto data = plaintext_string( + new std::wstring(reinterpret_cast(rawPlaintext), plaintext->Length / 2)); + SecureZeroMemory(rawPlaintext, plaintext->Length); + return std::move(data); +} + +#else + +win32_encryption::win32_encryption(const std::wstring& data) : m_numCharacters(data.size()) +{ + // Early return because CryptProtectMemory crashes with empty string + if (m_numCharacters == 0) + { + return; + } + + if (data.size() > (std::numeric_limits::max)() / sizeof(wchar_t)) + { + throw std::length_error("Encryption string too long"); + } + + const auto dataSizeDword = static_cast(data.size() * sizeof(wchar_t)); + + // Round up dataSizeDword to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE + static_assert(CRYPTPROTECTMEMORY_BLOCK_SIZE == 16, "Power of 2 assumptions in this bit masking violated"); + const auto mask = static_cast(CRYPTPROTECTMEMORY_BLOCK_SIZE - 1u); + const auto dataNumBytes = (dataSizeDword & ~mask) + ((dataSizeDword & mask) != 0) * CRYPTPROTECTMEMORY_BLOCK_SIZE; + assert((dataNumBytes % CRYPTPROTECTMEMORY_BLOCK_SIZE) == 0); + assert(dataNumBytes >= dataSizeDword); + m_buffer.resize(dataNumBytes); + memcpy_s(m_buffer.data(), m_buffer.size(), data.c_str(), dataNumBytes); + if (!CryptProtectMemory(m_buffer.data(), dataNumBytes, CRYPTPROTECTMEMORY_SAME_PROCESS)) + { + throw ::utility::details::create_system_error(GetLastError()); + } +} + +win32_encryption::~win32_encryption() { SecureZeroMemory(m_buffer.data(), m_buffer.size()); } + +plaintext_string win32_encryption::decrypt() const +{ + // Copy the buffer and decrypt to avoid having to re-encrypt. + auto result = plaintext_string(new std::wstring(reinterpret_cast(m_buffer.data()), + m_buffer.size() / sizeof(wchar_t))); + auto& data = *result; + if (!m_buffer.empty()) + { + if (!CryptUnprotectMemory(&data[0], static_cast(m_buffer.size()), CRYPTPROTECTMEMORY_SAME_PROCESS)) + { + throw ::utility::details::create_system_error(GetLastError()); + } + + assert(m_numCharacters <= m_buffer.size()); + SecureZeroMemory(&data[m_numCharacters], data.size() - m_numCharacters); + data.erase(m_numCharacters); + } + + return result; +} +#endif +#endif + +void zero_memory_deleter::operator()(::utility::string_t* data) const +{ + CASABLANCA_UNREFERENCED_PARAMETER(data); +#if defined(_WIN32) + SecureZeroMemory(&(*data)[0], data->size() * sizeof(::utility::string_t::value_type)); + delete data; +#endif +} +} // namespace details + +} // namespace web diff --git a/doc/coding/organization.md b/doc/coding/organization.md new file mode 100644 index 00000000000..e7e609d9a9e --- /dev/null +++ b/doc/coding/organization.md @@ -0,0 +1,29 @@ +# Code Organization + +## Rules + +- **Follow the pattern of what you already see in the code** +- Try to package new ideas/components into libraries that have nicely defined interfaces +- Package new ideas into classes or refactor existing ideas into a class as you extend + +## Code Overview + +General project organization: + +#### The [`build`](/build) folder +Contains the Azure pipeline CI build scripts. + +#### The [`deps`](/deps) folder +Contains other projects, that PowerToys uses as dependency. + +#### The [`doc`](/doc) folder +Documentation for the project, including a [coding guide](/doc/coding) and [design docs](/doc/specs). + +#### The [`installer`](/installer) folder +Contains the source code of the PowerToys installer. + +#### The [`src`](/src) folder +Contains the source code of the PowerToys runner and of all of the PowerToys modules. **This is where the most of the magic happens.** + +#### The [`tools`](/tools) folder +Various tools used by PowerToys. Includes the Visual Studio 2019 project template for new PowerToys. diff --git a/doc/coding/style.md b/doc/coding/style.md new file mode 100644 index 00000000000..8e3c7b9e55f --- /dev/null +++ b/doc/coding/style.md @@ -0,0 +1,5 @@ +# Coding Style + +## Philosophy +1. If it's inserting something into the existing classes/functions, try to follow the existing style as closely as possible. +1. If it's brand new code or refactoring a complete class or area of the code, please follow as Modern C++ of a style as you can and reference the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) as much as you possibly can. diff --git a/doc/images/FZTutorial.jpg b/doc/images/FZTutorial.jpg new file mode 100644 index 00000000000..74214b0edb7 Binary files /dev/null and b/doc/images/FZTutorial.jpg differ diff --git a/doc/images/Logo.jpg b/doc/images/Logo.jpg new file mode 100644 index 00000000000..194251abff3 Binary files /dev/null and b/doc/images/Logo.jpg differ diff --git a/doc/images/MTNDWidget.jpg b/doc/images/MTNDWidget.jpg new file mode 100644 index 00000000000..72dd1147e57 Binary files /dev/null and b/doc/images/MTNDWidget.jpg differ diff --git a/doc/images/WindowsKeyShortcutGuide.jpg b/doc/images/WindowsKeyShortcutGuide.jpg new file mode 100644 index 00000000000..919e6e7bdfb Binary files /dev/null and b/doc/images/WindowsKeyShortcutGuide.jpg differ diff --git a/doc/images/example_powertoy/settings.png b/doc/images/example_powertoy/settings.png new file mode 100644 index 00000000000..2c97d71dcfc Binary files /dev/null and b/doc/images/example_powertoy/settings.png differ diff --git a/doc/images/runner/tray.png b/doc/images/runner/tray.png new file mode 100644 index 00000000000..42cfcf82230 Binary files /dev/null and b/doc/images/runner/tray.png differ diff --git a/doc/images/shortcut_guide/settings.png b/doc/images/shortcut_guide/settings.png new file mode 100644 index 00000000000..06ad3eb0d10 Binary files /dev/null and b/doc/images/shortcut_guide/settings.png differ diff --git a/doc/images/shortcut_guide/usage.png b/doc/images/shortcut_guide/usage.png new file mode 100644 index 00000000000..5954f86aefb Binary files /dev/null and b/doc/images/shortcut_guide/usage.png differ diff --git a/doc/planning/FancyZonesBacklog.md b/doc/planning/FancyZonesBacklog.md new file mode 100644 index 00000000000..c96fb843999 --- /dev/null +++ b/doc/planning/FancyZonesBacklog.md @@ -0,0 +1,27 @@ +# Backlog + +This file captures the prioritized list of issues the FancyZones team will tackle + +## On deck +Implement multi-mon editor support [331](https://github.com/JaneaSystems/powertoys/issues/331) +Add telemetry to new editor [332](https://github.com/JaneaSystems/powertoys/issues/332) + +## Backlog +Add tests to the new editor [333](https://github.com/JaneaSystems/powertoys/issues/333) +Hitting Esc while dragging should cancel the drag and not move the window into a zone [140](https://github.com/JaneaSystems/powertoys/issues/140) +Flash Zones is wayyyy too slow with multiple large monitors [137](https://github.com/JaneaSystems/powertoys/issues/137) +Cycle through windows in a Zone [145](https://github.com/JaneaSystems/powertoys/issues/145) +Minimize/restore windows in a zone as a group [144](https://github.com/JaneaSystems/powertoys/issues/144) +FancyZones should support custom layouts for different "environments" [149](https://github.com/JaneaSystems/powertoys/issues/149) +Win+arrow works between monitors [130](https://github.com/JaneaSystems/powertoys/issues/130) +Win+arrow is directional based on zone rect [131](https://github.com/JaneaSystems/powertoys/issues/131) +Dragging a zoned window should restore size to a checkpointed size instead of current rect [136](https://github.com/JaneaSystems/powertoys/issues/136) +FancyZones should merge with MTND and include zone moves in the pop-up [150](https://github.com/JaneaSystems/powertoys/issues/150) +Drag to edge of screen automatically switches virtual desktops [150](https://github.com/JaneaSystems/powertoys/issues/138) +Different color schemes [134](https://github.com/JaneaSystems/powertoys/issues/134) +Ensure you can easily see zone while dragging [131](https://github.com/JaneaSystems/powertoys/issues/131) +Visual updates for Win+Arrow [141](https://github.com/JaneaSystems/powertoys/issues/141) +Add a CLI for FancyZones [152](https://github.com/JaneaSystems/powertoys/issues/152) +Add "magnetic dragging and resizing" mode to FancyZones [153](https://github.com/JaneaSystems/powertoys/issues/153) +Create layout from current windows [127](https://github.com/JaneaSystems/powertoys/issues/127) +Zone sets that have a dynamic number of zones [128](https://github.com/JaneaSystems/powertoys/issues/128) diff --git a/doc/planning/PowerToysBacklog.md b/doc/planning/PowerToysBacklog.md new file mode 100644 index 00000000000..10caee1b523 --- /dev/null +++ b/doc/planning/PowerToysBacklog.md @@ -0,0 +1,25 @@ +# PowerToys Backlog + +The list below is the set of utilities we're considering and the rough priority order of the utilities. If you have feedback on the order of the utilities, please use the issues for each one to provide that feedback. Note that new features for existing utilities (dock / undock zone layouts for FancyZones) are tracked in teh abcklog for each utility. + +## On deck + +* Maximize to new desktop widget - The MTND widget shows a pop-up button when a user hovers over the maximize / restore button on any window. Clicking it creates a new desktop, sends the app to that desktop and maximizes the app on the new desktop. +* [Process terminate tool](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/Terminate%20Spec.md) +* [Batch file renamer](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/File%20Classification%20Spec.md) +* [Animated gif screen recorder](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/GIF%20Maker%20Spec.md) + +## Backlog + +HPlease use issues and votes to guide the project to suggest new ideas and help us prioritize the list below. + +1. [Keyboard shortcut manager](https://github.com/microsoft/PowerToys/issues/6) +2. [Win+R replacement](https://github.com/microsoft/PowerToys/issues/44) +3. Resrouce use tool (maps between a resource like a file handle to an app and vice-versa) +4. Performance analysis over time to track which processes have been slowing down your machine +5. Better Alt+Tab including browser tab integration and search for running apps +6. [Battery tracker](https://github.com/microsoft/PowerToys/issues/7) +7. [Quick resolution swaps in taskbar](https://github.com/microsoft/PowerToys/issues/27) +8. Mouse events without focus +9. Cmd (or PS or Bash) from here +10. Contents menu file browsing diff --git a/doc/planning/ShortcutGuideBacklog.md b/doc/planning/ShortcutGuideBacklog.md new file mode 100644 index 00000000000..82b378e93d2 --- /dev/null +++ b/doc/planning/ShortcutGuideBacklog.md @@ -0,0 +1,13 @@ +# Backlog + +This file captures the prioritized list of issues for the Windows key shortcut guide + +## On deck +Windows key shortcut guide animation performance is choppy [335](https://github.com/JaneaSystems/powertoys/issues/335) +Shortcut guide strings should be localized [336](https://github.com/JaneaSystems/powertoys/issues/336) + +## Backlog +Add Win+Shift+S to the WKSG (screenshot tool) [151](https://github.com/JaneaSystems/powertoys/issues/151) +Replace SVG with software-generated content. [90](https://github.com/JaneaSystems/powertoys/issues/90) +Shortcut sorting [21](https://github.com/JaneaSystems/powertoys/issues/21) +Make shortcut descriptors clickable [6](https://github.com/JaneaSystems/powertoys/issues/6) diff --git a/doc/specs/PowerToys-fancyzones.md b/doc/specs/PowerToys-fancyzones.md new file mode 100644 index 00000000000..7ab2953131c --- /dev/null +++ b/doc/specs/PowerToys-fancyzones.md @@ -0,0 +1,215 @@ +# FancyZones + +## FancyZones +FancyZones is the base class that runs the show. It uses hooks to monitor for windows entering and exiting the move/size loop and to listen for key presses for hotkey interception. For every connected display, it creates a ZoneWindow which is used to display the active ZoneSet per monitor for use when editing the layout or displaying the drop targets. A ZoneSet is composed of one or more Zones which are the locations where windows can be easily positioned. + +### SetWinEventHook +The main driving force behind FancyZones is the accessibility hook used to know when a window enters the move/size loop. It listens for EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, and EVENT_OBJECT_LOCATIONCHANGE. For each of these three events, it forwards on to the ZoneWindow associated with the monitor that the window being dragged is currently on. + +### Keyboard Hook +A low-level keyboard hook is installed in order to, optionally, intercept Window+Arrow hotkeys. Traditionally, Win+Left/Right arrow will move a window between Windows Snap regions. This hook allows FancyZones to use Win+Left/Right arrow to move windows between Zones. +The hook also allows using 0-9 to change the active ZoneSet during a drag operation. + +### Display Changes +During initial standup, FancyZones creates a ZoneWindow for each connected monitor. When it receives a WM_DISPLAYCHANGE, it updates the available ZoneWindows to reflect the state of the system (eg add a new ZoneWindow for newly connected monitor, delete ZoneWindow for disconnected monitor, etc) + +### Interface +``` +interface IFancyZones : public IUnknown +{ + // Returns the main application window + IFACEMETHOD_(HWND, GetWindow)() = 0; + + // Returns the global HINSTANCE for the process + IFACEMETHOD_(HINSTANCE, GetHInstance)() = 0; + + // Returns the global Settings object used to look up individual settings throughout the product + IFACEMETHOD_(Settings, GetSettings)() = 0; + + // Used in WinMain to initialize FancyZones and enter the message loop + IFACEMETHOD_(void, Run)() = 0; + + // Toggles the visibility of all ZoneWindows + IFACEMETHOD_(void, ToggleZoneViewers)() = 0; + + // Shows a single ZoneWindow in editor mode on the provided monitor + IFACEMETHOD_(void, ShowZoneEditorForMonitor)(_In_ HMONITOR monitor) = 0; + + // Returns true if we are currently detecting a movesize loop + IFACEMETHOD_(bool, InMoveSize)() = 0; + + // Called by the event hook in response to EVENT_SYSTEM_MOVESIZESTART + IFACEMETHOD(MoveSizeEnter)(_In_ HWND window, _In_ HMONITOR monitor, POINT ptScreen) = 0; + + // Called by the event hook in response to EVENT_SYSTEM_MOVESIZEEND + IFACEMETHOD(MoveSizeExit)(_In_ HWND window, POINT ptScreen) = 0; + + // Called by the event hook in response to EVENT_OBJECT_LOCATIONCHANGE + IFACEMETHOD(MoveSizeUpdate)(_In_ HMONITOR monitor, POINT ptScreen) = 0; + + // Called during startup or on WM_DISPLAYCHANGE to add a ZoneWindow to the collection + // There will be one ZoneWindow per connected monitor + IFACEMETHOD(AddZoneWindow)(_In_ IZoneWindow* zoneWindow, _In_ HMONITOR monitor) = 0; + + // Called in response to WM_DISPLAYCHANGE from the main application window + IFACEMETHOD_(void, OnDisplayChange)(DisplayChangeType changeType) = 0; + + // Used to move the specified HWND into Zone + // The ZoneSet used is found by looking up the monitor that window is currently on + // This gets called to keep windows in their current zones after a WM_DISPLAYCHANGE + IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(_In_ HWND window, int index) = 0; + + // Used to filter out windows that the hook should not be processing + // Currently checks if the window's GWL_STYLE has WS_MAXIMIZEBOX + IFACEMETHOD_(bool, IsInterestingWindow)(_In_ HWND window) = 0; + + // Called byt the event hook in response to EVENT_OBJECT_NAMECHANGE on the Desktop window + // The accessible name of the desktop window changes when the current virtual desktop changes + IFACEMETHOD_(void, VirtualDesktopChanged)() = 0; + + // Returns the GUID of the current active virtual desktop + IFACEMETHOD_(GUID, GetCurrentVirtualDesktopId)() = 0; + + // Called by the LL keyboard hook + // Used to override snap hotkeys and to change the active ZoneSet during a drag + IFACEMETHOD_(bool, OnKeyDown)(LPARAM lparam) = 0; + + // Keep windows positioned inside their zones when the active ZoneSet changes + IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0; +}; +``` + +## ZoneWindow +ZoneWindow is used to display the Zones a user can drop a window in during a drag operation, flash the Zones when the ZoneSet changes, and draw the Zone Editor UI when in edit mode. Basically, when a ZoneSet needs to be visualized, ZoneWindow does it. + +### Interface +``` +interface IZoneWindow : public IUnknown +{ + // Shows the ZoneWindow + // If activate is true, set foreground to the window otherwise just show + IFACEMETHOD(ShowZoneWindow)(bool activate) = 0; + + // Hide the ZoneWindow + IFACEMETHOD(HideZoneWindow)() = 0; + + // Called when the drag enters the monitor this ZoneWindow is assigned to + IFACEMETHOD(MoveSizeEnter)(_In_ HWND window, POINT ptScreen, DragMode dragMode) = 0; + + // Called when the drag exits the monitor + IFACEMETHOD(MoveSizeExit)(_In_ HWND window, POINT ptScreen) = 0; + + // Called when the drag updates position on this monitor + IFACEMETHOD(MoveSizeUpdate)(POINT ptScreen, DragMode dragMode) = 0; + + // Called when a drag ends and the window is not dropped in a Zone + IFACEMETHOD(MoveSizeCancel)() = 0; + + // Returns the DragMode of the current drag operation + // DragMode allows for overriding drag behavior via settings or via hotkey + IFACEMETHOD_(DragMode, GetDragMode)() = 0; + + // Part of the chain to move a window into a specific Zone + IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(_In_ HWND window, int index) = 0; + + // Used to cycle a window between zones via the hijacked snap hotkeys + IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(_In_ HWND window, DWORD vkCode) = 0; + + // Called in response to WM_DISPLAYCHANGE + // Allows cleanup, if necessary, since ZoneWindow will be destroyed shortly thereafter + IFACEMETHOD_(void, OnDisplayChange)(DisplayChangeType type) = 0; + + // Allows changing the active ZoneSet via key press either during a drag or while the ZoneWindow is in foreground + IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0; +}; +``` + +## ZoneSet +Collection of one or more Zones. Only one ZoneSet is active at a time per monitor. + +### Interface +``` +interface IZoneSet : public IUnknown +{ + // Gets the unique ID used to identify this ZoneSet + IFACEMETHOD_(GUID, GetId)() = 0; + + // Adds a Zone to the collection + IFACEMETHOD(AddZone)(_In_ Microsoft::WRL::ComPtr zone, bool front) = 0; + + // Removes a Zone from the collection + IFACEMETHOD(RemoveZone)(_In_ Microsoft::WRL::ComPtr zone) = 0; + + // Returns the topmost Zone at the given point + IFACEMETHOD_(Microsoft::WRL::ComPtr, ZoneFromPoint)(POINT pt) = 0; + + // Returns a Zone that the window is in + // Will return nullptr if the window is not in a Zone + IFACEMETHOD_(Microsoft::WRL::ComPtr, ZoneFromWindow)(_In_ HWND window) = 0; + + // Gets all the Zones + IFACEMETHOD_(std::vector>, GetZones)() = 0; + + // ZoneSetLayout + // * Grid - Pregenerated layout (2x2, 3x3, etc) + // * Row - Pregenerated layout in a single row + // * Focus - Pregenerated layout with a central focus Zone and fanned peripheral Zones + // * Custom - User generated Zone + IFACEMETHOD_(ZoneSetLayout, GetLayout)() = 0; + + // The amount of default padding between Zones in a generated layout + IFACEMETHOD_(int, GetInnerPadding)() = 0; + + // Makes a copy of the IZoneSet and marks it as ZoneSetLayout::Custom + IFACEMETHOD_(Microsoft::WRL::ComPtr, MakeCustomClone)() = 0; + + // Persists ZoneSet data to the registry + IFACEMETHOD_(void, Save)() = 0; + + // Moves a Zone to the front of the collection + IFACEMETHOD_(void, MoveZoneToFront)(_In_ Microsoft::WRL::ComPtr zone) = 0; + + // Moves a Zone to the back of the collection + IFACEMETHOD_(void, MoveZoneToBack)(_In_ Microsoft::WRL::ComPtr zone) = 0; + + // Part of the chain to move a window into a specific Zone + IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(_In_ HWND window, _In_ HWND zoneWindow, int index) = 0; + + // Part of the chain to move a window into a specific Zone + IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(_In_ HWND window, _In_ HWND zoneWindow, DWORD vkCode) = 0; + + // Called when a drag ends or leaves the monitor this ZoneWindow is on + // This will remove the window from its currently assigned Zone and assign it + // to a different Zone based on the current cursor position + IFACEMETHOD_(void, MoveSizeExit)(_In_ HWND window, _In_ HWND zoneWindow, _In_ POINT ptClient) = 0; +}; +``` + +## Zone +Basically a RECT and a map of HWND->RECT to keep track of where windows can be placed and which windows are currently in the Zone. + +### Interface +``` +interface IZone : public IUnknown +{ + // Returns the RECT that this Zone represents + IFACEMETHOD_(RECT, GetZoneRect)() = 0; + + // Returns true if the specified window is in this Zone's collection + IFACEMETHOD_(bool, ContainsWindow)(_In_ HWND window) = 0; + + // Adds the window the collection + IFACEMETHOD_(void, AddWindowToZone)(_In_ HWND window, _In_ HWND zoneWindow, bool stampZone) = 0; + + // Removes the window from the collection + IFACEMETHOD_(void, RemoveWindowFromZone)(_In_ HWND window, bool restoreSize) = 0; + + // Sets an id for this Zone + // The id will be unique per ZoneSet + IFACEMETHOD_(void, SetId)(size_t id) = 0; + + // Returns the id given to this Zone + IFACEMETHOD_(size_t, GetId)() = 0; +}; +``` + diff --git a/doc/specs/PowerToys-settings.md b/doc/specs/PowerToys-settings.md new file mode 100644 index 00000000000..6cd059e61c3 --- /dev/null +++ b/doc/specs/PowerToys-settings.md @@ -0,0 +1,57 @@ +# Power Toys Settings Framework and Core Infrastructure +The Power Toys app will have a settings framework that each Power Toy can plug into. The settings framework has a UI frame that creates a page for each Power Toy. The UI frame should use the Navigation View “hamburger” UI. Each Power Toy will represent its settings as a json blob as described below. + +Each Power Toy will line in a separate .dll and be run in a separate thread by the main Power Toys process. The main Power Toys .exe will expose key global Windows event handlers so that there is only one system-level hook for these critical events. The current set of Power Toys require these global events. This list will be amended as new Power Toys are authored that require additional global hooks. +* SetWinEventHook - FancyZones requires knowledge of when a window enters the move/size loop. It listens for EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, and EVENT_OBJECT_LOCATIONCHANGE messages from SetWinEventHook. +* Low-level keyboard hook - The Windows key Shortcut Guide and FancyZones both require low-level keybord hooks to intercept keyboard input and get a first chance to process it. Other Power Toys will require this as well + +* Each Power Toy must listen for 4 events: + * Enable – When invoked, enables the Power Toys’ functionality and performs any necessary initialization. Invoked with a JSON string from the persisted settings store + * Disable – When invoked, disables the Power Toys’ functionality and performs any clean-up to suspend all resource use + * OutputSettings – Return a json serialized blob of the settings for the Power Toy + * InputSettings – Invoked with a JSON string with updated settings from the UI which is then deserialized and the state is applied. If the settings cannot be applied by the Power Toy, the PT must return an error and an error string for the end user +* Each Power Toy may optionally provide one or more custom configuration UIs that can be invoked from its settings page + * Each custom UI is specified as a JSON string in the settings property bag + * The Power Toy must provide a named method that returns a serialized JSON settings string for the settings framework to call + * The method should launch UI to edit the settings but the UI shown must be asynchronous and not block the setting UI +* The Power Toys main .exe will provide a method called InvokeSettingsUI that will show the settings dialog for the calling Power Toy. +* Settings will be serialized by the settings framework and will be read at launch of the Power Toys framework and each Power Toy’s settings will be passed into the PT’s Enable method +* Settings will be serialized on a per-user basis +* The Settings JSON format will be versioned nad each payload must specify it's version attribute. The initial version is 1.0 + +## Power Toys Settings Object +The settings JSON object for each Power Toy should provide: +* Title string +* Icon +* Logo Image +* Credits string +* Credits link +* Settings property bag. Each item in the property bag has two items: + * String: display name + * String: property / editor type +* Version number: Currently only 1.0 is supported + +Property Bag of settings in priority order (type->editor) +* Bool->slide switch +* Int->free text box +* String->free text box +* Int ->Up/Down spinner +* Color-> Color picker +* Image->File picker, preview area, drag and drop +* Cursor->file picker and drop down, possibly an image +* Property Bag JSON string->Button to launch a custom editor from the Power Toy +* Method name to invoke. The method will return a serialized JSON string with the updated custom editor settings +* String to display on the button +* Percentage->Slider +* Time->Time picker +* Date->Date picker +* IP address->masked text box + +## PowerToys Main Settings Page +* Need to get Nick to help with the settings UI design (see attached for a whiteboard sketch) +* Need to have a settings page for overall PowerToys which will include the following + * Check for updates + * Startup at launch + * Enable / disable for each utility. + * This invokes the Enable and Disable events for the PowerToy and suspends all resource use including CPU, GPU, Networking, Disk I/O and memory commit +* The settings UI should have an “Apply” button which will push the settings object to diff --git a/doc/specs/Shared-hooks.md b/doc/specs/Shared-hooks.md new file mode 100644 index 00000000000..bc546f0970c --- /dev/null +++ b/doc/specs/Shared-hooks.md @@ -0,0 +1,85 @@ +# Shared hooks + +To minimize the performance impact on the machine only `runner` installs global hooks, passing the events to registered callbacks in each PowerToy module. + +When a PowerToy module is loaded, the `runner` calls the [`get_events()`](/src/modules/interface/powertoy_module_interface.h#L40) method to get a NULL-terminated array of NULL-terminated strings with the names of the events that the PowerToy wants to subscribe to. A `const wchar_t*` string is provided for each of the event names. + +Events are signalled by the `runner` calling the [`signal_event(name, data)`](/src/modules/interface/powertoy_module_interface.h#L53) method of the PowerToy module. The `name` parameter contains the NULL-terminated name of the event. The `data` parameter and the method return value are specific for each event. + +Currently supported hooks: + * `"ll_keyboard"` - [Low Level Keyboard Hook](#low-level-keyboard-hook) + * `"win_hook_event"` - [Windows Event Hook](#windows-event-hook) + +## Low Level Keyboard Hook + +This event is signaled whenever the user presses or releases a key on the keyboard. To subscribe to this event, add `"ll_keyboard"` to the table returned by the `get_events()` method. + +The PowerToys runner installs low-level keyboard hook using `SetWindowsHookEx(WH_KEYBOARD_LL, ...)`. See [this MSDN page](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v%3Dvs.85)) for details. + +When a keyboard event is signaled and `ncCode` equals `HC_ACTION`, the `wParam` and `lParam` event parameters are passed to all subscribed clients in the [`LowlevelKeyboardEvent`](/src/modules/interface/lowlevel_keyboard_event_data.h#L38-L41) struct. + +The `intptr_t data` event argument is a pointer to the `LowlevelKeyboardEvent` struct. + +A non-zero return value from any of the subscribed PowerToys will cause the runner hook proc to return 1, thus swallowing the keyboard event. + +Example usage, that makes Windows ignore the L key: + +```c++ +virtual const wchar_t** get_events() override { + static const wchar_t* events[2] = { ll_keyboard, + nullptr }; + return events; +} + +virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override { + if (wcscmp(name, ll_keyboard) == 0) { + auto& event = *(reinterpret_cast(data)); + // The L key has vkCode of 0x4C, see: + // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + if (event.wParam == WM_KEYDOWN && event.lParam->vkCode == 0x4C) { + return 1; + } else { + return 0; + } + } else { + return 0; + } +} +``` + +## Windows Event Hook + +This event is signaled for [a range of events](https://docs.microsoft.com/pl-pl/windows/win32/winauto/event-constants). To subscribe to this event, add `"win_hook_event"` to the table returned by the `get_events()` method. See [this MSDN doc](https://docs.microsoft.com/pl-pl/windows/win32/api/winuser/nf-winuser-setwineventhook) for details. + +The `intptr_t data` event argument is a pointer to the [`WinHookEvent`](/src/modules/interface/win_hook_event_data.h#L43-L50) struct. + +The return value of the event handler is ignored. + +Example usage, that detects a window being resized: + +```c++ +virtual const wchar_t** get_events() override { + static const wchar_t* events[2] = { win_hook_event, + nullptr }; + return events; +} + +virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override { + if (wcscmp(name, win_hook_event) == 0) { + auto& event = *(reinterpret_cast(data)); + switch (event.event) { + case EVENT_SYSTEM_MOVESIZESTART: + size_start(event.hwnd); + break; + case EVENT_SYSTEM_MOVESIZEEND: + size_end(event.hwnd); + break; + default: + break; + } + } + return 0; +} +``` + +Taking too long to process the events has negative impact on the whole system performance. To address this, the events are signaled from a different thread, not from the event hook callback itself. diff --git a/installer/PowerToysSetup.sln b/installer/PowerToysSetup.sln new file mode 100644 index 00000000000..e9e00eaa846 --- /dev/null +++ b/installer/PowerToysSetup.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29215.179 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysSetup", "PowerToysSetup\PowerToysSetup.wixproj", "{022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActions", "PowerToysSetupCustomActions\PowerToysSetupCustomActions.vcxproj", "{32F3882B-F2D6-4586-B5ED-11E39E522BD3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Debug|x64.ActiveCfg = Debug|x64 + {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Debug|x64.Build.0 = Debug|x64 + {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Release|x64.ActiveCfg = Release|x64 + {022A9D30-7C4F-416D-A9DF-5FF2661CC0AD}.Release|x64.Build.0 = Release|x64 + {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Debug|x64.ActiveCfg = Debug|x64 + {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Debug|x64.Build.0 = Debug|x64 + {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|x64.ActiveCfg = Release|x64 + {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B7A3DA30-D443-40FF-AC51-988AD41E3962} + EndGlobalSection +EndGlobal diff --git a/installer/PowerToysSetup/Bitmaps/banner.bmp b/installer/PowerToysSetup/Bitmaps/banner.bmp new file mode 100644 index 00000000000..9dadff72719 Binary files /dev/null and b/installer/PowerToysSetup/Bitmaps/banner.bmp differ diff --git a/installer/PowerToysSetup/Bitmaps/dialog.bmp b/installer/PowerToysSetup/Bitmaps/dialog.bmp new file mode 100644 index 00000000000..41dfb97f8a9 Binary files /dev/null and b/installer/PowerToysSetup/Bitmaps/dialog.bmp differ diff --git a/installer/PowerToysSetup/CustomDialogs/PTInstallDirDlg.wxs b/installer/PowerToysSetup/CustomDialogs/PTInstallDirDlg.wxs new file mode 100644 index 00000000000..3db54070481 --- /dev/null +++ b/installer/PowerToysSetup/CustomDialogs/PTInstallDirDlg.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetup/CustomDialogs/WixUI_PTInstallDir.wxs b/installer/PowerToysSetup/CustomDialogs/WixUI_PTInstallDir.wxs new file mode 100644 index 00000000000..af7ae970004 --- /dev/null +++ b/installer/PowerToysSetup/CustomDialogs/WixUI_PTInstallDir.wxs @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + "1"]]> + + 1 + + NOT Installed + Installed AND PATCH + + 1 + LicenseAccepted = "1" + + 1 + 1 + NOT WIXUI_DONTVALIDATEPATH + "1"]]> + WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" + 1 + 1 + + NOT Installed + Installed AND NOT PATCH + Installed AND PATCH + + 1 + + 1 + 1 + 1 + + + + + + + diff --git a/installer/PowerToysSetup/PowerToysSetup.wixproj b/installer/PowerToysSetup/PowerToysSetup.wixproj new file mode 100644 index 00000000000..ccbffd912d7 --- /dev/null +++ b/installer/PowerToysSetup/PowerToysSetup.wixproj @@ -0,0 +1,62 @@ + + + + Release + x64 + 3.10 + 022a9d30-7c4f-416d-a9df-5ff2661cc0ad + 2.0 + PowerToysSetup + Package + + + Debug + $(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + $(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + + + + + + + $(WixExtDir)\WixUtilExtension.dll + WixUtilExtension + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + + + + + + PowerToysSetupCustomActions + {32f3882b-f2d6-4586-b5ed-11e39e522bd3} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + + + + + + + \ No newline at end of file diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs new file mode 100644 index 00000000000..37d1941d65a --- /dev/null +++ b/installer/PowerToysSetup/Product.wxs @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + = 17134)]]> + + + + + + + + + + + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + 1 + NOT Installed + NOT Installed + Installed AND _REMOVE_ALL="Yes" + Installed AND _REMOVE_ALL="Yes" + Installed AND NOT (_REMOVE_ALL="Yes") + Installed AND NOT (_REMOVE_ALL="Yes") + + + + + + + + + + + + + + + NOT Installed and CREATESCHEDULEDTASK = 1 + + + + Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + NOT Installed + + + Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSTALLSTARTMENUSHORTCUT + + + + + + + + + + INSTALLDESKTOPSHORTCUT + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp new file mode 100644 index 00000000000..20639ab4c43 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -0,0 +1,528 @@ +#include "stdafx.h" + +#define SECURITY_WIN32 +#include +#pragma comment(lib, "Secur32.lib") +#include + +#include +#include +#pragma comment(lib, "taskschd.lib") +#pragma comment(lib, "comsupp.lib") + +#include +#include +#include + +using namespace std; + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToysInstaller", + // {e1d8165d-5cb6-5c74-3b51-bdfbfe4f7a3b} + (0xe1d8165d, 0x5cb6, 0x5c74, 0x3b, 0x51, 0xbd, 0xfb, 0xfe, 0x4f, 0x7a, 0x3b), + TraceLoggingOptionProjectTelemetry()); + +const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0' +const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' + +// Creates a Scheduled Task to run at logon for the current user. +// The path of the executable to run should be passed as the CustomActionData (Value). +// Based on the Task Scheduler Logon Trigger Example: +// https://docs.microsoft.com/en-us/windows/desktop/taskschd/logon-trigger-example--c---/ +UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + TCHAR username_domain[USERNAME_DOMAIN_LEN]; + TCHAR username[USERNAME_LEN]; + + std::wstring wstrTaskName; + + ITaskService *pService = NULL; + ITaskFolder *pTaskFolder = NULL; + ITaskDefinition *pTask = NULL; + IRegistrationInfo *pRegInfo = NULL; + ITaskSettings *pSettings = NULL; + ITriggerCollection *pTriggerCollection = NULL; + IRegisteredTask *pRegisteredTask = NULL; + + hr = WcaInitialize(hInstall, "CreateScheduledTaskCA"); + ExitOnFailure(hr, "Failed to initialize"); + + WcaLog(LOGMSG_STANDARD, "Initialized."); + + // ------------------------------------------------------ + // Get the Domain/Username for the trigger. + // + // This action needs to run as the system to get elevated privileges from the installation, + // so GetUserNameEx can't be used to get the current user details. + // The USERNAME and USERDOMAIN environment variables are used instead. + if (!GetEnvironmentVariable(L"USERNAME", username, USERNAME_LEN)) { + ExitWithLastError(hr, "Getting username failed: %x", hr); + } + if (!GetEnvironmentVariable(L"USERDOMAIN", username_domain, USERNAME_DOMAIN_LEN)) { + ExitWithLastError(hr, "Getting the user's domain failed: %x", hr); + } + wcscat_s(username_domain, L"\\"); + wcscat_s(username_domain, username); + + WcaLog(LOGMSG_STANDARD, "Current user detected: %ls", username_domain); + + // Task Name. + wstrTaskName = L"Autorun for "; + wstrTaskName += username; + + // Get the executable path passed to the custom action. + LPWSTR wszExecutablePath = NULL; + hr = WcaGetProperty(L"CustomActionData", &wszExecutablePath); + ExitOnFailure(hr, "Failed to get the executable path from CustomActionData."); + + // COM and Security Initialization is expected to have been done by the MSI. + // It couldn't be done in the DLL, anyway. + // ------------------------------------------------------ + // Create an instance of the Task Service. + hr = CoCreateInstance(CLSID_TaskScheduler, + NULL, + CLSCTX_INPROC_SERVER, + IID_ITaskService, + (void**)&pService); + ExitOnFailure(hr, "Failed to create an instance of ITaskService: %x", hr); + + // Connect to the task service. + hr = pService->Connect(_variant_t(), _variant_t(), + _variant_t(), _variant_t()); + ExitOnFailure(hr, "ITaskService::Connect failed: %x", hr); + + // ------------------------------------------------------ + // Get the PowerToys task folder. Creates it if it doesn't exist. + hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder); + if (FAILED(hr)) { + // Folder doesn't exist. Get the Root folder and create the PowerToys subfolder. + ITaskFolder *pRootFolder = NULL; + hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder); + ExitOnFailure(hr, "Cannot get Root Folder pointer: %x", hr); + hr = pRootFolder->CreateFolder(_bstr_t(L"\\PowerToys"), _variant_t(L""), &pTaskFolder); + if (FAILED(hr)) { + pRootFolder->Release(); + ExitOnFailure(hr, "Cannot create PowerToys task folder: %x", hr); + } + WcaLog(LOGMSG_STANDARD, "PowerToys task folder created."); + } + + // If the same task exists, remove it. + pTaskFolder->DeleteTask(_bstr_t(wstrTaskName.c_str()), 0); + + // Create the task builder object to create the task. + hr = pService->NewTask(0, &pTask); + ExitOnFailure(hr, "Failed to create a task definition: %x", hr); + + // ------------------------------------------------------ + // Get the registration info for setting the identification. + hr = pTask->get_RegistrationInfo(&pRegInfo); + ExitOnFailure(hr, "Cannot get identification pointer: %x", hr); + hr = pRegInfo->put_Author(_bstr_t(username_domain)); + ExitOnFailure(hr, "Cannot put identification info: %x", hr); + + // ------------------------------------------------------ + // Create the settings for the task + hr = pTask->get_Settings(&pSettings); + ExitOnFailure(hr, "Cannot get settings pointer: %x", hr); + + hr = pSettings->put_StartWhenAvailable(VARIANT_FALSE); + ExitOnFailure(hr, "Cannot put_StartWhenAvailable setting info: %x", hr); + hr = pSettings->put_StopIfGoingOnBatteries(VARIANT_FALSE); + ExitOnFailure(hr, "Cannot put_StopIfGoingOnBatteries setting info: %x", hr); + hr = pSettings->put_ExecutionTimeLimit(_bstr_t(L"PT0S")); //Unlimited + ExitOnFailure(hr, "Cannot put_ExecutionTimeLimit setting info: %x", hr); + hr = pSettings->put_DisallowStartIfOnBatteries(VARIANT_FALSE); + ExitOnFailure(hr, "Cannot put_DisallowStartIfOnBatteries setting info: %x", hr); + + // ------------------------------------------------------ + // Get the trigger collection to insert the logon trigger. + hr = pTask->get_Triggers(&pTriggerCollection); + ExitOnFailure(hr, "Cannot get trigger collection: %x", hr); + + // Add the logon trigger to the task. + ITrigger *pTrigger = NULL; + hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger); + ExitOnFailure(hr, "Cannot create the trigger: %x", hr); + + ILogonTrigger *pLogonTrigger = NULL; + hr = pTrigger->QueryInterface( + IID_ILogonTrigger, (void**)&pLogonTrigger); + pTrigger->Release(); + ExitOnFailure(hr, "QueryInterface call failed for ILogonTrigger: %x", hr); + + hr = pLogonTrigger->put_Id(_bstr_t(L"Trigger1")); + if (FAILED(hr)) { + WcaLogError(hr, "Cannot put the trigger ID: %x", hr); + } + + // Timing issues may make explorer not be started when the task runs. + // Add a little delay to mitigate this. + hr = pLogonTrigger->put_Delay(_bstr_t(L"PT03S")); + if (FAILED(hr)) { + WcaLogError(hr, "Cannot put the trigger delay: %x", hr); + } + + // Define the user. The task will execute when the user logs on. + // The specified user must be a user on this computer. + hr = pLogonTrigger->put_UserId(_bstr_t(username_domain)); + pLogonTrigger->Release(); + ExitOnFailure(hr, "Cannot add user ID to logon trigger: %x", hr); + + // ------------------------------------------------------ + // Add an Action to the task. This task will execute the path passed to this custom action. + IActionCollection *pActionCollection = NULL; + + // Get the task action collection pointer. + hr = pTask->get_Actions(&pActionCollection); + ExitOnFailure(hr, "Cannot get Task collection pointer: %x", hr); + + // Create the action, specifying that it is an executable action. + IAction *pAction = NULL; + hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction); + pActionCollection->Release(); + ExitOnFailure(hr, "Cannot create the action: %x", hr); + + IExecAction *pExecAction = NULL; + // QI for the executable task pointer. + hr = pAction->QueryInterface( + IID_IExecAction, (void**)&pExecAction); + pAction->Release(); + ExitOnFailure(hr, "QueryInterface call failed for IExecAction: %x", hr); + + // Set the path of the executable to PowerToys (passed as CustomActionData). + hr = pExecAction->put_Path(_bstr_t(wszExecutablePath)); + pExecAction->Release(); + ExitOnFailure(hr, "Cannot set path of executable: %x", hr); + + // ------------------------------------------------------ + // Create the principal for the task + IPrincipal *pPrincipal = NULL; + hr = pTask->get_Principal(&pPrincipal); + ExitOnFailure(hr, "Cannot get principal pointer: %x", hr); + + // Set up principal information: + hr = pPrincipal->put_Id(_bstr_t(L"Principal1")); + if (FAILED(hr)) { + WcaLogError(hr, "Cannot put the principal ID: %x", hr); + } + + hr = pPrincipal->put_UserId(_bstr_t(username_domain)); + if (FAILED(hr)) { + WcaLogError(hr, "Cannot put principal user Id: %x", hr); + } + + hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN); + if (FAILED(hr)) { + WcaLogError(hr, "Cannot put principal logon type: %x", hr); + } + + // Run the task with the highest available privileges. + hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); + pPrincipal->Release(); + ExitOnFailure(hr, "Cannot put principal run level: %x", hr); + + // ------------------------------------------------------ + // Save the task in the PowerToys folder. + hr = pTaskFolder->RegisterTaskDefinition( + _bstr_t(wstrTaskName.c_str()), + pTask, + TASK_CREATE_OR_UPDATE, + _variant_t(username_domain), + _variant_t(), + TASK_LOGON_INTERACTIVE_TOKEN, + _variant_t(L""), + &pRegisteredTask); + ExitOnFailure(hr, "Error saving the Task : %x", hr); + + WcaLog(LOGMSG_STANDARD, "Scheduled task created for the current user."); + +LExit: + ReleaseStr(wszExecutablePath); + if (pService) pService->Release(); + if (pTaskFolder) pTaskFolder->Release(); + if (pTask) pTask->Release(); + if (pRegInfo) pRegInfo->Release(); + if (pSettings) pSettings->Release(); + if (pTriggerCollection) pTriggerCollection->Release(); + if (pRegisteredTask) pRegisteredTask->Release(); + + if (!SUCCEEDED(hr)) { + PMSIHANDLE hRecord = MsiCreateRecord(0); + MsiRecordSetString(hRecord, 0, TEXT("Failed to create a scheduled task to start PowerToys at user login. You can re-try to create the scheduled task using the PowerToys settings.")); + MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_WARNING + MB_OK), hRecord); + } + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +// Removes all Scheduled Tasks in the PowerToys folder and deletes the folder afterwards. +// Based on the Task Scheduler Displaying Task Names and State example: +// https://docs.microsoft.com/en-us/windows/desktop/TaskSchd/displaying-task-names-and-state--c---/ +UINT __stdcall RemoveScheduledTasksCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + ITaskService *pService = NULL; + ITaskFolder *pTaskFolder = NULL; + IRegisteredTaskCollection* pTaskCollection = NULL; + + hr = WcaInitialize(hInstall, "RemoveScheduledTasksCA"); + ExitOnFailure(hr, "Failed to initialize"); + + WcaLog(LOGMSG_STANDARD, "Initialized."); + + // COM and Security Initialization is expected to have been done by the MSI. + // It couldn't be done in the DLL, anyway. + // ------------------------------------------------------ + // Create an instance of the Task Service. + hr = CoCreateInstance(CLSID_TaskScheduler, + NULL, + CLSCTX_INPROC_SERVER, + IID_ITaskService, + (void**)&pService); + ExitOnFailure(hr, "Failed to create an instance of ITaskService: %x", hr); + + // Connect to the task service. + hr = pService->Connect(_variant_t(), _variant_t(), + _variant_t(), _variant_t()); + ExitOnFailure(hr, "ITaskService::Connect failed: %x", hr); + + // ------------------------------------------------------ + // Get the PowerToys task folder. + hr = pService->GetFolder(_bstr_t(L"\\PowerToys"), &pTaskFolder); + if (FAILED(hr)) { + // Folder doesn't exist. No need to delete anything. + WcaLog(LOGMSG_STANDARD, "The PowerToys scheduled task folder wasn't found. Nothing to delete."); + hr = S_OK; + ExitFunction(); + } + + // ------------------------------------------------------- + // Get the registered tasks in the folder. + hr = pTaskFolder->GetTasks(TASK_ENUM_HIDDEN, &pTaskCollection); + ExitOnFailure(hr, "Cannot get the registered tasks: %x", hr); + + LONG numTasks = 0; + hr = pTaskCollection->get_Count(&numTasks); + for (LONG i = 0; i < numTasks; i++) { + // Delete all the tasks found. + // If some tasks can't be deleted, the folder won't be deleted later and the user will still be notified. + IRegisteredTask* pRegisteredTask = NULL; + hr = pTaskCollection->get_Item(_variant_t(i + 1), &pRegisteredTask); + if (SUCCEEDED(hr)) { + BSTR taskName = NULL; + hr = pRegisteredTask->get_Name(&taskName); + if (SUCCEEDED(hr)) { + hr = pTaskFolder->DeleteTask(taskName, NULL); + if (FAILED(hr)) { + WcaLogError(hr, "Cannot delete the '%S' task: %x", taskName, hr); + } + SysFreeString(taskName); + } else { + WcaLogError(hr, "Cannot get the registered task name: %x", hr); + } + pRegisteredTask->Release(); + } else { + WcaLogError(hr, "Cannot get the registered task item at index=%d: %x", i + 1, hr); + } + } + + // ------------------------------------------------------ + // Get the pointer to the root task folder and delete the PowerToys subfolder. + ITaskFolder *pRootFolder = NULL; + hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder); + ExitOnFailure(hr, "Cannot get Root Folder pointer: %x", hr); + hr = pRootFolder->DeleteFolder(_bstr_t(L"PowerToys"), NULL); + pRootFolder->Release(); + ExitOnFailure(hr, "Cannot delete the PowerToys folder: %x", hr); + + WcaLog(LOGMSG_STANDARD, "Deleted the PowerToys Task Scheduler folder."); + +LExit: + if (pService) pService->Release(); + if (pTaskFolder) pTaskFolder->Release(); + if (pTaskCollection) pTaskCollection->Release(); + + if (!SUCCEEDED(hr)) { + PMSIHANDLE hRecord = MsiCreateRecord(0); + MsiRecordSetString(hRecord, 0, TEXT("Failed to remove the PowerToys folder from the scheduled task. These can be removed manually later.")); + MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_WARNING + MB_OK), hRecord); + } + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogInstallSuccessCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogInstallSuccessCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Install::Success", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogInstallCancelCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogInstallCancelCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Install::Cancel", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogInstallFailCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogInstallFailCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Install::Fail", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogUninstallSuccessCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogUninstallSuccessCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Uninstall::Success", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogUninstallCancelCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogUninstallCancelCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Uninstall::Cancel", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogUninstallFailCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogUninstallFailCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Uninstall::Fail", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogRepairCancelCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogRepairCancelCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Repair::Cancel", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +UINT __stdcall TelemetryLogRepairFailCA(MSIHANDLE hInstall) { + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "TelemetryLogRepairFailCA"); + ExitOnFailure(hr, "Failed to initialize"); + + TraceLoggingWrite( + g_hProvider, + "Repair::Fail", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +// DllMain - Initialize and cleanup WiX custom action utils. +extern "C" BOOL WINAPI DllMain(__in HINSTANCE hInst, __in ULONG ulReason, __in LPVOID) { + switch (ulReason) { + case DLL_PROCESS_ATTACH: + WcaGlobalInitialize(hInst); + TraceLoggingRegister(g_hProvider); + break; + + case DLL_PROCESS_DETACH: + TraceLoggingUnregister(g_hProvider); + WcaGlobalFinalize(); + break; + } + + return TRUE; +} diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def new file mode 100644 index 00000000000..ff324e56571 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -0,0 +1,13 @@ +LIBRARY "PowerToysSetupCustomActions" + +EXPORTS + CreateScheduledTaskCA + RemoveScheduledTasksCA + TelemetryLogInstallSuccessCA + TelemetryLogInstallCancelCA + TelemetryLogInstallFailCA + TelemetryLogUninstallSuccessCA + TelemetryLogUninstallCancelCA + TelemetryLogUninstallFailCA + TelemetryLogRepairCancelCA + TelemetryLogRepairFailCA \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj new file mode 100644 index 00000000000..0c077a14a77 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj @@ -0,0 +1,116 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {32f3882b-f2d6-4586-b5ed-11e39e522bd3} + Win32Proj + PowerToysSetupCustomActions + 10.0 + + + + DynamicLibrary + Unicode + v142 + + + DynamicLibrary + Unicode + true + v142 + + + + + + + + + + + + + true + $(Platform)\$(Configuration)\ + + + false + $(Platform)\$(Configuration)\ + + + + Disabled + inc;telemetry;$(WIX)sdk\$(WixPlatformToolset)\inc;%(AdditionalIncludeDirectories) + WIN64;_DEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Use + Level3 + ProgramDatabase + + + msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies) + $(WIX)sdk\$(WixPlatformToolset)\lib\x64;%(AdditionalLibraryDirectories) + CustomAction.def + true + Windows + HighestAvailable + + + + + MaxSpeed + true + inc;telemetry;$(WIX)sdk\$(WixPlatformToolset)\inc;%(AdditionalIncludeDirectories) + WIN64;NDEBUG;_WINDOWS;_USRDLL;CUSTOMACTIONTEST_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + Use + Level3 + ProgramDatabase + + + msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies) + $(WIX)sdk\$(WixPlatformToolset)\lib\x64;%(AdditionalLibraryDirectories) + CustomAction.def + true + Windows + true + true + MachineX64 + HighestAvailable + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj.filters b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj.filters new file mode 100644 index 00000000000..03f3a4b8b46 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + + + + + + Telemetry + + + Telemetry + + + + + + + + {6e73ce5d-e715-4e7e-b796-c5d180b07ff2} + + + \ No newline at end of file diff --git a/installer/PowerToysSetupCustomActions/Telemetry/ProjectTelemetry.h b/installer/PowerToysSetupCustomActions/Telemetry/ProjectTelemetry.h new file mode 100644 index 00000000000..1289d692011 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/Telemetry/ProjectTelemetry.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +TRACELOGGING_DECLARE_PROVIDER(g_hProvider); diff --git a/installer/PowerToysSetupCustomActions/Telemetry/TraceLoggingDefines.h b/installer/PowerToysSetupCustomActions/Telemetry/TraceLoggingDefines.h new file mode 100644 index 00000000000..2543208f02b --- /dev/null +++ b/installer/PowerToysSetupCustomActions/Telemetry/TraceLoggingDefines.h @@ -0,0 +1,6 @@ +#pragma once + +#define TraceLoggingOptionProjectTelemetry() TraceLoggingOptionGroup(0x42749043, 0x438c, 0x46a2, 0x82, 0xbe, 0xc6, 0xcb, 0xeb, 0x19, 0x2f, 0xf2) +#define ProjectTelemetryPrivacyDataTag(tag) TraceLoggingUInt64((tag), "Ignore") +#define ProjectTelemetryTag_ProductAndServicePerformance 0x0u +#define PROJECT_KEYWORD_MEASURE 0x0 diff --git a/installer/PowerToysSetupCustomActions/stdafx.cpp b/installer/PowerToysSetupCustomActions/stdafx.cpp new file mode 100644 index 00000000000..9a5cc24eaa5 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/stdafx.cpp @@ -0,0 +1,4 @@ +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/installer/PowerToysSetupCustomActions/stdafx.h b/installer/PowerToysSetupCustomActions/stdafx.h new file mode 100644 index 00000000000..490d80ec146 --- /dev/null +++ b/installer/PowerToysSetupCustomActions/stdafx.h @@ -0,0 +1,13 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include +#include +#include + +// WiX Header Files: +#include + + +// TODO: reference additional headers your program requires here diff --git a/installer/README.md b/installer/README.md new file mode 100644 index 00000000000..86436094e26 --- /dev/null +++ b/installer/README.md @@ -0,0 +1,7 @@ +# PowerToys Setup Project + +## Build instructions + * Install the [WiX Toolset Visual Studio 2019 Extension](https://marketplace.visualstudio.com/items?itemName=RobMensching.WiXToolset). + * Install the [WiX Toolset build tools](https://wixtoolset.org/releases/) in the development machine. + * Open `powertoys.sln`, select the "Release" and "x64" configurations and build the `PowerToysSetup` project. + * The resulting installer will be built to `PowerToysSetup\bin\Release\PowerToysSetup.msi`. diff --git a/src/README.md b/src/README.md new file mode 100644 index 00000000000..3e424f2f098 --- /dev/null +++ b/src/README.md @@ -0,0 +1,8 @@ +# PowerToys Source Code + +## Code organization +The PowerToys are splitted into DLLs for each PowerToy module ([`modules`](/src/modules) folder), and an executable ([`runner`](/src/runner) folder) that loads and manages those DLLs. + +The settings window is a separate executable, contained in [`editor`](/src/editor) folder. In utilizes a WebView to display a HTML-based settings window (contained in [`settings-web`](/src/settings-web) folder). + +The [`common`](/src/common) contains code for a static libary with helper functions, used by both the runner and the PowerToys modules. diff --git a/src/common/README.md b/src/common/README.md new file mode 100644 index 00000000000..9fbc0a2f366 --- /dev/null +++ b/src/common/README.md @@ -0,0 +1,48 @@ +# Introduction +The common lib, as the name suggests, contains code shared by multiple PowerToys components and modules. + +# Classes and structures + +#### class Animation: [header](./animation.h) [source](./animation.cpp) +Animation helper class with two easing-in animations: linear and exponential. + +#### class AsyncMessageQueue: [header](./async_message_queue.h) +Header-only asynchronous message queue. Used by `TwoWayPipeMessageIPC`. + +#### class TwoWayPipeMessageIPC: [header](./two_way_pipe_message_ipc.h) +Header-only asynchronous IPC messaging class. Used by the runner to communicate with the settings window. + +#### class D2DSVG: [header](./d2d_svg.h) [source](./d2d_svg.cpp) +Class for loading, rendering and for some basic modifications of SVG graphics. + +#### class D2DText: [header](./d2d_text.h) [source](./d2d_text.cpp) +Class for rendering text using DirectX. + +#### class D2DWindow: [header](./d2d_window.h) [source](./d2d_window.cpp) +Base class for creating borderless windows, with DirectX enabled rendering pipeline. + +#### class DPIAware: [header](./dpi_aware.h) [source](./dpi_aware.cpp) +Helper class for creating DPI-aware applications. + +#### struct MonitorInfo: [header](./monitors.h) [source](./monitors.cpp) +Class for obtaining information about physical displays connected to the machine. + +#### class Settings, class PowerToyValues, class CustomActionObject: [header](./settings_objects.h) [source](./settings_objects.cpp) +Classes used to define settings screens for the PowerToys modules. + +#### class Tasklist: [header](./tasklist_positions.h) [source](./tasklist_positions.cpp) +Class that can detect the position of the windows buttons on the taskbar. It also detects which window will react to pressing `WinKey + number`. + +#### struct WindowsColors: [header](./windows_colors.h) [source](./windows_colors.cpp) +Class for detecting the current Windows color scheme. + +# Helpers + +#### Common helpers: [header](./common.h) [source](./common.cpp) +Various helper functions. + +#### Settings helpers: [header](./settings_helpers.h) +Helper methods for the settings. + +#### Start visible helper: [header](./start_visible.h) [source](./start_visible.cpp) +Contains function to test if the Start menu is visible. diff --git a/src/common/Telemetry/PowerToys.wprp b/src/common/Telemetry/PowerToys.wprp new file mode 100644 index 00000000000..70ccf172aa1 --- /dev/null +++ b/src/common/Telemetry/PowerToys.wprp @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/common/Telemetry/ProjectTelemetry.h b/src/common/Telemetry/ProjectTelemetry.h new file mode 100644 index 00000000000..1289d692011 --- /dev/null +++ b/src/common/Telemetry/ProjectTelemetry.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +TRACELOGGING_DECLARE_PROVIDER(g_hProvider); diff --git a/src/common/Telemetry/TraceLoggingDefines.h b/src/common/Telemetry/TraceLoggingDefines.h new file mode 100644 index 00000000000..2543208f02b --- /dev/null +++ b/src/common/Telemetry/TraceLoggingDefines.h @@ -0,0 +1,6 @@ +#pragma once + +#define TraceLoggingOptionProjectTelemetry() TraceLoggingOptionGroup(0x42749043, 0x438c, 0x46a2, 0x82, 0xbe, 0xc6, 0xcb, 0xeb, 0x19, 0x2f, 0xf2) +#define ProjectTelemetryPrivacyDataTag(tag) TraceLoggingUInt64((tag), "Ignore") +#define ProjectTelemetryTag_ProductAndServicePerformance 0x0u +#define PROJECT_KEYWORD_MEASURE 0x0 diff --git a/src/common/Telemetry/readme.md b/src/common/Telemetry/readme.md new file mode 100644 index 00000000000..9d88e81e480 --- /dev/null +++ b/src/common/Telemetry/readme.md @@ -0,0 +1,25 @@ + +# Overview + +Telemetry from the PowerToys provider can be captured using the PowerToys.wprp file and WPR. + +## Starting trace capture + +To capture a trace for the PowerToys provider, run the following: + +`wpr.exe -start "PowerToys.wprp"` + +## Stopping trace capture + +To capture a trace for the PowerToys provider, run the following: + +`wpr.exe -Stop "Trace.etl"` + +## Viewing Events + +Open the trace.etl file in WPA. + +## Additional Resources +[Tracelogging on MSDN](https://docs.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal) + +[Recording and Viewing Events](https://docs.microsoft.com/en-us/windows/win32/tracelogging/tracelogging-record-and-display-tracelogging-events) \ No newline at end of file diff --git a/src/common/UnitTests-CommonLib/Settings.Tests.cpp b/src/common/UnitTests-CommonLib/Settings.Tests.cpp new file mode 100644 index 00000000000..470806c529d --- /dev/null +++ b/src/common/UnitTests-CommonLib/Settings.Tests.cpp @@ -0,0 +1,62 @@ +#include "pch.h" +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace PowerToysSettings; + +namespace UnitTestsCommonLib +{ + TEST_CLASS(SettingsUnitTests) + { + private: + const std::wstring m_json = L"{\"name\":\"Module Name\",\"properties\" : {\"bool_toggle_true\":{\"value\":true},\"bool_toggle_false\":{\"value\":false},\"color_picker\" : {\"value\":\"#ff8d12\"},\"int_spinner\" : {\"value\":10},\"string_text\" : {\"value\":\"a quick fox\"}},\"version\" : \"1.0\" }"; + + public: + TEST_METHOD(LoadFromJsonBoolTrue) + { + PowerToyValues values = PowerToyValues::from_json_string(m_json); + Assert::IsTrue(values.is_bool_value(L"bool_toggle_true")); + + bool value = values.get_bool_value(L"bool_toggle_true"); + Assert::AreEqual(true, value); + } + + TEST_METHOD(LoadFromJsonBoolFalse) + { + PowerToyValues values = PowerToyValues::from_json_string(m_json); + Assert::IsTrue(values.is_bool_value(L"bool_toggle_false")); + + bool value = values.get_bool_value(L"bool_toggle_false"); + Assert::AreEqual(false, value); + } + + TEST_METHOD(LoadFromJsonInt) + { + PowerToyValues values = PowerToyValues::from_json_string(m_json); + Assert::IsTrue(values.is_int_value(L"int_spinner")); + + int value = values.get_int_value(L"int_spinner"); + Assert::AreEqual(10, value); + } + + TEST_METHOD(LoadFromJsonString) + { + PowerToyValues values = PowerToyValues::from_json_string(m_json); + Assert::IsTrue(values.is_string_value(L"string_text")); + + std::wstring value = values.get_string_value(L"string_text"); + std::wstring expected = L"a quick fox"; + Assert::AreEqual(expected, value); + } + + TEST_METHOD(LoadFromJsonColorPicker) + { + PowerToyValues values = PowerToyValues::from_json_string(m_json); + Assert::IsTrue(values.is_string_value(L"color_picker")); + + std::wstring value = values.get_string_value(L"color_picker"); + std::wstring expected = L"#ff8d12"; + Assert::AreEqual(expected, value); + } + }; +} diff --git a/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj b/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj new file mode 100644 index 00000000000..75ea7a35296 --- /dev/null +++ b/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj @@ -0,0 +1,115 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {1A066C63-64B3-45F8-92FE-664E1CCE8077} + Win32Proj + UnitTestsCommonLib + 10.0 + NativeUnitTestProject + + + + DynamicLibrary + true + v142 + Unicode + false + + + DynamicLibrary + false + v142 + true + Unicode + false + + + + + + + + + + + + + + + false + + + true + + + + Use + Level3 + MaxSpeed + true + true + true + ..\;..\Telemetry;..\..\..\deps\cpprestsdk\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + stdcpplatest + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + RuntimeObject.lib;%(AdditionalDependencies) + + + + + Use + Level3 + Disabled + true + $(VCInstallDir)UnitTest\include;..\;..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + true + pch.h + MultiThreadedDebug + stdcpplatest + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + RuntimeObject.lib;%(AdditionalDependencies) + + + + + Create + Create + + + + + + + + + {74485049-c722-400f-abe5-86ac52d929b3} + + + + + + \ No newline at end of file diff --git a/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj.filters b/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj.filters new file mode 100644 index 00000000000..15ce6b75d8b --- /dev/null +++ b/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/src/common/UnitTests-CommonLib/pch.cpp b/src/common/UnitTests-CommonLib/pch.cpp new file mode 100644 index 00000000000..64b7eef6d6b --- /dev/null +++ b/src/common/UnitTests-CommonLib/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/common/UnitTests-CommonLib/pch.h b/src/common/UnitTests-CommonLib/pch.h new file mode 100644 index 00000000000..d7c448a6eda --- /dev/null +++ b/src/common/UnitTests-CommonLib/pch.h @@ -0,0 +1,16 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include +#include + +#include "CppUnitTest.h" + +#endif //PCH_H diff --git a/src/common/animation.cpp b/src/common/animation.cpp new file mode 100644 index 00000000000..363d5efa9dc --- /dev/null +++ b/src/common/animation.cpp @@ -0,0 +1,43 @@ +#include "pch.h" +#include "animation.h" + +Animation::Animation(double duration, double start, double stop) : + start_value(start), end_value(stop), duration(duration), start(std::chrono::high_resolution_clock::now()) { } + +void Animation::reset() { + start = std::chrono::high_resolution_clock::now(); +} +void Animation::reset(double duration) { + this->duration = duration; + reset(); +} +void Animation::reset(double duration, double start, double stop) { + start_value = start; + end_value = stop; + reset(duration); +} + +static double ease_out_expo(double t) { + return 1 - pow(2, -8 * t); +} + +double Animation::apply_animation_function(double t, AnimFunctions apply_function) const { + switch (apply_function) { + case EASE_OUT_EXPO: + return ease_out_expo(t); + case LINEAR: + default: + return t; + } +} + +double Animation::value(AnimFunctions apply_function) const { + auto anim_duration = std::chrono::high_resolution_clock::now() - start; + double t = std::chrono::duration(anim_duration).count() / duration; + if (t >= 1) + return end_value; + return start_value + (end_value - start_value) * apply_animation_function(t, apply_function); +} +bool Animation::done() const { + return std::chrono::high_resolution_clock::now() - start >= std::chrono::duration(duration); +} diff --git a/src/common/animation.h b/src/common/animation.h new file mode 100644 index 00000000000..25384919ec2 --- /dev/null +++ b/src/common/animation.h @@ -0,0 +1,31 @@ +#pragma once +#include + +/* + Usage: + When creating animation contstructor takes one parameter - how long + should the animation take in seconds. + + Call reset() when starting animation. + + When redering, call value() to get value from 0 to 1 - depending on animation + progress. +*/ +class Animation { +public: + enum AnimFunctions { + LINEAR = 0, + EASE_OUT_EXPO + }; + + Animation(double duration = 1, double start = 0, double stop = 1); + void reset(); + void reset(double duration); + void reset(double duration, double start, double stop); + double value(AnimFunctions apply_function) const; + bool done() const; +private: + double apply_animation_function(double t, AnimFunctions apply_function) const; + std::chrono::high_resolution_clock::time_point start; + double start_value, end_value, duration; +}; diff --git a/src/common/async_message_queue.h b/src/common/async_message_queue.h new file mode 100644 index 00000000000..a5bfb4570ab --- /dev/null +++ b/src/common/async_message_queue.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include +#include + +class AsyncMessageQueue { +private: + std::mutex queue_mutex; + std::queue message_queue; + std::condition_variable message_ready; + bool interrupted = false; + + //Disable copy + AsyncMessageQueue(const AsyncMessageQueue&); + AsyncMessageQueue& operator=(const AsyncMessageQueue&); + +public: + AsyncMessageQueue() { + } + void queue_message(std::wstring message) { + this->queue_mutex.lock(); + this->message_queue.push(message); + this->queue_mutex.unlock(); + this->message_ready.notify_one(); + } + std::wstring pop_message() { + std::unique_lock lock(this->queue_mutex); + while (message_queue.empty() && !this->interrupted) { + this->message_ready.wait(lock); + } + if (this->interrupted) { + //Just returns a empty string if the queue was interrupted. + return std::wstring(L""); + } + std::wstring message = this->message_queue.front(); + this->message_queue.pop(); + return message; + } + void interrupt() { + this->queue_mutex.lock(); + this->interrupted = true; + this->queue_mutex.unlock(); + this->message_ready.notify_all(); + } +}; diff --git a/src/common/common.cpp b/src/common/common.cpp new file mode 100644 index 00000000000..2ecba4746b1 --- /dev/null +++ b/src/common/common.cpp @@ -0,0 +1,152 @@ +#include "pch.h" +#include "common.h" +#include +#pragma comment(lib, "dwmapi.lib") +#include + + +std::optional get_button_pos(HWND hwnd) { + RECT button; + if (DwmGetWindowAttribute(hwnd, DWMWA_CAPTION_BUTTON_BOUNDS, &button, sizeof(RECT)) == S_OK) { + return button; + } else { + return {}; + } +} + +std::optional get_window_pos(HWND hwnd) { + RECT window; + if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &window, sizeof(window)) == S_OK) { + return window; + } else { + return {}; + } +} + +std::optional get_mouse_pos() { + POINT point; + if (GetCursorPos(&point) == 0) { + return {}; + } else { + return point; + } +} + +int width(const RECT& rect) { + return rect.right - rect.left; +} + +int height(const RECT& rect) { + return rect.bottom - rect.top; +} + +bool operator<(const RECT& lhs, const RECT& rhs) { + auto lhs_tuple = std::make_tuple(lhs.left, lhs.right, lhs.top, lhs.bottom); + auto rhs_tuple = std::make_tuple(rhs.left, rhs.right, rhs.top, rhs.bottom); + return lhs_tuple < rhs_tuple; +} + +RECT keep_rect_inside_rect(const RECT& small_rect, const RECT& big_rect) { + RECT result = small_rect; + if ((result.right - result.left) > (big_rect.right - big_rect.left)) { + // small_rect is too big horizontally. resize it. + result.right = big_rect.right; + result.left = big_rect.left; + } else { + if (result.right > big_rect.right) { + // move the rect left. + result.left -= result.right-big_rect.right; + result.right -= result.right-big_rect.right; + } + if (result.left < big_rect.left) { + // move the rect right. + result.right += big_rect.left-result.left; + result.left += big_rect.left-result.left; + } + } + if ((result.bottom - result.top) > (big_rect.bottom - big_rect.top)) { + // small_rect is too big vertically. resize it. + result.bottom = big_rect.bottom; + result.top = big_rect.top; + } else { + if (result.bottom > big_rect.bottom) { + // move the rect up. + result.top -= result.bottom-big_rect.bottom; + result.bottom -= result.bottom-big_rect.bottom; + } + if (result.top < big_rect.top) { + // move the rect down. + result.bottom += big_rect.top-result.top; + result.top += big_rect.top-result.top; + } + } + return result; +} + +int run_message_loop() { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return static_cast(msg.wParam); +} + +void show_last_error_message(LPCWSTR lpszFunction, DWORD dw) { + // Retrieve the system error message for the error code + LPWSTR lpMsgBuf = NULL; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + lpMsgBuf, + 0, NULL) > 0) { + // Display the error message and exit the process + LPWSTR lpDisplayBuf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, (lstrlenW(lpMsgBuf) + lstrlenW(lpszFunction) + 40) * sizeof(WCHAR)); + if (lpDisplayBuf != NULL) { + StringCchPrintfW(lpDisplayBuf, + LocalSize(lpDisplayBuf) / sizeof(WCHAR), + L"%s failed with error %d: %s", + lpszFunction, dw, lpMsgBuf); + MessageBoxW(NULL, (LPCTSTR)lpDisplayBuf, L"Error", MB_OK); + LocalFree(lpDisplayBuf); + } + LocalFree(lpMsgBuf); + } +} + +WindowState get_window_state(HWND hwnd) { + WINDOWPLACEMENT placement; + placement.length = sizeof(WINDOWPLACEMENT); + if (GetWindowPlacement(hwnd, &placement) == 0) { + return UNKNONW; + } + if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED || IsIconic(hwnd)) { + return MINIMIZED; + } + if (placement.showCmd == SW_MAXIMIZE || placement.showCmd == SW_SHOWMAXIMIZED) { + return MAXIMIZED; + } + auto rectp = get_window_pos(hwnd); + if (!rectp) { + return UNKNONW; + } + auto rect = *rectp; + MONITORINFO monitor; + monitor.cbSize = sizeof(MONITORINFO); + auto h_monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + GetMonitorInfo(h_monitor, &monitor); + bool top_left = monitor.rcWork.top == rect.top && monitor.rcWork.left == rect.left; + bool bottom_left = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.left == rect.left; + bool top_right = monitor.rcWork.top == rect.top && monitor.rcWork.right == rect.right; + bool bottom_right = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.right == rect.right; + if (top_left && bottom_left) return SNAPED_LEFT; + if (top_left) return SNAPED_TOP_LEFT; + if (bottom_left) return SNAPED_BOTTOM_LEFT; + if (top_right && bottom_right) return SNAPED_RIGHT; + if (top_right) return SNAPED_TOP_RIGHT; + if (bottom_right) return SNAPED_BOTTOM_RIGHT; + return RESTORED; +} \ No newline at end of file diff --git a/src/common/common.h b/src/common/common.h new file mode 100644 index 00000000000..f32c80d7b0d --- /dev/null +++ b/src/common/common.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +// Returns RECT with positions of the minmize/maximize buttons of the given window. +// Does not always work, since some apps draw custom toolbars. +std::optional get_button_pos(HWND hwnd); +// Gets position of given window. +std::optional get_window_pos(HWND hwnd); +// Gets mouse postion. +std::optional get_mouse_pos(); +// Calculate sizes +int width(const RECT& rect); +int height(const RECT& rect); +// Compare rects +bool operator<(const RECT& lhs, const RECT& rhs); +// Moves and/or resizes small_rect to fit inside big_rect. +RECT keep_rect_inside_rect(const RECT& small_rect, const RECT& big_rect); +// Initializes and runs windows message loop +int run_message_loop(); + +void show_last_error_message(LPCWSTR lpszFunction, DWORD dw); + +enum WindowState { + UNKNONW, + MINIMIZED, + MAXIMIZED, + SNAPED_TOP_LEFT, + SNAPED_LEFT, + SNAPED_BOTTOM_LEFT, + SNAPED_TOP_RIGHT, + SNAPED_RIGHT, + SNAPED_BOTTOM_RIGHT, + RESTORED +}; +WindowState get_window_state(HWND hwnd); diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj new file mode 100644 index 00000000000..71235261751 --- /dev/null +++ b/src/common/common.vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {74485049-C722-400F-ABE5-86AC52D929B3} + Win32Proj + common + 10.0 + common + + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + pch.h + stdcpplatest + MultiThreadedDebug + inc;telemetry;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) + + + Windows + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + stdcpplatest + pch.h + MultiThreaded + inc;telemetry;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + + + + {4e577735-dfab-41af-8a6e-b6e8872a2928} + + + + + + \ No newline at end of file diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters new file mode 100644 index 00000000000..c6e4c641456 --- /dev/null +++ b/src/common/common.vcxproj.filters @@ -0,0 +1,109 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {ed0f9961-6b12-408b-8dbc-fed779a557ac} + + + {3e9f944e-5d97-4a28-8865-2eff3a3568e7} + + + + + Header Files\Direct2D + + + Header Files\Direct2D + + + Header Files\Direct2D + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Telemetry + + + Header Files\Telemetry + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/src/common/d2d_svg.cpp b/src/common/d2d_svg.cpp new file mode 100644 index 00000000000..86c6c7c815d --- /dev/null +++ b/src/common/d2d_svg.cpp @@ -0,0 +1,105 @@ +#include "pch.h" +#include "d2d_svg.h" + +D2DSVG& D2DSVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) { + svg = nullptr; + winrt::com_ptr svg_stream; + winrt::check_hresult(SHCreateStreamOnFileEx(filename.c_str(), + STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, + nullptr, + svg_stream.put())); + + winrt::check_hresult(d2d_dc->CreateSvgDocument( + svg_stream.get(), + D2D1::SizeF(1, 1), + svg.put())); + + winrt::com_ptr root; + svg->GetRoot(root.put()); + float tmp; + winrt::check_hresult(root->GetAttributeValue(L"width", &tmp)); + svg_width = (int)tmp; + winrt::check_hresult(root->GetAttributeValue(L"height", &tmp)); + svg_height = (int)tmp; + return *this; +} + +D2DSVG& D2DSVG::resize(int x, int y, int width, int height, float fill, float max_scale) { + // Center + transform = D2D1::Matrix3x2F::Identity(); + transform = transform * D2D1::Matrix3x2F::Translation((width - svg_width) / 2.0f, (height - svg_height) / 2.0f); + float h_scale = fill * height / svg_height; + float v_scale = fill * width / svg_width; + used_scale = min(h_scale, v_scale); + if (max_scale > 0) { + used_scale = min(used_scale, max_scale); + } + transform = transform * D2D1::Matrix3x2F::Scale(used_scale, used_scale, D2D1::Point2F(width / 2.0f, height / 2.0f)); + transform = transform * D2D1::Matrix3x2F::Translation((float)x, (float)y); + return *this; +} + +D2DSVG& D2DSVG::recolor(uint32_t oldcolor, uint32_t newcolor) { + auto new_color = D2D1::ColorF(newcolor & 0xFFFFFF, 1); + auto old_color = D2D1::ColorF(oldcolor & 0xFFFFFF, 1); + std::function recurse = [&](ID2D1SvgElement* element) { + if (!element) + return; + if (element->IsAttributeSpecified(L"fill")) { + D2D1_COLOR_F elem_fill; + winrt::com_ptr paint; + element->GetAttributeValue(L"fill", paint.put()); + paint->GetColor(&elem_fill); + if (elem_fill.r == old_color.r && elem_fill.g == old_color.g && elem_fill.b == old_color.b) { + winrt::check_hresult(element->SetAttributeValue(L"fill", new_color)); + } + } + winrt::com_ptr sub; + element->GetFirstChild(sub.put()); + while (sub) { + recurse(sub.get()); + winrt::com_ptr next; + element->GetNextChild(sub.get(), next.put()); + sub = next; + } + }; + winrt::com_ptr root; + svg->GetRoot(root.put()); + recurse(root.get()); + return *this; +} + + +D2DSVG& D2DSVG::render(ID2D1DeviceContext5* d2d_dc) { + D2D1_MATRIX_3X2_F current; + d2d_dc->GetTransform(¤t); + d2d_dc->SetTransform(transform * current); + d2d_dc->DrawSvgDocument(svg.get()); + d2d_dc->SetTransform(current); + return *this; +} + +D2DSVG& D2DSVG::toggle_element(const wchar_t* id, bool visible) { + winrt::com_ptr element; + if (svg->FindElementById(id, element.put()) != S_OK) + return *this; + if (!element) + return *this; + element->SetAttributeValue(L"display", visible ? D2D1_SVG_DISPLAY::D2D1_SVG_DISPLAY_INLINE : D2D1_SVG_DISPLAY::D2D1_SVG_DISPLAY_NONE); + return *this; +} + +winrt::com_ptr D2DSVG::find_element(const std::wstring& id) { + winrt::com_ptr< ID2D1SvgElement> element; + winrt::check_hresult(svg->FindElementById(id.c_str(), element.put())); + return element; +} + +D2D1_RECT_F D2DSVG::rescale(D2D1_RECT_F rect) { + D2D1_RECT_F result; + auto src = reinterpret_cast(&rect); + auto dst = reinterpret_cast(&result); + dst[0] = src[0] * transform; + dst[1] = src[1] * transform; + return result; +} diff --git a/src/common/d2d_svg.h b/src/common/d2d_svg.h new file mode 100644 index 00000000000..b8892a73a89 --- /dev/null +++ b/src/common/d2d_svg.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include + +class D2DSVG { +public: + D2DSVG& load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc); + D2DSVG& resize(int x, int y, int width, int height, float fill, float max_scale = -1.0f); + D2DSVG& render(ID2D1DeviceContext5* d2d_dc); + D2DSVG& recolor(uint32_t oldcolor, uint32_t newcolor); + float get_scale() const { return used_scale; } + int width() const { return svg_width; } + int height() const { return svg_height; } + D2DSVG& toggle_element(const wchar_t* id, bool visible); + winrt::com_ptr find_element(const std::wstring& id); + D2D1_RECT_F rescale(D2D1_RECT_F rect); +protected: + float used_scale = 1.0f; + winrt::com_ptr svg; + int svg_width = -1, svg_height = -1; + D2D1::Matrix3x2F transform; +}; diff --git a/src/common/d2d_text.cpp b/src/common/d2d_text.cpp new file mode 100644 index 00000000000..439fc4cacc5 --- /dev/null +++ b/src/common/d2d_text.cpp @@ -0,0 +1,48 @@ +#include "pch.h" +#include "d2d_text.h" + +D2DText::D2DText(float text_size, float scale) { + winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast(factory.put_void()))); + resize(text_size, scale); + winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); + winrt::check_hresult(format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); +} + +D2DText & D2DText::resize(float text_size, float scale) { + format = nullptr; + winrt::check_hresult(factory->CreateTextFormat(L"Segoe UI", + nullptr, + DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + text_size * scale, + L"en-us", + format.put())); + winrt::check_hresult(format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); + return *this; +} + +D2DText & D2DText::set_aligment_left() { + winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); + return *this; +} + +D2DText & D2DText::set_aligment_center() { + winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); + return *this; +} + +D2DText & D2DText::set_aligment_right() { + winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING)); + return *this; +} + +void D2DText::write(ID2D1DeviceContext5 * d2d_dc, D2D1_COLOR_F color, D2D1_RECT_F rect, std::wstring text) { + winrt::com_ptr brush; + d2d_dc->CreateSolidColorBrush(color, brush.put()); + d2d_dc->DrawText(text.c_str(), + (UINT32)text.length(), + format.get(), + rect, + brush.get()); +} diff --git a/src/common/d2d_text.h b/src/common/d2d_text.h new file mode 100644 index 00000000000..c3d0e0ada49 --- /dev/null +++ b/src/common/d2d_text.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +class D2DText { +public: + D2DText(float text_size = 15.0f, float scale = 1.0f); + D2DText& resize(float text_size, float scale); + D2DText& set_aligment_left(); + D2DText& set_aligment_center(); + D2DText& set_aligment_right(); + void write(ID2D1DeviceContext5* d2d_dc, D2D1_COLOR_F color, D2D1_RECT_F rect, std::wstring text); +private: + winrt::com_ptr factory; + winrt::com_ptr format; +}; diff --git a/src/common/d2d_window.cpp b/src/common/d2d_window.cpp new file mode 100644 index 00000000000..7ac0761da7f --- /dev/null +++ b/src/common/d2d_window.cpp @@ -0,0 +1,184 @@ +#include "pch.h" +#include "d2d_window.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +D2DWindow::D2DWindow() { + static const WCHAR* class_name = L"PToyD2DPopup"; + WNDCLASS wc = {}; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hInstance = reinterpret_cast(&__ImageBase); + wc.lpszClassName = class_name; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = d2d_window_proc; + RegisterClass(&wc); + hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOREDIRECTIONBITMAP | WS_EX_LAYERED, + wc.lpszClassName, + L"PToyD2DPopup", + WS_POPUP| WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, wc.hInstance, this); + WINRT_VERIFY(hwnd); +} + +void D2DWindow::show(UINT x, UINT y, UINT width, UINT height) { + if (!initialized) { + base_init(); + } + base_resize(width, height); + render_empty(); + on_show(); + SetWindowPos(hwnd, HWND_TOPMOST, x, y, width, height, 0); + ShowWindow(hwnd, SW_SHOWNORMAL); + UpdateWindow(hwnd); +} + +void D2DWindow::hide() { + ShowWindow(hwnd, SW_HIDE); + on_hide(); +} + +void D2DWindow::initialize() { + base_init(); +} + +void D2DWindow::base_init() { + std::unique_lock lock(mutex); + // D2D1Factory is independent from the device, no need to recreate it if we need to recreate the device. + if (!d2d_factory) { +#ifdef _DEBUG + D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_INFORMATION }; +#else + D2D1_FACTORY_OPTIONS options = {}; +#endif + winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(d2d_factory), + &options, + d2d_factory.put_void())); + } + // For all other stuff - assing nullptr first to release the object, to reset the com_ptr. + d2d_dc = nullptr; + d2d_device = nullptr; + dxgi_factory = nullptr; + dxgi_device = nullptr; + d3d_device = nullptr; + winrt::check_hresult(D3D11CreateDevice(nullptr, + D3D_DRIVER_TYPE_HARDWARE, + nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + nullptr, + 0, + D3D11_SDK_VERSION, + d3d_device.put(), + nullptr, + nullptr)); + winrt::check_hresult(d3d_device->QueryInterface(__uuidof(dxgi_device), dxgi_device.put_void())); + winrt::check_hresult(CreateDXGIFactory2(0, __uuidof(dxgi_factory), dxgi_factory.put_void())); + winrt::check_hresult(d2d_factory->CreateDevice(dxgi_device.get(), d2d_device.put())); + winrt::check_hresult(d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, d2d_dc.put())); + init(); + initialized = true; +} + +void D2DWindow::base_resize(UINT width, UINT height) { + std::unique_lock lock(mutex); + if (!initialized) { + return; + } + window_width = width; + window_height = height; + if (window_width == 0 || window_height == 0) { + return; + } + DXGI_SWAP_CHAIN_DESC1 sc_description = {}; + sc_description.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + sc_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sc_description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + sc_description.BufferCount = 2; + sc_description.SampleDesc.Count = 1; + sc_description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + sc_description.Width = window_width; + sc_description.Height = window_height; + dxgi_swap_chain = nullptr; + winrt::check_hresult(dxgi_factory->CreateSwapChainForComposition(dxgi_device.get(), + &sc_description, + nullptr, + dxgi_swap_chain.put())); + composition_device = nullptr; + winrt::check_hresult(DCompositionCreateDevice(dxgi_device.get(), + __uuidof(composition_device), + composition_device.put_void())); + + composition_target = nullptr; + winrt::check_hresult(composition_device->CreateTargetForHwnd(hwnd, true, composition_target.put())); + + composition_visual = nullptr; + winrt::check_hresult(composition_device->CreateVisual(composition_visual.put())); + winrt::check_hresult(composition_visual->SetContent(dxgi_swap_chain.get())); + winrt::check_hresult(composition_target->SetRoot(composition_visual.get())); + + dxgi_surface = nullptr; + winrt::check_hresult(dxgi_swap_chain->GetBuffer(0, __uuidof(dxgi_surface), dxgi_surface.put_void())); + D2D1_BITMAP_PROPERTIES1 properties = {}; + properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; + properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW; + + d2d_bitmap = nullptr; + winrt::check_hresult(d2d_dc->CreateBitmapFromDxgiSurface(dxgi_surface.get(), + properties, + d2d_bitmap.put())); + d2d_dc->SetTarget(d2d_bitmap.get()); + resize(); +} + +void D2DWindow::base_render() { + std::unique_lock lock(mutex); + if (!initialized || !d2d_dc || !d2d_bitmap) + return; + d2d_dc->BeginDraw(); + render(d2d_dc.get()); + winrt::check_hresult(d2d_dc->EndDraw()); + winrt::check_hresult(dxgi_swap_chain->Present(1, 0)); + winrt::check_hresult(composition_device->Commit()); +} + +void D2DWindow::render_empty() { + std::unique_lock lock(mutex); + if (!initialized || !d2d_dc || !d2d_bitmap) + return; + d2d_dc->BeginDraw(); + d2d_dc->Clear(); + winrt::check_hresult(d2d_dc->EndDraw()); + winrt::check_hresult(dxgi_swap_chain->Present(1, 0)); + winrt::check_hresult(composition_device->Commit()); +} + +D2DWindow::~D2DWindow() { + ShowWindow(hwnd, SW_HIDE); + DestroyWindow(hwnd); +} + +D2DWindow* D2DWindow::this_from_hwnd(HWND window) { + return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); +} + +LRESULT __stdcall D2DWindow::d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { + switch (message) { + case WM_NCCREATE: { + auto create_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(create_struct->lpCreateParams)); + return TRUE; + } + case WM_MOVE: + case WM_SIZE: + this_from_hwnd(window)->base_resize((unsigned)lparam & 0xFFFF, (unsigned)lparam >> 16); + // Fall through to call 'base_render()' + case WM_PAINT: + this_from_hwnd(window)->base_render(); + return 0; + default: + return DefWindowProc(window, message, wparam, lparam); + } +} diff --git a/src/common/d2d_window.h b/src/common/d2d_window.h new file mode 100644 index 00000000000..5963acd95e8 --- /dev/null +++ b/src/common/d2d_window.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "d2d_svg.h" + +class D2DWindow +{ +public: + D2DWindow(); + void show(UINT x, UINT y, UINT width, UINT height); + void hide(); + void initialize(); + virtual ~D2DWindow(); +protected: + // Implement this: + + // Initialization - called when D2D device needs to be created. + // When called all D2DWindow members will be initialized, including d2d_dc + virtual void init() = 0; + // resize - when called, window_width and window_height will have current window size + virtual void resize() = 0; + // render - called on WM_PAIT, BeginPaint/EndPaint is handled by D2DWindow + virtual void render(ID2D1DeviceContext5* d2d_dc) = 0; + // on_show, on_hide - called when the window is about to be shown or about to be hidden + virtual void on_show() = 0; + virtual void on_hide() = 0; + + static LRESULT __stdcall d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + static D2DWindow* this_from_hwnd(HWND window); + + void base_init(); + void base_resize(UINT width, UINT height); + void base_render(); + void render_empty(); + + std::recursive_mutex mutex; + bool initialized = false; + HWND hwnd; + UINT window_width, window_height; + winrt::com_ptr d3d_device; + winrt::com_ptr dxgi_device; + winrt::com_ptr dxgi_factory; + winrt::com_ptr dxgi_swap_chain; + winrt::com_ptr composition_device; + winrt::com_ptr composition_target; + winrt::com_ptr composition_visual; + winrt::com_ptr dxgi_surface; + winrt::com_ptr d2d_bitmap; + winrt::com_ptr d2d_factory; + winrt::com_ptr d2d_device; + winrt::com_ptr d2d_dc; +}; diff --git a/src/common/dpi_aware.cpp b/src/common/dpi_aware.cpp new file mode 100644 index 00000000000..55e8f4c9bb0 --- /dev/null +++ b/src/common/dpi_aware.cpp @@ -0,0 +1,28 @@ +#include "pch.h" +#include "dpi_aware.h" +#include "monitors.h" +#include + +HRESULT DPIAware::GetScreenDPIForWindow(HWND hwnd, UINT &dpi_x, UINT &dpi_y) { + auto monitor_handle = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + dpi_x = 0; + dpi_y = 0; + if (monitor_handle != nullptr) { + return GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y); + } else { + return E_FAIL; + } +} + +void DPIAware::Convert(HMONITOR monitor_handle, int &width, int &height) { + if (monitor_handle == NULL) { + const POINT ptZero = { 0, 0 }; + monitor_handle = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY); + } + + UINT dpi_x, dpi_y; + if (GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y) == S_OK) { + width = width * dpi_x / DEFAULT_DPI; + height = height * dpi_y / DEFAULT_DPI; + } +} \ No newline at end of file diff --git a/src/common/dpi_aware.h b/src/common/dpi_aware.h new file mode 100644 index 00000000000..52396ff16a6 --- /dev/null +++ b/src/common/dpi_aware.h @@ -0,0 +1,11 @@ +#pragma once +#include "windef.h" + +class DPIAware { +private: + static const int DEFAULT_DPI = 96; + +public: + static HRESULT GetScreenDPIForWindow(HWND hwnd, UINT & dpi_x, UINT & dpi_y); + static void Convert(HMONITOR monitor_handle, int &width, int &high); +}; diff --git a/src/common/monitors.cpp b/src/common/monitors.cpp new file mode 100644 index 00000000000..7b59d1fac7b --- /dev/null +++ b/src/common/monitors.cpp @@ -0,0 +1,67 @@ +#include "pch.h" +#include "monitors.h" + +bool operator==(const ScreenSize& lhs, const ScreenSize& rhs) { + auto lhs_tuple = std::make_tuple(lhs.rect.left, lhs.rect.right, lhs.rect.top, lhs.rect.bottom); + auto rhs_tuple = std::make_tuple(rhs.rect.left, rhs.rect.right, rhs.rect.top, rhs.rect.bottom); + return lhs_tuple == rhs_tuple; +} + +static BOOL CALLBACK get_displays_enum_cb(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM data) { + MONITORINFOEX monitor_info; + monitor_info.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor, &monitor_info); + reinterpret_cast*>(data)->emplace_back(monitor, monitor_info.rcWork); + return true; +}; + +static BOOL CALLBACK get_displays_enum_cb_with_toolbar(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM data) { + MONITORINFOEX monitor_info; + monitor_info.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor, &monitor_info); + reinterpret_cast*>(data)->emplace_back(monitor, monitor_info.rcMonitor); + return true; +}; + +std::vector MonitorInfo::GetMonitors(bool include_toolbars) { + std::vector monitors; + EnumDisplayMonitors(NULL, NULL, include_toolbars ? get_displays_enum_cb_with_toolbar : get_displays_enum_cb, reinterpret_cast(&monitors)); + std::sort(begin(monitors), end(monitors), [](const MonitorInfo& lhs, const MonitorInfo& rhs) { + return lhs.rect < rhs.rect; + }); + return monitors; +} + +static BOOL CALLBACK get_primary_display_enum_cb(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM data) { + MONITORINFOEX monitor_info; + monitor_info.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor, &monitor_info); + if (monitor_info.dwFlags & MONITORINFOF_PRIMARY) { + reinterpret_cast(data)->handle = monitor; + reinterpret_cast(data)->rect = monitor_info.rcWork; + } + return true; +}; + +MonitorInfo MonitorInfo::GetPrimaryMonitor() { + MonitorInfo primary({}, {}); + EnumDisplayMonitors(NULL, NULL, get_primary_display_enum_cb, reinterpret_cast(&primary)); + return primary; +} + +MonitorInfo MonitorInfo::GetFromWindow(HWND hwnd) { + auto monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + return GetFromHandle(monitor); +} + +MonitorInfo MonitorInfo::GetFromPoint(POINT p) { + auto monitor = MonitorFromPoint(p, MONITOR_DEFAULTTONEAREST); + return GetFromHandle(monitor); +} + +MonitorInfo MonitorInfo::GetFromHandle(HMONITOR monitor) { + MONITORINFOEX monitor_info; + monitor_info.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfo(monitor, &monitor_info); + return MonitorInfo(monitor, monitor_info.rcWork); +} diff --git a/src/common/monitors.h b/src/common/monitors.h new file mode 100644 index 00000000000..1b2fb59efb8 --- /dev/null +++ b/src/common/monitors.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include + +struct ScreenSize { + explicit ScreenSize(RECT rect) : rect(rect) {} + RECT rect; + int left() const { return rect.left; } + int right() const { return rect.right; } + int top() const { return rect.top; } + int bottom() const { return rect.bottom; } + int height() const { return rect.bottom - rect.top; }; + int width() const { return rect.right - rect.left; }; + POINT top_left() const { return { rect.left, rect.top }; }; + POINT top_middle() const { return { rect.left + width() / 2, rect.top }; }; + POINT top_right() const { return { rect.right, rect.top }; }; + POINT middle_left() const { return { rect.left, rect.top + height() / 2 }; }; + POINT middle() const { return { rect.left + width() / 2, rect.top + height() / 2 }; }; + POINT middle_right() const { return { rect.right, rect.top + height() / 2 }; }; + POINT bottom_left() const { return { rect.left, rect.bottom }; }; + POINT bottm_midle() const { return { rect.left + width() / 2, rect.bottom }; }; + POINT bottom_right() const { return { rect.right, rect.bottom }; }; +}; + +struct MonitorInfo : ScreenSize { + explicit MonitorInfo(HMONITOR monitor, RECT rect) : handle(monitor), ScreenSize(rect) {} + HMONITOR handle; + + // Returns monitor rects ordered from left to right + static std::vector GetMonitors(bool include_toolbar); + // Return primary display + static MonitorInfo GetPrimaryMonitor(); + // Return monitor on which hwnd window is displayed + static MonitorInfo GetFromWindow(HWND hwnd); + // Return monitor nearest to a point + static MonitorInfo GetFromPoint(POINT p); + // Return monitor info given a HMONITOR + static MonitorInfo GetFromHandle(HMONITOR monitor); +}; + +bool operator==(const ScreenSize& lhs, const ScreenSize& rhs); diff --git a/src/common/pch.cpp b/src/common/pch.cpp new file mode 100644 index 00000000000..1d9f38c57d6 --- /dev/null +++ b/src/common/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/common/pch.h b/src/common/pch.h new file mode 100644 index 00000000000..fed02a46ebf --- /dev/null +++ b/src/common/pch.h @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" diff --git a/src/common/settings_helpers.cpp b/src/common/settings_helpers.cpp new file mode 100644 index 00000000000..c60a3c59e7a --- /dev/null +++ b/src/common/settings_helpers.cpp @@ -0,0 +1,75 @@ +#include "pch.h" +#include "settings_helpers.h" +#include +#include + +namespace PTSettingsHelper { + std::wstring get_root_save_folder_location() { + PWSTR local_app_path; + std::wstring result(L""); + + winrt::check_hresult(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &local_app_path)); + result = std::wstring(local_app_path); + CoTaskMemFree(local_app_path); + + result += L"\\Microsoft\\PowerToys"; + std::filesystem::path save_path(result); + if (!std::filesystem::exists(save_path)) { + std::filesystem::create_directories(save_path); + } + return result; + } + + std::wstring get_module_save_folder_location(const std::wstring& powertoy_name) { + std::wstring result = get_root_save_folder_location(); + result += L"\\"; + result += powertoy_name; + std::filesystem::path save_path(result); + if (!std::filesystem::exists(save_path)) { + std::filesystem::create_directories(save_path); + } + return result; + } + + std::wstring get_module_save_file_location(const std::wstring& powertoy_name) { + std::wstring result = get_module_save_folder_location(powertoy_name); + result += L"\\settings.json"; + return result; + } + + std::wstring get_powertoys_general_save_file_location() { + std::wstring result = get_root_save_folder_location(); + result += L"\\settings.json"; + return result; + } + + void save_module_settings(const std::wstring& powertoy_name, web::json::value& settings) { + std::wstring save_file_location = get_module_save_file_location(powertoy_name); + std::ofstream save_file(save_file_location, std::ios::binary); + settings.serialize(save_file); + save_file.close(); + } + + web::json::value load_module_settings(const std::wstring& powertoy_name) { + std::wstring save_file_location = get_module_save_file_location(powertoy_name); + std::ifstream save_file(save_file_location, std::ios::binary); + web::json::value result = web::json::value::parse(save_file); + save_file.close(); + return result; + } + + void save_general_settings(web::json::value& settings) { + std::wstring save_file_location = get_powertoys_general_save_file_location(); + std::ofstream save_file(save_file_location, std::ios::binary); + settings.serialize(save_file); + save_file.close(); + } + + web::json::value load_general_settings() { + std::wstring save_file_location = get_powertoys_general_save_file_location(); + std::ifstream save_file(save_file_location, std::ios::binary); + web::json::value result = web::json::value::parse(save_file); + save_file.close(); + return result; + } +} diff --git a/src/common/settings_helpers.h b/src/common/settings_helpers.h new file mode 100644 index 00000000000..3ce1c6e7d6d --- /dev/null +++ b/src/common/settings_helpers.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include + +namespace PTSettingsHelper { + + void save_module_settings(const std::wstring& powertoy_name, web::json::value& settings); + web::json::value load_module_settings(const std::wstring& powertoy_name); + void save_general_settings(web::json::value& settings); + web::json::value load_general_settings(); + +} diff --git a/src/common/settings_objects.cpp b/src/common/settings_objects.cpp new file mode 100644 index 00000000000..a9bc92310d8 --- /dev/null +++ b/src/common/settings_objects.cpp @@ -0,0 +1,237 @@ +#include "pch.h" +#include "settings_objects.h" +#include "settings_helpers.h" + +namespace PowerToysSettings { + + Settings::Settings(const HINSTANCE hinstance, const std::wstring& powertoy_name) { + m_instance = hinstance; + m_json = web::json::value::object(); + m_json.as_object()[L"version"] = web::json::value::string(L"1.0"); + m_json.as_object()[L"name"] = web::json::value::string(powertoy_name); + m_json.as_object()[L"properties"] = web::json::value::object(); + } + + void Settings::set_description(UINT resource_id) { + m_json.as_object()[L"description"] = web::json::value::string(get_resource(resource_id)); + } + + void Settings::set_description(const std::wstring& description) { + m_json.as_object()[L"description"] = web::json::value::string(description); + } + + void Settings::set_icon_key(const std::wstring& icon_key) { + m_json.as_object()[L"icon_key"] = web::json::value::string(icon_key); + } + + void Settings::set_overview_link(const std::wstring& overview_link) { + m_json.as_object()[L"overview_link"] = web::json::value::string(overview_link); + } + + void Settings::set_video_link(const std::wstring& video_link) { + m_json.as_object()[L"video_link"] = web::json::value::string(video_link); + } + + // add_bool_toogle overloads. + void Settings::add_bool_toogle(const std::wstring& name, UINT description_resource_id, bool value) { + add_bool_toogle(name, get_resource(description_resource_id), value); + } + + void Settings::add_bool_toogle(const std::wstring& name, const std::wstring& description, bool value) { + web::json::value item = web::json::value::object(); + item.as_object()[L"display_name"] = web::json::value::string(description); + item.as_object()[L"editor_type"] = web::json::value::string(L"bool_toggle"); + item.as_object()[L"value"] = web::json::value::boolean(value); + item.as_object()[L"order"] = web::json::value::number(++m_curr_priority); + + m_json.as_object()[L"properties"].as_object()[name] = item; + } + + // add_int_spinner overloads. + void Settings::add_int_spinner(const std::wstring& name, UINT description_resource_id, int value, int min, int max, int step) { + add_int_spinner(name, get_resource(description_resource_id), value, min, max, step); + } + + void Settings::add_int_spinner(const std::wstring& name, const std::wstring& description, int value, int min, int max, int step) { + web::json::value item = web::json::value::object(); + item.as_object()[L"display_name"] = web::json::value::string(description); + item.as_object()[L"editor_type"] = web::json::value::string(L"int_spinner"); + item.as_object()[L"value"] = web::json::value::number(value); + item.as_object()[L"min"] = web::json::value::number(min); + item.as_object()[L"max"] = web::json::value::number(max); + item.as_object()[L"step"] = web::json::value::number(step); + item.as_object()[L"order"] = web::json::value::number(++m_curr_priority); + + m_json.as_object()[L"properties"].as_object()[name] = item; + } + + // add_string overloads. + void Settings::add_string(const std::wstring& name, UINT description_resource_id, const std::wstring& value) { + add_string(name, get_resource(description_resource_id), value); + } + + void Settings::add_string(const std::wstring& name, const std::wstring& description, const std::wstring& value) { + web::json::value item = web::json::value::object(); + item.as_object()[L"display_name"] = web::json::value::string(description); + item.as_object()[L"editor_type"] = web::json::value::string(L"string_text"); + item.as_object()[L"value"] = web::json::value::string(value); + item.as_object()[L"order"] = web::json::value::number(++m_curr_priority); + + m_json.as_object()[L"properties"].as_object()[name] = item; + } + + // add_color_picker overloads. + void Settings::add_color_picker(const std::wstring& name, UINT description_resource_id, const std::wstring& value) { + add_color_picker(name, get_resource(description_resource_id), value); + } + + void Settings::add_color_picker(const std::wstring& name, const std::wstring& description, const std::wstring& value) { + web::json::value item = web::json::value::object(); + item.as_object()[L"display_name"] = web::json::value::string(description); + item.as_object()[L"editor_type"] = web::json::value::string(L"color_picker"); + item.as_object()[L"value"] = web::json::value::string(value); + item.as_object()[L"order"] = web::json::value::number(++m_curr_priority); + + m_json.as_object()[L"properties"].as_object()[name] = item; + } + + // add_custom_action overloads. + void Settings::add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, UINT ext_description_resource_id) { + add_custom_action(name, get_resource(description_resource_id), get_resource(button_text_resource_id), get_resource(ext_description_resource_id)); + } + + void Settings::add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, const std::wstring& value) { + add_custom_action(name, get_resource(description_resource_id), get_resource(button_text_resource_id), value); + } + + void Settings::add_custom_action(const std::wstring& name, const std::wstring& description, const std::wstring& button_text, const std::wstring& value) { + web::json::value item = web::json::value::object(); + item.as_object()[L"display_name"] = web::json::value::string(description); + item.as_object()[L"button_text"] = web::json::value::string(button_text); + item.as_object()[L"editor_type"] = web::json::value::string(L"custom_action"); + item.as_object()[L"value"] = web::json::value::string(value); + item.as_object()[L"order"] = web::json::value::number(++m_curr_priority); + + m_json.as_object()[L"properties"].as_object()[name] = item; + } + + // Serialization methods. + std::wstring Settings::serialize() { + return m_json.serialize(); + } + + bool Settings::serialize_to_buffer(wchar_t* buffer, int *buffer_size) { + std::wstring result = m_json.serialize(); + int result_len = (int)result.length(); + + if (buffer == nullptr || *buffer_size < result_len) { + *buffer_size = result_len + 1; + return false; + } else { + wcscpy_s(buffer, *buffer_size, result.c_str()); + return true; + } + } + + // Resource helper. + std::wstring Settings::get_resource(UINT resource_id) { + if (resource_id != 0) { + wchar_t buffer[512]; + if (LoadString(m_instance, resource_id, buffer, ARRAYSIZE(buffer)) > 0) { + return std::wstring(buffer); + } + } + + return L"RESOURCE ID NOT FOUND: " + std::to_wstring(resource_id); + } + + PowerToyValues::PowerToyValues(const std::wstring& powertoy_name) { + _name = powertoy_name; + m_json = web::json::value::object(); + set_version(); + m_json.as_object()[L"name"] = web::json::value::string(powertoy_name); + m_json.as_object()[L"properties"] = web::json::value::object(); + } + + PowerToyValues PowerToyValues::from_json_string(const std::wstring& json) { + PowerToyValues result = PowerToyValues(); + result.m_json = web::json::value::parse(json); + result._name = result.m_json.as_object()[L"name"].as_string(); + return result; + } + + PowerToyValues PowerToyValues::load_from_settings_file(const std::wstring & powertoy_name) { + PowerToyValues result = PowerToyValues(); + result.m_json = PTSettingsHelper::load_module_settings(powertoy_name); + result._name = powertoy_name; + return result; + } + + template + web::json::value add_property_generic(const std::wstring& name, T value) { + std::vector> vector = { std::make_pair(L"value", web::json::value(value)) }; + return web::json::value::object(vector); + } + + template <> + void PowerToyValues::add_property(const std::wstring& name, bool value) { + m_json.as_object()[L"properties"].as_object()[name] = add_property_generic(name, value); + }; + + template <> + void PowerToyValues::add_property(const std::wstring& name, int value) { + m_json.as_object()[L"properties"].as_object()[name] = add_property_generic(name, value); + }; + + template <> + void PowerToyValues::add_property(const std::wstring& name, std::wstring value) { + m_json.as_object()[L"properties"].as_object()[name] = add_property_generic(name, value); + }; + + bool PowerToyValues::is_bool_value(const std::wstring& property_name) { + return m_json.is_object() && + m_json.has_object_field(L"properties") && + m_json[L"properties"].has_object_field(property_name) && + m_json[L"properties"][property_name].has_boolean_field(L"value"); + } + + bool PowerToyValues::is_int_value(const std::wstring& property_name) { + return m_json.is_object() && + m_json.has_object_field(L"properties") && + m_json[L"properties"].has_object_field(property_name) && + m_json[L"properties"][property_name].has_integer_field(L"value"); + } + + bool PowerToyValues::is_string_value(const std::wstring& property_name) { + return m_json.is_object() && + m_json.has_object_field(L"properties") && + m_json[L"properties"].has_object_field(property_name) && + m_json[L"properties"][property_name].has_string_field(L"value"); + } + + bool PowerToyValues::get_bool_value(const std::wstring& property_name) { + return m_json[L"properties"][property_name][L"value"].as_bool(); + } + + int PowerToyValues::get_int_value(const std::wstring& property_name) { + return m_json[L"properties"][property_name][L"value"].as_integer(); + } + + std::wstring PowerToyValues::get_string_value(const std::wstring& property_name) { + return m_json[L"properties"][property_name][L"value"].as_string(); + } + + std::wstring PowerToyValues::serialize() { + set_version(); + return m_json.serialize(); + } + + void PowerToyValues::save_to_settings_file() { + set_version(); + PTSettingsHelper::save_module_settings(_name, m_json); + } + + void PowerToyValues::set_version() { + m_json.as_object()[L"version"] = web::json::value::string(m_version); + } +} \ No newline at end of file diff --git a/src/common/settings_objects.h b/src/common/settings_objects.h new file mode 100644 index 00000000000..3f67c039ac9 --- /dev/null +++ b/src/common/settings_objects.h @@ -0,0 +1,96 @@ +#pragma once +#include +#include + +namespace PowerToysSettings { + + class Settings { + public: + Settings( + const HINSTANCE hinstance, // Module handle of the PowerToy DLL 'IMAGE_DOS_HEADER __ImageBase' + const std::wstring& powertoy_name + ); + + // Add additional general information to the PowerToy settings. + void set_description(UINT resource_id); + void set_description(const std::wstring& description); + + void set_icon_key(const std::wstring& icon_key); + void set_overview_link(const std::wstring& overview_link); + void set_video_link(const std::wstring& video_link); + + // Add properties to the PowerToy settings. + void add_bool_toogle(const std::wstring& name, UINT description_resource_id, bool value); + void add_bool_toogle(const std::wstring& name, const std::wstring& description, bool value); + + void add_int_spinner(const std::wstring& name, UINT description_resource_id, int value, int min, int max, int step); + void add_int_spinner(const std::wstring& name, const std::wstring& description, int value, int min, int max, int step); + + void add_string(const std::wstring& name, UINT description_resource_id, const std::wstring& value); + void add_string(const std::wstring& name, const std::wstring& description, const std::wstring& value); + + void add_color_picker(const std::wstring& name, UINT description_resource_id, const std::wstring& value); + void add_color_picker(const std::wstring& name, const std::wstring& description, const std::wstring& value); + + void add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, UINT ext_description_resource_id); + void add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, const std::wstring& value); + void add_custom_action(const std::wstring& name, const std::wstring& description, const std::wstring& button_text, const std::wstring& value); + + // Serialize the internal json to a string. + std::wstring serialize(); + // Serialize the internal json to the input buffer. + bool serialize_to_buffer(wchar_t* buffer, int *buffer_size); + + private: + web::json::value m_json; + int m_curr_priority = 0; // For keeping order when adding elements. + HINSTANCE m_instance; + + std::wstring get_resource(UINT resource_id); + }; + + class PowerToyValues { + public: + PowerToyValues(const std::wstring& powertoy_name); + static PowerToyValues from_json_string(const std::wstring& json); + static PowerToyValues load_from_settings_file(const std::wstring& powertoy_name); + + template + void add_property(const std::wstring& name, T value); + + // Check property value type + bool is_bool_value(const std::wstring& property_name); + bool is_int_value(const std::wstring& property_name); + bool is_string_value(const std::wstring& property_name); + + // Get property value + bool get_bool_value(const std::wstring& property_name); + int get_int_value(const std::wstring& property_name); + std::wstring get_string_value(const std::wstring& property_name); + + std::wstring serialize(); + void save_to_settings_file(); + + private: + const std::wstring m_version = L"1.0"; + void set_version(); + web::json::value m_json; + std::wstring _name; + PowerToyValues() {} + }; + + class CustomActionObject { + public: + static CustomActionObject from_json_string(const std::wstring& json) { + web::json::value parsed_json = web::json::value::parse(json); + return CustomActionObject(parsed_json); + } + + std::wstring get_name() { return m_json[L"action_name"].as_string(); } + std::wstring get_value() { return m_json[L"value"].as_string(); } + + protected: + CustomActionObject(web::json::value action_json) : m_json(action_json) {}; + web::json::value m_json; + }; +} diff --git a/src/common/start_visible.cpp b/src/common/start_visible.cpp new file mode 100644 index 00000000000..5c9c8d13bfd --- /dev/null +++ b/src/common/start_visible.cpp @@ -0,0 +1,16 @@ +#include "pch.h" +#include "start_visible.h" + +bool is_start_visible() { + static winrt::com_ptr app_visibility; + if (!app_visibility) { + winrt::check_hresult(CoCreateInstance(CLSID_AppVisibility, + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(app_visibility), + app_visibility.put_void())); + } + BOOL visible; + auto result = app_visibility->IsLauncherVisible(&visible); + return SUCCEEDED(result) && visible; +} diff --git a/src/common/start_visible.h b/src/common/start_visible.h new file mode 100644 index 00000000000..05e8b5b720f --- /dev/null +++ b/src/common/start_visible.h @@ -0,0 +1,4 @@ +#pragma once + +bool is_start_visible(); + diff --git a/src/common/tasklist_positions.cpp b/src/common/tasklist_positions.cpp new file mode 100644 index 00000000000..f3970db30fa --- /dev/null +++ b/src/common/tasklist_positions.cpp @@ -0,0 +1,93 @@ +#include "pch.h" +#include "tasklist_positions.h" + +void Tasklist::update() { + // Get HWND of the tasklist + auto tasklist_hwnd = FindWindowA("Shell_TrayWnd", nullptr); + if (!tasklist_hwnd) return; + tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "ReBarWindow32", nullptr); + if (!tasklist_hwnd) return; + tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskSwWClass", nullptr); + if (!tasklist_hwnd) return; + tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskListWClass", nullptr); + if (!tasklist_hwnd) return; + if (!automation) { + winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IUIAutomation, + automation.put_void())); + winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); + } + element = nullptr; + winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); +} + +bool Tasklist::update_buttons(std::vector& buttons) { + if (!automation || !element) { + return false; + } + winrt::com_ptr elements; + if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) + return false; + if (!elements) + return false; + int count; + if (elements->get_Length(&count) < 0) + return false; + winrt::com_ptr child; + std::vector found_butttons; + found_butttons.reserve(count); + for (int i = 0; i < count; ++i) { + child = nullptr; + if (elements->GetElement(i, child.put()) < 0) + return false; + TasklistButton button; + if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) { + if (var_rect.vt == (VT_R8 | VT_ARRAY)) { + LONG pos; + double value; + pos = 0; SafeArrayGetElement(var_rect.parray, &pos, &value); + button.x = (long)value; + pos = 1; SafeArrayGetElement(var_rect.parray, &pos, &value); + button.y = (long)value; + pos = 2; SafeArrayGetElement(var_rect.parray, &pos, &value); + button.width = (long)value; + pos = 3; SafeArrayGetElement(var_rect.parray, &pos, &value); + button.height = (long)value; + } + VariantClear(&var_rect); + } else { + return false; + } + if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) { + button.name = automation_id; + SysFreeString(automation_id); + } + found_butttons.push_back(button); + } + // assign keynums + buttons.clear(); + for (auto& button : found_butttons) { + if (buttons.empty()) { + button.keynum = 1; + buttons.push_back(std::move(button)); + } else { + if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row + break; + if (button.name == buttons.back().name) + continue; // skip buttons from the same app + button.keynum = buttons.back().keynum + 1; + buttons.push_back(std::move(button)); + if (button.keynum == 10) + break; // no more than 10 buttons + } + } + return true; +} + +std::vector Tasklist::get_buttons() { + std::vector buttons; + update_buttons(buttons); + return buttons; +} \ No newline at end of file diff --git a/src/common/tasklist_positions.h b/src/common/tasklist_positions.h new file mode 100644 index 00000000000..05fc92d8832 --- /dev/null +++ b/src/common/tasklist_positions.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include +#include +#include + +struct TasklistButton { + std::wstring name; + long x, y, width, height, keynum; +}; + +class Tasklist { +public: + void update(); + std::vector get_buttons(); + bool update_buttons(std::vector& buttons); +private: + winrt::com_ptr automation; + winrt::com_ptr element; + winrt::com_ptr true_condition; +}; diff --git a/src/common/two_way_pipe_message_ipc.h b/src/common/two_way_pipe_message_ipc.h new file mode 100644 index 00000000000..097060531d4 --- /dev/null +++ b/src/common/two_way_pipe_message_ipc.h @@ -0,0 +1,429 @@ +#pragma once +#include +#include "async_message_queue.h" +#include +#include +#include +#include + +class TwoWayPipeMessageIPC { +public: + typedef void(*callback_function)(const std::wstring&); + void send(std::wstring msg) { + output_queue.queue_message(msg); + } + TwoWayPipeMessageIPC(std::wstring _input_pipe_name, std::wstring _output_pipe_name, callback_function p_func) { + input_pipe_name = _input_pipe_name; + output_pipe_name = _output_pipe_name; + dispatch_inc_message_function = p_func; + } + void start(HANDLE _restricted_pipe_token) { + output_queue_thread = std::thread(&TwoWayPipeMessageIPC::consume_output_queue_thread, this); + input_queue_thread = std::thread(&TwoWayPipeMessageIPC::consume_input_queue_thread, this); + input_pipe_thread = std::thread(&TwoWayPipeMessageIPC::start_named_pipe_server, this, _restricted_pipe_token); + } + + void end() { + closed = true; + input_queue.interrupt(); + input_queue_thread.join(); + output_queue.interrupt(); + output_queue_thread.join(); + pipe_connect_handle_mutex.lock(); + if (current_connect_pipe_handle != NULL) { + //Cancels the Pipe currently waiting for a connection. + CancelIoEx(current_connect_pipe_handle,NULL); + } + pipe_connect_handle_mutex.unlock(); + input_pipe_thread.join(); + } + +private: + AsyncMessageQueue input_queue; + AsyncMessageQueue output_queue; + std::wstring output_pipe_name; + std::wstring input_pipe_name; + std::thread input_queue_thread; + std::thread output_queue_thread; + std::thread input_pipe_thread; + std::mutex pipe_connect_handle_mutex; // For manipulating the current_connect_pipe + + HANDLE current_connect_pipe_handle = NULL; + bool closed = false; + TwoWayPipeMessageIPC::callback_function dispatch_inc_message_function; + const DWORD BUFSIZE = 1024; + + void send_pipe_message(std::wstring message) { + // Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-client + HANDLE output_pipe_handle; + const wchar_t* message_send = message.c_str(); + BOOL fSuccess = FALSE; + DWORD cbToWrite, cbWritten, dwMode; + const wchar_t* lpszPipename = output_pipe_name.c_str(); + + // Try to open a named pipe; wait for it, if necessary. + + while (1) { + output_pipe_handle = CreateFile( + lpszPipename, // pipe name + GENERIC_READ | // read and write access + GENERIC_WRITE, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + 0, // default attributes + NULL); // no template file + + // Break if the pipe handle is valid. + + if (output_pipe_handle != INVALID_HANDLE_VALUE) + break; + + // Exit if an error other than ERROR_PIPE_BUSY occurs. + DWORD curr_error = 0; + if ((curr_error = GetLastError()) != ERROR_PIPE_BUSY) { + + return; + } + + // All pipe instances are busy, so wait for 20 seconds. + + if (!WaitNamedPipe(lpszPipename, 20000)) { + return; + } + } + dwMode = PIPE_READMODE_MESSAGE; + fSuccess = SetNamedPipeHandleState( + output_pipe_handle, // pipe handle + &dwMode, // new pipe mode + NULL, // don't set maximum bytes + NULL); // don't set maximum time + if (!fSuccess) { + return; + } + + // Send a message to the pipe server. + + cbToWrite = (lstrlen(message_send)) * sizeof(WCHAR); // no need to send final '\0'. Pipe is in message mode. + + fSuccess = WriteFile( + output_pipe_handle, // pipe handle + message_send, // message + cbToWrite, // message length + &cbWritten, // bytes written + NULL); // not overlapped + if (!fSuccess) { + return; + } + CloseHandle(output_pipe_handle); + return; + } + + void consume_output_queue_thread() { + while (!closed) { + std::wstring message = output_queue.pop_message(); + if (message.length() == 0) { + break; + } + send_pipe_message(message); + } + } + + BOOL GetLogonSID(HANDLE hToken, PSID *ppsid) { + // From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85) + BOOL bSuccess = FALSE; + DWORD dwIndex; + DWORD dwLength = 0; + PTOKEN_GROUPS ptg = NULL; + + // Verify the parameter passed in is not NULL. + if (NULL == ppsid) + goto Cleanup; + + // Get required buffer size and allocate the TOKEN_GROUPS buffer. + + if (!GetTokenInformation( + hToken, // handle to the access token + TokenGroups, // get information about the token's groups + (LPVOID)ptg, // pointer to TOKEN_GROUPS buffer + 0, // size of buffer + &dwLength // receives required buffer size + )) { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto Cleanup; + + ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, dwLength); + + if (ptg == NULL) + goto Cleanup; + } + + // Get the token group information from the access token. + + if (!GetTokenInformation( + hToken, // handle to the access token + TokenGroups, // get information about the token's groups + (LPVOID)ptg, // pointer to TOKEN_GROUPS buffer + dwLength, // size of buffer + &dwLength // receives required buffer size + )) { + goto Cleanup; + } + + // Loop through the groups to find the logon SID. + + for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++) + if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) + == SE_GROUP_LOGON_ID) { + // Found the logon SID; make a copy of it. + + dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid); + *ppsid = (PSID)HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, dwLength); + if (*ppsid == NULL) + goto Cleanup; + if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid)) { + HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); + goto Cleanup; + } + break; + } + + bSuccess = TRUE; + + Cleanup: + + // Free the buffer for the token groups. + + if (ptg != NULL) + HeapFree(GetProcessHeap(), 0, (LPVOID)ptg); + + return bSuccess; + } + + VOID FreeLogonSID(PSID *ppsid) { + // From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85) + HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid); + } + + + int change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token) { + PACL old_dacl, new_dacl; + PSECURITY_DESCRIPTOR sd; + EXPLICIT_ACCESS ea; + PSID user_restricted; + int error; + + if (!GetLogonSID(token, &user_restricted)) { + error = 5; // No access error. + goto Ldone; + } + + if (GetSecurityInfo(handle, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + &old_dacl, + NULL, + &sd)) { + error = GetLastError(); + goto Lclean_sid; + } + + memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); + ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; + ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES; + ea.grfAccessPermissions |= SYNCHRONIZE; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_USER; + ea.Trustee.ptstrName = (LPTSTR)user_restricted; + + if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl)) { + error = GetLastError(); + goto Lclean_sd; + } + + if (SetSecurityInfo(handle, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + new_dacl, + NULL)) { + error = GetLastError(); + goto Lclean_dacl; + } + + error = 0; + + Lclean_dacl: + LocalFree((HLOCAL)new_dacl); + Lclean_sd: + LocalFree((HLOCAL)sd); + Lclean_sid: + FreeLogonSID(&user_restricted); + Ldone: + return error; + } + + HANDLE create_medium_integrity_token() { + HANDLE restricted_token_handle; + SAFER_LEVEL_HANDLE level_handle = NULL; + DWORD sid_size = SECURITY_MAX_SID_SIZE; + BYTE medium_sid[SECURITY_MAX_SID_SIZE]; + if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &level_handle, NULL)) { + return NULL; + } + if (!SaferComputeTokenFromLevel(level_handle, NULL, &restricted_token_handle, 0, NULL)) { + SaferCloseLevel(level_handle); + return NULL; + } + SaferCloseLevel(level_handle); + + if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, medium_sid, &sid_size)) { + CloseHandle(restricted_token_handle); + return NULL; + } + + TOKEN_MANDATORY_LABEL integrity_level = { 0 }; + integrity_level.Label.Attributes = SE_GROUP_INTEGRITY; + integrity_level.Label.Sid = reinterpret_cast(medium_sid); + + if (!SetTokenInformation(restricted_token_handle, TokenIntegrityLevel, &integrity_level, sizeof(integrity_level))) { + CloseHandle(restricted_token_handle); + return NULL; + } + + return restricted_token_handle; + } + + void handle_pipe_connection(HANDLE input_pipe_handle) { + //Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server + HANDLE hHeap = GetProcessHeap(); + uint8_t* pchRequest = (uint8_t*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(uint8_t)); + + DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0; + BOOL fSuccess = FALSE; + + // Do some extra error checking since the app will keep running even if this thread fails. + std::list> message_parts; + + if (input_pipe_handle == NULL) { + if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest); + return; + } + + if (pchRequest == NULL) { + return; + } + + // Loop until done reading + do { + // Read client requests from the pipe. This simplistic code only allows messages + // up to BUFSIZE characters in length. + ZeroMemory(pchRequest, BUFSIZE * sizeof(uint8_t)); + fSuccess = ReadFile( + input_pipe_handle, // handle to pipe + pchRequest, // buffer to receive data + BUFSIZE * sizeof(uint8_t), // size of buffer + &cbBytesRead, // number of bytes read + NULL); // not overlapped I/O + + if (!fSuccess && GetLastError() != ERROR_MORE_DATA) { + break; + } + std::vector part_vector; + part_vector.reserve(cbBytesRead); + std::copy(pchRequest, pchRequest + cbBytesRead, std::back_inserter(part_vector)); + message_parts.push_back(part_vector); + } while (!fSuccess); + + if (fSuccess) { + // Reconstruct the total_message. + std::vector reconstructed_message; + size_t total_size = 0; + for (auto& part_vector : message_parts) { + total_size += part_vector.size(); + } + reconstructed_message.reserve(total_size); + for (auto& part_vector : message_parts) { + std::move(part_vector.begin(), part_vector.end(), std::back_inserter(reconstructed_message)); + } + std::wstring unicode_msg; + unicode_msg.assign(reinterpret_cast(reconstructed_message.data()), reconstructed_message.size() / sizeof(std::wstring::value_type)); + input_queue.queue_message(unicode_msg); + } + + // Flush the pipe to allow the client to read the pipe's contents + // before disconnecting. Then disconnect the pipe, and close the + // handle to this pipe instance. + + FlushFileBuffers(input_pipe_handle); + DisconnectNamedPipe(input_pipe_handle); + CloseHandle(input_pipe_handle); + + HeapFree(hHeap, 0, pchRequest); + + printf("InstanceThread exitting.\n"); + } + + void start_named_pipe_server(HANDLE token) { + // Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server + const wchar_t* pipe_name = input_pipe_name.c_str(); + BOOL connected = FALSE; + HANDLE connect_pipe_handle = INVALID_HANDLE_VALUE; + while(!closed) { + { + std::unique_lock lock(pipe_connect_handle_mutex); + connect_pipe_handle = CreateNamedPipe( + pipe_name, + PIPE_ACCESS_DUPLEX | + WRITE_DAC, + PIPE_TYPE_MESSAGE | + PIPE_READMODE_MESSAGE | + PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + BUFSIZE, + BUFSIZE, + 0, + NULL + ); + + if (connect_pipe_handle == INVALID_HANDLE_VALUE) { + return; + } + + if (token != NULL) { + int err = change_pipe_security_allow_restricted_token(connect_pipe_handle, token); + } + current_connect_pipe_handle = connect_pipe_handle; + } + connected = ConnectNamedPipe(connect_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + { + std::unique_lock lock(pipe_connect_handle_mutex); + current_connect_pipe_handle = NULL; + } + if (connected) { + std::thread(&TwoWayPipeMessageIPC::handle_pipe_connection, this, connect_pipe_handle).detach(); + } else { + // Client could not connect. + CloseHandle(connect_pipe_handle); + } + } + } + + void consume_input_queue_thread() { + while (!closed) { + std::wstring message = input_queue.pop_message(); + if (message.length() == 0) { + break; + } + dispatch_inc_message_function(message); + } + } + +}; diff --git a/src/common/windows_colors.cpp b/src/common/windows_colors.cpp new file mode 100644 index 00000000000..490469f677d --- /dev/null +++ b/src/common/windows_colors.cpp @@ -0,0 +1,71 @@ +#include "pch.h" +#include "windows_colors.h" + +DWORD WindowsColors::rgb_color(DWORD abgr_color) { + // registry keeps the colors in ABGR format, we want RGB + auto r = (abgr_color & 0xFF); + auto g = (abgr_color & 0xFF00) >> 8; + auto b = (abgr_color & 0xFF0000) >> 16; + return (r << 16) | (g << 8) | b; +} +DWORD WindowsColors::rgb_color(winrt::Windows::UI::Color color) { + return ((DWORD)color.R << 16) | ((DWORD)color.G << 8) | ((DWORD)color.B); +} +WindowsColors::Color WindowsColors::get_button_face_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::ButtonFace); +} +WindowsColors::Color WindowsColors::get_button_text_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::ButtonText); +} +WindowsColors::Color WindowsColors::get_highlight_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::Highlight); +} +WindowsColors::Color WindowsColors::get_hotlight_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::Hotlight); +} +WindowsColors::Color WindowsColors::get_highlight_text_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.UIElementColor(winrt::Windows::UI::ViewManagement::UIElementType::HighlightText); +} +WindowsColors::Color WindowsColors::get_accent_light_1_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::AccentLight1); +} +WindowsColors::Color WindowsColors::get_accent_light_2_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::AccentLight2); +} +WindowsColors::Color WindowsColors::get_accent_dark_1_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::AccentDark1); +} +WindowsColors::Color WindowsColors::get_accent_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent); +} +WindowsColors::Color WindowsColors::get_background_color() { + winrt::Windows::UI::ViewManagement::UISettings uiSettings; + return uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Background); +} + +bool WindowsColors::update() { + auto new_accent_color_menu = rgb_color(get_accent_color()); + auto new_start_color_menu = new_accent_color_menu; + auto new_desktop_fill_color = rgb_color(GetSysColor(COLOR_DESKTOP)); + auto new_light_mode = rgb_color(get_background_color()) != 0; //Dark mode will have black as the background color. + + bool changed = new_accent_color_menu != accent_color_menu || + new_start_color_menu != start_color_menu || + new_light_mode != light_mode || + new_desktop_fill_color != desktop_fill_color; + accent_color_menu = new_accent_color_menu; + start_color_menu = new_start_color_menu; + light_mode = new_light_mode; + desktop_fill_color = new_desktop_fill_color; + + return changed; +} diff --git a/src/common/windows_colors.h b/src/common/windows_colors.h new file mode 100644 index 00000000000..2fd4e834b99 --- /dev/null +++ b/src/common/windows_colors.h @@ -0,0 +1,27 @@ +#pragma once +#include + +struct WindowsColors { + using Color = winrt::Windows::UI::Color; + + static DWORD rgb_color(DWORD abgr_color); + static DWORD rgb_color(Color color); + static Color get_button_face_color(); + static Color get_button_text_color(); + static Color get_highlight_color(); + static Color get_hotlight_color(); + static Color get_highlight_text_color(); + static Color get_accent_light_1_color(); + static Color get_accent_light_2_color(); + static Color get_accent_dark_1_color(); + static Color get_accent_color(); + static Color get_background_color(); + + // Update colors - returns true if the values where changed + bool update(); + + DWORD accent_color_menu = 0, + start_color_menu = 0, + desktop_fill_color = 0; + bool light_mode = true; +}; diff --git a/src/editor/README.md b/src/editor/README.md new file mode 100644 index 00000000000..e183c720d0f --- /dev/null +++ b/src/editor/README.md @@ -0,0 +1,21 @@ +# PowerToys Settings project + +## Introduction + +This path contains the WebView project for editing the PowerToys settings. + +The html portion of the project that is shown in the WebView is contained in `settings-html`. +Instructions on how build a new version and update this project are in the [Web project for the Settings UI](../settings-web). + +While developing, it's possible to connect the WebView to the development server running in localhost by setting the `_DEBUG_WITH_LOCALHOST` flag to `1` and following the instructions near it in `./main.cpp`. + +## Code Organization + +#### [main.cpp](./main.cpp) +Contains the main executable code, initializing and managing the Window containing the WebView and communication with the main PowerToys executable. + +#### [StreamURIResolverFromFile.cpp](./StreamURIResolverFromFile.cpp) +Defines a class implementing `IUriToStreamResolver`. Allows the WebView to navigate to filesystem files in this Win32 project. + +#### [settings-html/](./settings-html/) +Contains the assets file from building the [Web project for the Settings UI](../settings-web). It will be loaded by the WebView. diff --git a/src/editor/StreamURIResolverFromFile.cpp b/src/editor/StreamURIResolverFromFile.cpp new file mode 100644 index 00000000000..46575d88417 --- /dev/null +++ b/src/editor/StreamURIResolverFromFile.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "StreamUriResolverFromFile.h" + +winrt::Windows::Foundation::IAsyncOperation StreamUriResolverFromFile::UriToStreamAsync(const winrt::Windows::Foundation::Uri & uri) const { + + winrt::Windows::Storage::StorageFolder folder = winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(winrt::param::hstring(base_path)).get(); + + std::wstring myuri = uri.Path().c_str(); + myuri.erase(0, 1); // Removes the first slash from the URI + + std::replace(myuri.begin(), myuri.end(), '/', '\\'); + winrt::Windows::Storage::StorageFile file = nullptr; + + try { + file = folder.GetFileAsync(winrt::param::hstring(myuri)).get(); + } + catch (winrt::hresult_error const& e) { + WCHAR message[1024] = L""; + StringCchPrintf(message, ARRAYSIZE(message), L"failed: %ls", e.message().c_str()); + MessageBox(NULL, message, L"Error", MB_OK); + } + + return file.OpenSequentialReadAsync(); +} diff --git a/src/editor/StreamUriResolverFromFile.h b/src/editor/StreamUriResolverFromFile.h new file mode 100644 index 00000000000..a76a7ff1702 --- /dev/null +++ b/src/editor/StreamUriResolverFromFile.h @@ -0,0 +1,8 @@ +#pragma once +#include "pch.h" + +struct StreamUriResolverFromFile : winrt::implements { + WCHAR base_path[MAX_PATH]; + winrt::Windows::Foundation::IAsyncOperation UriToStreamAsync(const winrt::Windows::Foundation::Uri & uri) const; +}; + diff --git a/src/editor/main.cpp b/src/editor/main.cpp new file mode 100644 index 00000000000..c140e9ee2a7 --- /dev/null +++ b/src/editor/main.cpp @@ -0,0 +1,444 @@ +#include "pch.h" +#include +#include "StreamUriResolverFromFile.h" +#include +#include +#include +#include "resource.h" +#include + +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "shcore.lib") +#pragma comment(lib, "windowsapp") +#pragma comment(lib, "dxgi") +#pragma comment(lib, "d3d11") +#pragma comment(lib, "d2d1") +#pragma comment(lib, "dcomp") +#pragma comment(lib, "dwmapi") + +#ifdef _DEBUG +#define _DEBUG_WITH_LOCALHOST 0 +// Define as 1 For debug purposes, to access localhost servers. +// webview_process_options.PrivateNetworkClientServerCapability(winrt::Windows::Web::UI::Interop::WebViewControlProcessCapabilityState::Enabled); +// To access localhost:8080 for development, you'll also need to disable loopback restrictions for the webview: +// > checknetisolation LoopbackExempt -a -n=Microsoft.Win32WebViewHost_cw5n1h2txyewy +// To remove the exception after development: +// > checknetisolation LoopbackExempt -d -n=Microsoft.Win32WebViewHost_cw5n1h2txyewy +// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/2226#issuecomment-396360314 +#endif +HINSTANCE m_hInst; +HWND main_window_handler = nullptr; +using namespace winrt; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Storage::Streams; +using namespace winrt::Windows::Web::Http; +using namespace winrt::Windows::Web::Http::Headers; +using namespace winrt::Windows::Web::UI; +using namespace winrt::Windows::Web::UI::Interop; +using namespace winrt::Windows::System; + +winrt::Windows::Web::UI::Interop::WebViewControl webview_control = nullptr; +winrt::Windows::Web::UI::Interop::WebViewControlProcess webview_process = nullptr; +winrt::Windows::Web::UI::Interop::WebViewControlProcessOptions webview_process_options = nullptr; +StreamUriResolverFromFile local_uri_resolver; + +// Windows message for receiving copied data to send to the webview. +UINT wm_copydata_webview = 0; + +// Windows message to destroy the window. Used if: +// - Parent process has terminated. +// - WebView confirms that the Window can close. +UINT wm_my_destroy_window = 0; + +// mutex for checking if the window has already been created. +std::mutex m_window_created_mutex; + +TwoWayPipeMessageIPC* current_settings_ipc = NULL; + +// Set to true if waiting for webview confirmation before closing the Window. +bool m_waiting_for_close_confirmation = false; + +#ifdef _DEBUG +void NavigateToLocalhostReactServer() { + // Useful for connecting to instance running in react development server. + webview_control.Navigate(Uri(hstring(L"http://localhost:8080"))); +} +#endif +void NavigateToUri(_In_ LPCWSTR uri_as_string) { + Uri url = webview_control.BuildLocalStreamUri(hstring(L"settings-html"), hstring(uri_as_string)); + webview_control.NavigateToLocalStreamUri(url, local_uri_resolver); + +} + +Rect hwnd_client_rect_to_bounds_rect(_In_ HWND hwnd) { + RECT client_rect = { 0 }; + GetClientRect(hwnd, &client_rect); + + Rect bounds = + { + 0, + 0, + static_cast(client_rect.right - client_rect.left), + static_cast(client_rect.bottom - client_rect.top) + }; + + return bounds; +} + +void resize_web_view() { + Rect bounds = hwnd_client_rect_to_bounds_rect(main_window_handler); + winrt::Windows::Web::UI::Interop::IWebViewControlSite webViewControlSite = (winrt::Windows::Web::UI::Interop::IWebViewControlSite) webview_control; + webViewControlSite.Bounds(bounds); + +} + +#define SEND_TO_WEBVIEW_MSG 1 + +void send_message_to_webview(const std::wstring& msg) { + if (main_window_handler != NULL && wm_copydata_webview!=0) { + // Allocate the COPYDATASTRUCT and message to pass to the Webview. + // This is needed in order to use PostMessage, since COM calls to + // webview_control.InvokeScriptAsync can't be made from + PCOPYDATASTRUCT copy_data_message = new COPYDATASTRUCT(); + const wchar_t* orig_msg = msg.c_str(); + DWORD orig_len = (DWORD)wcslen(orig_msg); + wchar_t* copy_msg = new wchar_t[orig_len + 1]; + wcscpy_s(copy_msg, orig_len + 1, orig_msg); + copy_data_message->dwData = SEND_TO_WEBVIEW_MSG; + copy_data_message->cbData = (orig_len + 1) * sizeof(wchar_t); + copy_data_message->lpData = (PVOID)copy_msg; + PostMessage(main_window_handler, wm_copydata_webview, (WPARAM)main_window_handler, (LPARAM)copy_data_message); + // wnd_static_proc will be responsible for freeing these. + } +} + +void send_message_to_powertoys(const std::wstring msg) { + if (current_settings_ipc != NULL) { + current_settings_ipc->send(msg); + } else { + // For Debug purposes, in case the webview is being run alone. +#ifdef _DEBUG + MessageBox(main_window_handler, msg.c_str(), L"From Webview", MB_OK); + //throw in some sample data + std::wstring debug_settings_info(LR"json({ + "general": { + "startup": true, + "enabled": { + "Shortcut Guide":false, + "Example PowerToy":true + } + }, + "powertoys": { + "Shortcut Guide": { + "version": "1.0", + "name": "Shortcut Guide", + "description": "Shows a help overlay with Windows shortcuts when the Windows key is pressed.", + "icon_key": "pt-shortcut-guide", + "properties": { + "press time" : { + "display_name": "How long to press the Windows key before showing the Shortcut Guide (ms)", + "editor_type": "int_spinner", + "value": 300 + } + } + }, + "Example PowerToy": { + "version": "1.0", + "name": "Example PowerToy", + "description": "Shows the different controls for the settings.", + "overview_link": "https://github.com/microsoft/PowerToys", + "video_link": "https://www.youtube.com/watch?v=d3LHo2yXKoY&t=21462", + "properties": { + "test bool_toggle": { + "display_name": "This is what a bool_toggle looks like", + "editor_type": "bool_toggle", + "value": false + }, + "test int_spinner": { + "display_name": "This is what a int_spinner looks like", + "editor_type": "int_spinner", + "value": 10 + }, + "test string_text": { + "display_name": "This is what a string_text looks like", + "editor_type": "string_text", + "value": "A sample string value" + }, + "test color_picker": { + "display_name": "This is what a color_picker looks like", + "editor_type": "color_picker", + "value": "#0450fd" + }, + "test custom_action": { + "display_name": "This is what a custom_action looks like", + "editor_type": "custom_action", + "value": "This is to be custom data. It\ncan\nhave\nmany\nlines\nthat\nshould\nmake\nthe\nfield\nbigger.", + "button_text": "Call a Custom Action!" + } + } + } + } + })json"); + send_message_to_webview(debug_settings_info); +#endif + } +} + +void receive_message_from_webview(const std::wstring& msg) { + if (msg[0] == '{') { + // It's a JSON, send message to PowerToys + std::thread(send_message_to_powertoys, msg).detach(); + } else { + // It's not a JSON, check for expected control messages. + if (msg == L"exit") { + // WebView confirms the settings application can exit. + PostMessage(main_window_handler, wm_my_destroy_window, 0, 0); + } else if (msg == L"cancel-exit") { + // WebView canceled the exit request. + m_waiting_for_close_confirmation = false; + } + } +} + +void initialize_win32_webview(HWND hwnd, int nCmdShow) { + + // initialize the base_path for the html content relative to the executable. + WCHAR executable_path[MAX_PATH]; + GetModuleFileName(NULL, executable_path, MAX_PATH); + PathRemoveFileSpec(executable_path); + wcscat_s(executable_path, L"\\settings-html"); + wcscpy_s(local_uri_resolver.base_path, executable_path); + + try { + if (!webview_process_options) { + webview_process_options = winrt::Windows::Web::UI::Interop::WebViewControlProcessOptions(); + } + + if (!webview_process) { + webview_process = winrt::Windows::Web::UI::Interop::WebViewControlProcess(webview_process_options); + } + auto asyncwebview = webview_process.CreateWebViewControlAsync((int64_t)main_window_handler, hwnd_client_rect_to_bounds_rect(main_window_handler)); + asyncwebview.Completed([=](IAsyncOperation const& sender, AsyncStatus args) { + webview_control = sender.GetResults(); + + // In order to receive window.external.notify() calls in ScriptNotify + webview_control.Settings().IsScriptNotifyAllowed(true); + + webview_control.Settings().IsJavaScriptEnabled(true); + + webview_control.NewWindowRequested([=](IWebViewControl sender_requester, WebViewControlNewWindowRequestedEventArgs args ) { + // Open the requested link in the default browser registered in the Shell + ShellExecute(NULL, L"open", args.Uri().AbsoluteUri().c_str(), NULL, NULL, SW_SHOWNORMAL); + }); + + webview_control.DOMContentLoaded([=](IWebViewControl sender_loaded, WebViewControlDOMContentLoadedEventArgs const& args_loaded) { + // runs when the content has been loaded. + }); + webview_control.ScriptNotify([=](IWebViewControl sender_script_notify, WebViewControlScriptNotifyEventArgs const& args_script_notify) { + // content called window.external.notify() + std::wstring message_sent = args_script_notify.Value().c_str(); + receive_message_from_webview(message_sent); + }); + resize_web_view(); +#if defined(_DEBUG) && _DEBUG_WITH_LOCALHOST + // navigates to localhost:8080 + NavigateToLocalhostReactServer(); +#else + // navigates to settings-html/index.html + ShowWindow(main_window_handler, nCmdShow); + NavigateToUri(L"index.html"); +#endif + }); + } + catch (hresult_error const& e) { + WCHAR message[1024] = L""; + StringCchPrintf(message, ARRAYSIZE(message), L"failed: %ls", e.message().c_str()); + MessageBox(main_window_handler, message, L"Error", MB_OK); + } +} + +LRESULT CALLBACK wnd_proc_static(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_CLOSE: + if(m_waiting_for_close_confirmation) { + // If another WM_CLOSE is received while waiting for webview confirmation, + // allow DefWindowProc to be called and destroy the window. + break; + } else { + // Allow user to confirm exit in the WebView in case there's possible data loss. + m_waiting_for_close_confirmation = true; + if (webview_control != NULL) { + webview_control.InvokeScriptAsync(hstring(L"exit_settings_app"), {}); + } else { + break; + } + return 0; + } + case WM_DESTROY: + PostQuitMessage(0); + break; + case WM_SIZE: + if (webview_control != nullptr) { + resize_web_view(); + } + break; + case WM_CREATE: + wm_copydata_webview = RegisterWindowMessageW(L"PTSettingsCopyDataWebView"); + wm_my_destroy_window = RegisterWindowMessageW(L"PTSettingsParentTerminated"); + m_window_created_mutex.unlock(); + break; + case WM_DPICHANGED: + { + // Resize the window using the suggested rect + RECT* const prcNewWindow = (RECT*)lParam; + SetWindowPos(hWnd, + NULL, + prcNewWindow->left, + prcNewWindow->top, + prcNewWindow->right - prcNewWindow->left, + prcNewWindow->bottom - prcNewWindow->top, + SWP_NOZORDER | SWP_NOACTIVATE); + } + break; + case WM_NCCREATE: + { + // Enable auto-resizing the title bar + EnableNonClientDpiScaling(hWnd); + } + break; + default: + if (message == wm_copydata_webview) { + PCOPYDATASTRUCT msg = (PCOPYDATASTRUCT)lParam; + if (msg->dwData == SEND_TO_WEBVIEW_MSG) { + wchar_t* json_message = (wchar_t*)(msg->lpData); + if (webview_control != NULL) { + webview_control.InvokeScriptAsync(hstring(L"receive_from_settings_app"), { hstring(json_message) }); + } + delete[] json_message; + } + // wnd_proc_static is responsible for freeing memory. + delete msg; + } else { + if (message == wm_my_destroy_window) { + DestroyWindow(hWnd); + } + } + break; + } + return DefWindowProc(hWnd, message, wParam, lParam);; +} + +void register_classes(HINSTANCE hInstance) { + WNDCLASSEXW wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = wnd_proc_static; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(APPICON)); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = nullptr; + wcex.lpszClassName = L"PTSettingsClass"; + wcex.hIconSm = nullptr; + + RegisterClassExW(&wcex); +} + +int init_instance(HINSTANCE hInstance, int nCmdShow) { + m_hInst = hInstance; + + RECT desktopRect; + const HWND hDesktop = GetDesktopWindow(); + GetWindowRect(hDesktop, &desktopRect); + + int wind_width = 1024; + int wind_height = 700; + DPIAware::Convert(NULL, wind_width, wind_height); + + main_window_handler = CreateWindowW( + L"PTSettingsClass", + L"PowerToys Settings", + WS_OVERLAPPEDWINDOW, + (desktopRect.right - wind_width)/2, + (desktopRect.bottom - wind_height)/2, + wind_width, + wind_height, + nullptr, + nullptr, + hInstance, + nullptr); + + initialize_win32_webview(main_window_handler, nCmdShow); + UpdateWindow(main_window_handler); + + return TRUE; +} + +void wait_on_parent_process_thread(DWORD pid) { + HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid); + if (process != NULL) { + if (WaitForSingleObject(process, INFINITE) == WAIT_OBJECT_0) { + // If it's possible to detect when the PowerToys process terminates, message the main window. + CloseHandle(process); + { + // Send a terminated message only after the window has finished initializing. + std::unique_lock lock(m_window_created_mutex); + } + PostMessage(main_window_handler, wm_my_destroy_window, 0, 0); + } else { + CloseHandle(process); + } + } +} + +void quit_when_parent_terminates(std::wstring parent_pid) { + DWORD pid = std::stol(parent_pid); + std::thread(wait_on_parent_process_thread,pid).detach(); +} + +void read_arguments() { + // Expected calling arguments: + // [0] - This executable's path. + // [1] - PowerToys pipe server. + // [2] - Settings pipe server. + // [3] - PowerToys process pid. + LPWSTR *argument_list; + int n_args; + + argument_list = CommandLineToArgvW(GetCommandLineW(), &n_args); + if (n_args > 3) { + current_settings_ipc = new TwoWayPipeMessageIPC(std::wstring(argument_list[2]), std::wstring(argument_list[1]), send_message_to_webview); + current_settings_ipc->start(NULL); + quit_when_parent_terminates(std::wstring(argument_list[3])); + } else { +#ifndef _DEBUG + MessageBox(NULL, L"This executable isn't supposed to be called as a stand-alone process", L"Error running settings", MB_OK); + exit(1); +#endif + } + LocalFree(argument_list); +} + +int start_webview_window(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + // To be unlocked after the Window has finished being created. + m_window_created_mutex.lock(); + read_arguments(); + register_classes(hInstance); + init_instance(hInstance, nCmdShow); + MSG msg; + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (int)msg.wParam; +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + HRESULT hrInit = CoInitialize(nullptr); + return start_webview_window(hInstance, hPrevInstance, lpCmdLine, nCmdShow); +} diff --git a/src/editor/pch.cpp b/src/editor/pch.cpp new file mode 100644 index 00000000000..1d9f38c57d6 --- /dev/null +++ b/src/editor/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/editor/pch.h b/src/editor/pch.h new file mode 100644 index 00000000000..ddb5596363d --- /dev/null +++ b/src/editor/pch.h @@ -0,0 +1,17 @@ +#pragma once +#include +#pragma push_macro("GetCurrentTime") +#undef GetCurrentTime +// include winrt headers with fix for "warning C4002: Too many arguments for function-like macro invocation GetCurrentTime" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma pop_macro("GetCurrentTime") +#include +#include diff --git a/src/editor/resource.h b/src/editor/resource.h new file mode 100644 index 00000000000..268c4bbe4b7 --- /dev/null +++ b/src/editor/resource.h @@ -0,0 +1 @@ +#define APPICON 101 diff --git a/src/editor/settings-html/200.html b/src/editor/settings-html/200.html new file mode 100644 index 00000000000..d155482b139 --- /dev/null +++ b/src/editor/settings-html/200.html @@ -0,0 +1,25 @@ + + + + + PowerToys Settings + + +
+ + + diff --git a/src/editor/settings-html/dist/bundle.js b/src/editor/settings-html/dist/bundle.js new file mode 100644 index 00000000000..cc65753e101 --- /dev/null +++ b/src/editor/settings-html/dist/bundle.js @@ -0,0 +1,31 @@ +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=29)}([function(e,t,n){"use strict";e.exports=n(20)},,,,,,function(e,t,n){"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(21)},,,,,,,function(e,t,n){"use strict"; +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/var o=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var o={};return"abcdefghijklmnopqrst".split("").forEach(function(e){o[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},o)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,s,l=a(e),u=1;u0&&p(t)})}function p(e,t){a.loadStyles?a.loadStyles(g(e).styleString,e):n?function(e,t){if("undefined"==typeof document)return;var n=document.getElementsByTagName("head")[0],o=a.registeredStyles,r=a.lastStyleElement,i=r?r.styleSheet:void 0,s=i?i.cssText:"",u=o[o.length-1],c=g(e).styleString;(!r||s.length+c.length>l)&&((r=document.createElement("style")).type="text/css",t?(n.replaceChild(r,t.styleElement),t.styleElement=r):n.appendChild(r),t||(u={styleElement:r,themableStyle:e},o.push(u)));r.styleSheet.cssText+=m(c),Array.prototype.push.apply(u.themableStyle,e),a.lastStyleElement=r}(e,t):function(e){if("undefined"==typeof document)return;var t=document.getElementsByTagName("head")[0],n=document.createElement("style"),o=g(e),r=o.styleString,s=o.themable;n.type="text/css",i&&n.setAttribute("nonce",i);n.appendChild(document.createTextNode(r)),a.perf.count++,t.appendChild(n);var l={styleElement:n,themableStyle:e};s?a.registeredThemableStyles.push(l):a.registeredStyles.push(l)}(e)}function f(e){void 0===e&&(e=3),3!==e&&2!==e||(h(a.registeredStyles),a.registeredStyles=[]),3!==e&&1!==e||(h(a.registeredThemableStyles),a.registeredThemableStyles=[])}function h(e){e.forEach(function(e){var t=e&&e.styleElement;t&&t.parentElement&&t.parentElement.removeChild(t)})}function m(e){return e&&(e=g(v(e)).styleString),e}function g(e){var t=a.theme,n=!1;return{styleString:(e||[]).map(function(e){var o=e.theme;if(o){n=!0;var r=t?t[o]:void 0,i=e.defaultValue||"inherit";return!t||r||!console||o in t||"undefined"==typeof DEBUG||!DEBUG||console.warn('Theming value not provided for "'+o+'". Falling back to "'+i+'".'),r||i}return e.rawString}).join(""),themable:n}}function v(e){var t=[];if(e){for(var n=0,o=void 0;o=s.exec(e);){var r=o.index;r>n&&t.push({rawString:e.substring(n,r)}),t.push({theme:o[1],defaultValue:o[2]}),n=s.lastIndex}t.push({rawString:e.substring(n)})}return t}t.loadStyles=function(e,t){void 0===t&&(t=!1),c(function(){var o=Array.isArray(e)?e:v(e);void 0===n&&(n=function(){var e=!1;if("undefined"!=typeof document){var t=document.createElement("style");t.type="text/css",e=!!t.styleSheet}return e}());var r=a.runState,i=r.mode,s=r.buffer,l=r.flushTimer;t||1===i?(s.push(o),l||(a.runState.flushTimer=setTimeout(function(){a.runState.flushTimer=0,d()},0))):p(o)})},t.configureLoadStyles=function(e){a.loadStyles=e},t.configureRunMode=function(e){a.runState.mode=e},t.flush=d,t.loadTheme=function(e){a.theme=e,function(){if(a.theme){for(var e=[],t=0,n=a.registeredThemableStyles;t0&&(f(1),p([].concat.apply([],e)))}}()},t.clearStyles=f,t.detokenize=m,t.splitStyles=v}).call(this,n(14))},,,,,function(e,t,n){"use strict"; +/** @license React v16.8.6 + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var o=n(13),r="function"==typeof Symbol&&Symbol.for,i=r?Symbol.for("react.element"):60103,a=r?Symbol.for("react.portal"):60106,s=r?Symbol.for("react.fragment"):60107,l=r?Symbol.for("react.strict_mode"):60108,u=r?Symbol.for("react.profiler"):60114,c=r?Symbol.for("react.provider"):60109,d=r?Symbol.for("react.context"):60110,p=r?Symbol.for("react.concurrent_mode"):60111,f=r?Symbol.for("react.forward_ref"):60112,h=r?Symbol.for("react.suspense"):60113,m=r?Symbol.for("react.memo"):60115,g=r?Symbol.for("react.lazy"):60116,v="function"==typeof Symbol&&Symbol.iterator;function y(e){for(var t=arguments.length-1,n="https://reactjs.org/docs/error-decoder.html?invariant="+e,o=0;oA.length&&A.push(e)}function F(e,t,n){return null==e?0:function e(t,n,o,r){var s=typeof t;"undefined"!==s&&"boolean"!==s||(t=null);var l=!1;if(null===t)l=!0;else switch(s){case"string":case"number":l=!0;break;case"object":switch(t.$$typeof){case i:case a:l=!0}}if(l)return o(r,t,""===n?"."+O(t,0):n),1;if(l=0,n=""===n?".":n+":",Array.isArray(t))for(var u=0;uthis.eventPool.length&&this.eventPool.push(e)}function de(e){e.eventPool=[],e.getPooled=ue,e.release=ce}r(le.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=ae)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=ae)},persist:function(){this.isPersistent=ae},isPersistent:se,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=se,this._dispatchInstances=this._dispatchListeners=null}}),le.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},le.extend=function(e){function t(){}function n(){return o.apply(this,arguments)}var o=this;t.prototype=o.prototype;var i=new t;return r(i,n.prototype),n.prototype=i,n.prototype.constructor=n,n.Interface=r({},o.Interface,e),n.extend=o.extend,de(n),n},de(le);var pe=le.extend({data:null}),fe=le.extend({data:null}),he=[9,13,27,32],me=V&&"CompositionEvent"in window,ge=null;V&&"documentMode"in document&&(ge=document.documentMode);var ve=V&&"TextEvent"in window&&!ge,ye=V&&(!me||ge&&8=ge),be=String.fromCharCode(32),_e={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},xe=!1;function ke(e,t){switch(e){case"keyup":return-1!==he.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function Ce(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var we=!1;var Ee={eventTypes:_e,extractEvents:function(e,t,n,o){var r=void 0,i=void 0;if(me)e:{switch(e){case"compositionstart":r=_e.compositionStart;break e;case"compositionend":r=_e.compositionEnd;break e;case"compositionupdate":r=_e.compositionUpdate;break e}r=void 0}else we?ke(e,n)&&(r=_e.compositionEnd):"keydown"===e&&229===n.keyCode&&(r=_e.compositionStart);return r?(ye&&"ko"!==n.locale&&(we||r!==_e.compositionStart?r===_e.compositionEnd&&we&&(i=ie()):(oe="value"in(ne=o)?ne.value:ne.textContent,we=!0)),r=pe.getPooled(r,t,n,o),i?r.data=i:null!==(i=Ce(n))&&(r.data=i),j(r),i=r):i=null,(e=ve?function(e,t){switch(e){case"compositionend":return Ce(t);case"keypress":return 32!==t.which?null:(xe=!0,be);case"textInput":return(e=t.data)===be&&xe?null:e;default:return null}}(e,n):function(e,t){if(we)return"compositionend"===e||!me&&ke(e,t)?(e=ie(),re=oe=ne=null,we=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1