├── .gitignore ├── LICENSE ├── README.md └── src ├── DPAPI.cs ├── DPAPIbridge.csproj ├── DPAPIbridge.sln ├── Program.cs ├── Properties └── AssemblyInfo.cs └── vendor └── Options.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 |  Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2015 Vincent Paré 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DPAPIbridge 2 | --- 3 | DPAPIBridge is a command line tool designed to encrypt and decrypt data using [Windows Data Protection API](https://msdn.microsoft.com/en-us/library/ms995355.aspx) (DPAPI). 4 | 5 | DPAPI is a windows built-in feature providing a ciphering service tied to the user windows account. DPAPI is only available through `CryptProtectData` and `CryptUnpotectData` functions, contained in `Crypt32.dll`, but there is no end user interface to it. This is what DPAPIbridge is for. 6 | 7 | As a command line tool, DPAPIbridge is helpful to use DPAPI in a programming language that can't handle it natively (such as PHP). A typical use would be to store sensitive data (such as credentials) that you need at runtime from a scheduled task and you don't want to store those clear. Such sensitive data would be stored encrypted in a file, and then decrypted at runtime. 8 | 9 | ### Download ### 10 | [Download dpapibridge.exe](https://github.com/finalclap/DPAPIbridge/releases/download/1.0.0/dpapibridge.exe) 11 | 12 | ##### Requirements 13 | .NET Framework 2.0 or higher 14 | 15 | Works on Windows XP, Windows Vista, Windows 7 & Windows 8.1. 16 | 17 | 18 | ### Examples 19 | Encrypt raw : 20 | ``` 21 | dpapibridge --encrypt --input "foo bar" > encrypted.dat 22 | echo foo bar | dpapibridge --encrypt > encrypted.dat 23 | ``` 24 | 25 | Encrypt using base64 encoding : 26 | ``` 27 | dpapibridge --encrypt --base64 --input Zm9vIGJhcg== > encrypted.dat 28 | echo Zm9vIGJhcg== | dpapibridge --encrypt --base64 > encrypted.dat 29 | ``` 30 | 31 | Decrypt raw : 32 | ``` 33 | dpapibridge --decrypt < encrypted.dat 34 | dpapibridge --decrypt --input "AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAGMyvbyF [...]" 35 | ``` 36 | 37 | Decrypt using base64 encoding : 38 | ``` 39 | dpapibridge --decrypt --base64 < encrypted.dat 40 | dpapibridge --decrypt --base64 --input "AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAGMyvbyF [...]" 41 | ``` 42 | 43 | 44 | ### Usage ### 45 | ```bash 46 | Usage: dpapibridge (--encrypt|--decrypt) [--base64] [--input=] 47 | Options: 48 | -e, --encrypt Encrypt input data 49 | -d, --decrypt Decrypt input data 50 | -i, --input=VALUE Get input data from this argument (rather than 51 | stdin) 52 | -b, --base64 Encrypt mode : handle input as base64 encoded 53 | data. Decrypt mode : output base64-encoded 54 | result. Use it to avoid troubles when clear data 55 | contains non ASCII bytes, like binary data. 56 | -o, --output=VALUE Send output to file (instead of stdout) 57 | -?, -h, --help Show this message and exit 58 | ``` 59 | 60 | Powered by [DPAPI class from obviex.com](http://www.obviex.com/samples/dpapi.aspx) to wrap Crypt32.dll and [Mono Options](https://github.com/mono/mono/tree/master/mcs/class/Mono.Options) (formerly known as NDesk Options) for command line parsing. 61 | -------------------------------------------------------------------------------- /src/DPAPI.cs: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // SAMPLE: Encryption and decryption using DPAPI functions. 3 | // 4 | // To run this sample, create a new Visual C# project using the Console 5 | // Application template and replace the contents of the Class1.cs file 6 | // with the code below. 7 | // 8 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR 11 | // PURPOSE. 12 | // 13 | // Copyright (C) 2003 Obviex(TM). All rights reserved. 14 | // 15 | using System; 16 | using System.Text; 17 | using System.Runtime.InteropServices; 18 | using System.ComponentModel; 19 | 20 | namespace DPAPIbridge 21 | { 22 | /// 23 | /// Encrypts and decrypts data using DPAPI functions. 24 | /// 25 | public class DPAPI 26 | { 27 | // Wrapper for DPAPI CryptProtectData function. 28 | [DllImport("crypt32.dll", 29 | SetLastError = true, 30 | CharSet = System.Runtime.InteropServices.CharSet.Auto)] 31 | private static extern 32 | bool CryptProtectData(ref DATA_BLOB pPlainText, 33 | string szDescription, 34 | ref DATA_BLOB pEntropy, 35 | IntPtr pReserved, 36 | ref CRYPTPROTECT_PROMPTSTRUCT pPrompt, 37 | int dwFlags, 38 | ref DATA_BLOB pCipherText); 39 | 40 | // Wrapper for DPAPI CryptUnprotectData function. 41 | [DllImport("crypt32.dll", 42 | SetLastError = true, 43 | CharSet = System.Runtime.InteropServices.CharSet.Auto)] 44 | private static extern 45 | bool CryptUnprotectData(ref DATA_BLOB pCipherText, 46 | ref string pszDescription, 47 | ref DATA_BLOB pEntropy, 48 | IntPtr pReserved, 49 | ref CRYPTPROTECT_PROMPTSTRUCT pPrompt, 50 | int dwFlags, 51 | ref DATA_BLOB pPlainText); 52 | 53 | // BLOB structure used to pass data to DPAPI functions. 54 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 55 | internal struct DATA_BLOB 56 | { 57 | public int cbData; 58 | public IntPtr pbData; 59 | } 60 | 61 | // Prompt structure to be used for required parameters. 62 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 63 | internal struct CRYPTPROTECT_PROMPTSTRUCT 64 | { 65 | public int cbSize; 66 | public int dwPromptFlags; 67 | public IntPtr hwndApp; 68 | public string szPrompt; 69 | } 70 | 71 | // Wrapper for the NULL handle or pointer. 72 | static private IntPtr NullPtr = ((IntPtr)((int)(0))); 73 | 74 | // DPAPI key initialization flags. 75 | private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1; 76 | private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4; 77 | 78 | /// 79 | /// Initializes empty prompt structure. 80 | /// 81 | /// 82 | /// Prompt parameter (which we do not actually need). 83 | /// 84 | private static void InitPrompt(ref CRYPTPROTECT_PROMPTSTRUCT ps) 85 | { 86 | ps.cbSize = Marshal.SizeOf( 87 | typeof(CRYPTPROTECT_PROMPTSTRUCT)); 88 | ps.dwPromptFlags = 0; 89 | ps.hwndApp = NullPtr; 90 | ps.szPrompt = null; 91 | } 92 | 93 | /// 94 | /// Initializes a BLOB structure from a byte array. 95 | /// 96 | /// 97 | /// Original data in a byte array format. 98 | /// 99 | /// 100 | /// Returned blob structure. 101 | /// 102 | private static void InitBLOB(byte[] data, ref DATA_BLOB blob) 103 | { 104 | // Use empty array for null parameter. 105 | if (data == null) 106 | data = new byte[0]; 107 | 108 | // Allocate memory for the BLOB data. 109 | blob.pbData = Marshal.AllocHGlobal(data.Length); 110 | 111 | // Make sure that memory allocation was successful. 112 | if (blob.pbData == IntPtr.Zero) 113 | throw new Exception( 114 | "Unable to allocate data buffer for BLOB structure."); 115 | 116 | // Specify number of bytes in the BLOB. 117 | blob.cbData = data.Length; 118 | 119 | // Copy data from original source to the BLOB structure. 120 | Marshal.Copy(data, 0, blob.pbData, data.Length); 121 | } 122 | 123 | // Flag indicating the type of key. DPAPI terminology refers to 124 | // key types as user store or machine store. 125 | public enum KeyType { UserKey = 1, MachineKey }; 126 | 127 | // It is reasonable to set default key type to user key. 128 | private static KeyType defaultKeyType = KeyType.UserKey; 129 | 130 | /// 131 | /// Calls DPAPI CryptProtectData function to encrypt a plaintext 132 | /// string value with a user-specific key. This function does not 133 | /// specify data description and additional entropy. 134 | /// 135 | /// 136 | /// Plaintext data to be encrypted. 137 | /// 138 | /// 139 | /// Encrypted value in a base64-encoded format. 140 | /// 141 | public static string Encrypt(string plainText) 142 | { 143 | return Encrypt(defaultKeyType, plainText, String.Empty, 144 | String.Empty); 145 | } 146 | 147 | /// 148 | /// Calls DPAPI CryptProtectData function to encrypt a plaintext 149 | /// string value. This function does not specify data description 150 | /// and additional entropy. 151 | /// 152 | /// 153 | /// Defines type of encryption key to use. When user key is 154 | /// specified, any application running under the same user account 155 | /// as the one making this call, will be able to decrypt data. 156 | /// Machine key will allow any application running on the same 157 | /// computer where data were encrypted to perform decryption. 158 | /// Note: If optional entropy is specifed, it will be required 159 | /// for decryption. 160 | /// 161 | /// 162 | /// Plaintext data to be encrypted. 163 | /// 164 | /// 165 | /// Encrypted value in a base64-encoded format. 166 | /// 167 | public static string Encrypt(KeyType keyType, string plainText) 168 | { 169 | return Encrypt(keyType, plainText, String.Empty, 170 | String.Empty); 171 | } 172 | 173 | /// 174 | /// Calls DPAPI CryptProtectData function to encrypt a plaintext 175 | /// string value. This function does not specify data description. 176 | /// 177 | /// 178 | /// Defines type of encryption key to use. When user key is 179 | /// specified, any application running under the same user account 180 | /// as the one making this call, will be able to decrypt data. 181 | /// Machine key will allow any application running on the same 182 | /// computer where data were encrypted to perform decryption. 183 | /// Note: If optional entropy is specifed, it will be required 184 | /// for decryption. 185 | /// 186 | /// 187 | /// Plaintext data to be encrypted. 188 | /// 189 | /// 190 | /// Optional entropy which - if specified - will be required to 191 | /// perform decryption. 192 | /// 193 | /// 194 | /// Encrypted value in a base64-encoded format. 195 | /// 196 | public static string Encrypt(KeyType keyType, 197 | string plainText, 198 | string entropy) 199 | { 200 | return Encrypt(keyType, plainText, entropy, String.Empty); 201 | } 202 | 203 | /// 204 | /// Calls DPAPI CryptProtectData function to encrypt a plaintext 205 | /// string value. 206 | /// 207 | /// 208 | /// Defines type of encryption key to use. When user key is 209 | /// specified, any application running under the same user account 210 | /// as the one making this call, will be able to decrypt data. 211 | /// Machine key will allow any application running on the same 212 | /// computer where data were encrypted to perform decryption. 213 | /// Note: If optional entropy is specifed, it will be required 214 | /// for decryption. 215 | /// 216 | /// 217 | /// Plaintext data to be encrypted. 218 | /// 219 | /// 220 | /// Optional entropy which - if specified - will be required to 221 | /// perform decryption. 222 | /// 223 | /// 224 | /// Optional description of data to be encrypted. If this value is 225 | /// specified, it will be stored along with encrypted data and 226 | /// returned as a separate value during decryption. 227 | /// 228 | /// 229 | /// Encrypted value in a base64-encoded format. 230 | /// 231 | public static string Encrypt(KeyType keyType, 232 | string plainText, 233 | string entropy, 234 | string description) 235 | { 236 | // Make sure that parameters are valid. 237 | if (plainText == null) plainText = String.Empty; 238 | if (entropy == null) entropy = String.Empty; 239 | 240 | // Call encryption routine and convert returned bytes into 241 | // a base64-encoded value. 242 | return Convert.ToBase64String( 243 | Encrypt(keyType, 244 | Encoding.UTF8.GetBytes(plainText), 245 | Encoding.UTF8.GetBytes(entropy), 246 | description)); 247 | } 248 | 249 | /// 250 | /// Calls DPAPI CryptProtectData function to encrypt an array of 251 | /// plaintext bytes. 252 | /// 253 | /// 254 | /// Defines type of encryption key to use. When user key is 255 | /// specified, any application running under the same user account 256 | /// as the one making this call, will be able to decrypt data. 257 | /// Machine key will allow any application running on the same 258 | /// computer where data were encrypted to perform decryption. 259 | /// Note: If optional entropy is specifed, it will be required 260 | /// for decryption. 261 | /// 262 | /// 263 | /// Plaintext data to be encrypted. 264 | /// 265 | /// 266 | /// Optional entropy which - if specified - will be required to 267 | /// perform decryption. 268 | /// 269 | /// 270 | /// Optional description of data to be encrypted. If this value is 271 | /// specified, it will be stored along with encrypted data and 272 | /// returned as a separate value during decryption. 273 | /// 274 | /// 275 | /// Encrypted value. 276 | /// 277 | public static byte[] Encrypt(KeyType keyType, 278 | byte[] plainTextBytes, 279 | byte[] entropyBytes, 280 | string description) 281 | { 282 | // Make sure that parameters are valid. 283 | if (plainTextBytes == null) plainTextBytes = new byte[0]; 284 | if (entropyBytes == null) entropyBytes = new byte[0]; 285 | if (description == null) description = String.Empty; 286 | 287 | // Create BLOBs to hold data. 288 | DATA_BLOB plainTextBlob = new DATA_BLOB(); 289 | DATA_BLOB cipherTextBlob = new DATA_BLOB(); 290 | DATA_BLOB entropyBlob = new DATA_BLOB(); 291 | 292 | // We only need prompt structure because it is a required 293 | // parameter. 294 | CRYPTPROTECT_PROMPTSTRUCT prompt = 295 | new CRYPTPROTECT_PROMPTSTRUCT(); 296 | InitPrompt(ref prompt); 297 | 298 | try 299 | { 300 | // Convert plaintext bytes into a BLOB structure. 301 | try 302 | { 303 | InitBLOB(plainTextBytes, ref plainTextBlob); 304 | } 305 | catch (Exception ex) 306 | { 307 | throw new Exception( 308 | "Cannot initialize plaintext BLOB.", ex); 309 | } 310 | 311 | // Convert entropy bytes into a BLOB structure. 312 | try 313 | { 314 | InitBLOB(entropyBytes, ref entropyBlob); 315 | } 316 | catch (Exception ex) 317 | { 318 | throw new Exception( 319 | "Cannot initialize entropy BLOB.", ex); 320 | } 321 | 322 | // Disable any types of UI. 323 | int flags = CRYPTPROTECT_UI_FORBIDDEN; 324 | 325 | // When using machine-specific key, set up machine flag. 326 | if (keyType == KeyType.MachineKey) 327 | flags |= CRYPTPROTECT_LOCAL_MACHINE; 328 | 329 | // Call DPAPI to encrypt data. 330 | bool success = CryptProtectData(ref plainTextBlob, 331 | description, 332 | ref entropyBlob, 333 | IntPtr.Zero, 334 | ref prompt, 335 | flags, 336 | ref cipherTextBlob); 337 | // Check the result. 338 | if (!success) 339 | { 340 | // If operation failed, retrieve last Win32 error. 341 | int errCode = Marshal.GetLastWin32Error(); 342 | 343 | // Win32Exception will contain error message corresponding 344 | // to the Windows error code. 345 | throw new Exception( 346 | "CryptProtectData failed.", new Win32Exception(errCode)); 347 | } 348 | 349 | // Allocate memory to hold ciphertext. 350 | byte[] cipherTextBytes = new byte[cipherTextBlob.cbData]; 351 | 352 | // Copy ciphertext from the BLOB to a byte array. 353 | Marshal.Copy(cipherTextBlob.pbData, 354 | cipherTextBytes, 355 | 0, 356 | cipherTextBlob.cbData); 357 | 358 | // Return the result. 359 | return cipherTextBytes; 360 | } 361 | catch (Exception ex) 362 | { 363 | throw new Exception("DPAPI was unable to encrypt data.", ex); 364 | } 365 | // Free all memory allocated for BLOBs. 366 | finally 367 | { 368 | if (plainTextBlob.pbData != IntPtr.Zero) 369 | Marshal.FreeHGlobal(plainTextBlob.pbData); 370 | 371 | if (cipherTextBlob.pbData != IntPtr.Zero) 372 | Marshal.FreeHGlobal(cipherTextBlob.pbData); 373 | 374 | if (entropyBlob.pbData != IntPtr.Zero) 375 | Marshal.FreeHGlobal(entropyBlob.pbData); 376 | } 377 | } 378 | 379 | /// 380 | /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 381 | /// This function does not use additional entropy and does not 382 | /// return data description. 383 | /// 384 | /// 385 | /// Encrypted data formatted as a base64-encoded string. 386 | /// 387 | /// 388 | /// Decrypted data returned as a UTF-8 string. 389 | /// 390 | /// 391 | /// When decrypting data, it is not necessary to specify which 392 | /// type of encryption key to use: user-specific or 393 | /// machine-specific; DPAPI will figure it out by looking at 394 | /// the signature of encrypted data. 395 | /// 396 | public static string Decrypt(string cipherText) 397 | { 398 | string description; 399 | 400 | return Decrypt(cipherText, String.Empty, out description); 401 | } 402 | 403 | /// 404 | /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 405 | /// This function does not use additional entropy. 406 | /// 407 | /// 408 | /// Encrypted data formatted as a base64-encoded string. 409 | /// 410 | /// 411 | /// Returned description of data specified during encryption. 412 | /// 413 | /// 414 | /// Decrypted data returned as a UTF-8 string. 415 | /// 416 | /// 417 | /// When decrypting data, it is not necessary to specify which 418 | /// type of encryption key to use: user-specific or 419 | /// machine-specific; DPAPI will figure it out by looking at 420 | /// the signature of encrypted data. 421 | /// 422 | public static string Decrypt(string cipherText, 423 | out string description) 424 | { 425 | return Decrypt(cipherText, String.Empty, out description); 426 | } 427 | 428 | /// 429 | /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 430 | /// 431 | /// 432 | /// Encrypted data formatted as a base64-encoded string. 433 | /// 434 | /// 435 | /// Optional entropy, which is required if it was specified during 436 | /// encryption. 437 | /// 438 | /// 439 | /// Returned description of data specified during encryption. 440 | /// 441 | /// 442 | /// Decrypted data returned as a UTF-8 string. 443 | /// 444 | /// 445 | /// When decrypting data, it is not necessary to specify which 446 | /// type of encryption key to use: user-specific or 447 | /// machine-specific; DPAPI will figure it out by looking at 448 | /// the signature of encrypted data. 449 | /// 450 | public static string Decrypt(string cipherText, 451 | string entropy, 452 | out string description) 453 | { 454 | // Make sure that parameters are valid. 455 | if (entropy == null) entropy = String.Empty; 456 | 457 | return Encoding.UTF8.GetString( 458 | Decrypt(Convert.FromBase64String(cipherText), 459 | Encoding.UTF8.GetBytes(entropy), 460 | out description)); 461 | } 462 | 463 | /// 464 | /// Calls DPAPI CryptUnprotectData to decrypt ciphertext bytes. 465 | /// 466 | /// 467 | /// Encrypted data. 468 | /// 469 | /// 470 | /// Optional entropy, which is required if it was specified during 471 | /// encryption. 472 | /// 473 | /// 474 | /// Returned description of data specified during encryption. 475 | /// 476 | /// 477 | /// Decrypted data bytes. 478 | /// 479 | /// 480 | /// When decrypting data, it is not necessary to specify which 481 | /// type of encryption key to use: user-specific or 482 | /// machine-specific; DPAPI will figure it out by looking at 483 | /// the signature of encrypted data. 484 | /// 485 | public static byte[] Decrypt(byte[] cipherTextBytes, 486 | byte[] entropyBytes, 487 | out string description) 488 | { 489 | // Create BLOBs to hold data. 490 | DATA_BLOB plainTextBlob = new DATA_BLOB(); 491 | DATA_BLOB cipherTextBlob = new DATA_BLOB(); 492 | DATA_BLOB entropyBlob = new DATA_BLOB(); 493 | 494 | // We only need prompt structure because it is a required 495 | // parameter. 496 | CRYPTPROTECT_PROMPTSTRUCT prompt = 497 | new CRYPTPROTECT_PROMPTSTRUCT(); 498 | InitPrompt(ref prompt); 499 | 500 | // Initialize description string. 501 | description = String.Empty; 502 | 503 | try 504 | { 505 | // Convert ciphertext bytes into a BLOB structure. 506 | try 507 | { 508 | InitBLOB(cipherTextBytes, ref cipherTextBlob); 509 | } 510 | catch (Exception ex) 511 | { 512 | throw new Exception( 513 | "Cannot initialize ciphertext BLOB.", ex); 514 | } 515 | 516 | // Convert entropy bytes into a BLOB structure. 517 | try 518 | { 519 | InitBLOB(entropyBytes, ref entropyBlob); 520 | } 521 | catch (Exception ex) 522 | { 523 | throw new Exception( 524 | "Cannot initialize entropy BLOB.", ex); 525 | } 526 | 527 | // Disable any types of UI. CryptUnprotectData does not 528 | // mention CRYPTPROTECT_LOCAL_MACHINE flag in the list of 529 | // supported flags so we will not set it up. 530 | int flags = CRYPTPROTECT_UI_FORBIDDEN; 531 | 532 | // Call DPAPI to decrypt data. 533 | bool success = CryptUnprotectData(ref cipherTextBlob, 534 | ref description, 535 | ref entropyBlob, 536 | IntPtr.Zero, 537 | ref prompt, 538 | flags, 539 | ref plainTextBlob); 540 | 541 | // Check the result. 542 | if (!success) 543 | { 544 | // If operation failed, retrieve last Win32 error. 545 | int errCode = Marshal.GetLastWin32Error(); 546 | 547 | // Win32Exception will contain error message corresponding 548 | // to the Windows error code. 549 | throw new Exception( 550 | "CryptUnprotectData failed.", new Win32Exception(errCode)); 551 | } 552 | 553 | // Allocate memory to hold plaintext. 554 | byte[] plainTextBytes = new byte[plainTextBlob.cbData]; 555 | 556 | // Copy ciphertext from the BLOB to a byte array. 557 | Marshal.Copy(plainTextBlob.pbData, 558 | plainTextBytes, 559 | 0, 560 | plainTextBlob.cbData); 561 | 562 | // Return the result. 563 | return plainTextBytes; 564 | } 565 | catch (Exception ex) 566 | { 567 | throw new Exception("DPAPI was unable to decrypt data.", ex); 568 | } 569 | // Free all memory allocated for BLOBs. 570 | finally 571 | { 572 | if (plainTextBlob.pbData != IntPtr.Zero) 573 | Marshal.FreeHGlobal(plainTextBlob.pbData); 574 | 575 | if (cipherTextBlob.pbData != IntPtr.Zero) 576 | Marshal.FreeHGlobal(cipherTextBlob.pbData); 577 | 578 | if (entropyBlob.pbData != IntPtr.Zero) 579 | Marshal.FreeHGlobal(entropyBlob.pbData); 580 | } 581 | } 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /src/DPAPIbridge.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {6D8206D3-9EC6-4DFF-A6C9-6C64E027BDC3} 9 | Exe 10 | Properties 11 | DPAPIbridge 12 | dpapibridge 13 | v2.0 14 | 512 15 | 16 | 17 | x86 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | x86 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /src/DPAPIbridge.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DPAPIbridge", "DPAPIbridge.csproj", "{6D8206D3-9EC6-4DFF-A6C9-6C64E027BDC3}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {6D8206D3-9EC6-4DFF-A6C9-6C64E027BDC3}.Debug|x86.ActiveCfg = Debug|x86 13 | {6D8206D3-9EC6-4DFF-A6C9-6C64E027BDC3}.Debug|x86.Build.0 = Debug|x86 14 | {6D8206D3-9EC6-4DFF-A6C9-6C64E027BDC3}.Release|x86.ActiveCfg = Release|x86 15 | {6D8206D3-9EC6-4DFF-A6C9-6C64E027BDC3}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | /** 2 | * DPAPIBridge 3 | * Use Windows Data Protection API from command line 4 | * Requires .NET Framework 2.0 5 | * @author Vincent Paré 6 | */ 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Text; 11 | using System.Security.Cryptography; 12 | using NDesk.Options; 13 | using System.IO; 14 | 15 | namespace DPAPIbridge 16 | { 17 | class Program 18 | { 19 | static void Main(string[] args) 20 | { 21 | // Arguments initialization 22 | bool base64 = false; 23 | string mode = null; 24 | string input = null; 25 | bool show_help = false; 26 | byte[] inputBytes = new byte[0]; 27 | string outputFile = null; 28 | 29 | // Reading arguments 30 | var p = new OptionSet() { 31 | {"e|encrypt", "Encrypt input data", delegate (string v) {mode = "encrypt"; }}, 32 | {"d|decrypt", "Decrypt input data", delegate (string v) {mode = "decrypt"; }}, 33 | {"i|input=", "Get input data from this argument (rather than stdin)", delegate (string v) {if (v != null) input = v; }}, 34 | {"b|base64", "Encrypt mode : handle input as base64 encoded data. Decrypt mode : output base64-encoded result. Use it to avoid troubles when clear data contains non ASCII bytes, like binary data.", delegate (string v) {if (v != null) base64 = true; }}, 35 | {"o|output=", "Send output to file (instead of stdout)", delegate (string v) {if (v != null) outputFile = v; }}, 36 | { "?|h|help", "Show this message and exit", v => show_help = v != null }, 37 | }; 38 | List extra; 39 | try 40 | { 41 | extra = p.Parse(args); 42 | } 43 | catch (OptionException e) 44 | { 45 | Console.Error.WriteLine(e.Message); 46 | return; 47 | } 48 | 49 | // Print help message 50 | if (show_help) 51 | { 52 | Console.WriteLine("Usage: dpapibridge (--encrypt|--decrypt) [--base64] [--input=]"); 53 | Console.WriteLine("Options:"); 54 | p.WriteOptionDescriptions(Console.Out); 55 | return; 56 | } 57 | 58 | // Get input bytes 59 | if (input == null) 60 | { 61 | // Checking standard input for data 62 | bool inputAvailable = false; 63 | try 64 | { 65 | inputAvailable = Console.KeyAvailable; 66 | } 67 | catch (InvalidOperationException) 68 | { 69 | inputAvailable = true; 70 | } 71 | if (!inputAvailable) 72 | { 73 | Console.Error.WriteLine("No data available on standard input"); 74 | return; 75 | } 76 | 77 | // Reading standard input 78 | using (Stream stdin = Console.OpenStandardInput()) 79 | { 80 | byte[] buffer = new byte[2048]; 81 | int readBytes; 82 | while ((readBytes = stdin.Read(buffer, 0, buffer.Length)) > 0) 83 | { 84 | // Resize input array + content transfer 85 | byte[] newInputBytes = new byte[inputBytes.Length + readBytes]; 86 | Buffer.BlockCopy(inputBytes, 0, newInputBytes, 0, inputBytes.Length); 87 | 88 | // Append new data (buffer) 89 | Buffer.BlockCopy(buffer, 0, newInputBytes, inputBytes.Length, readBytes); 90 | inputBytes = newInputBytes; 91 | } 92 | } 93 | } 94 | else 95 | { 96 | // Convert --input from string to byte[] 97 | inputBytes = Encoding.UTF8.GetBytes(input); 98 | } 99 | 100 | // Dispatch 101 | switch (mode) 102 | { 103 | case "encrypt": 104 | Encrypt(inputBytes, base64); 105 | break; 106 | case "decrypt": 107 | Decrypt(inputBytes, base64, outputFile); 108 | break; 109 | default: 110 | Console.Error.WriteLine("Mode not set (you must either choose --encrypt or --decrypt)"); 111 | break; 112 | } 113 | } 114 | 115 | /** 116 | * Encrypts clearBytes and prints encrypted data (base64 encoded) 117 | * @param byte[] clearBytes : Clear data to encrypt 118 | * @param bool base64 : Tells if clearBytes contains base64-encoded data (true) or raw data (false) 119 | */ 120 | static void Encrypt(byte[] clearBytes, bool base64) 121 | { 122 | // base64 decode clearBytes 123 | if (base64) 124 | { 125 | string base64string = Encoding.UTF8.GetString(clearBytes); 126 | try 127 | { 128 | clearBytes = Convert.FromBase64String(base64string); 129 | } 130 | catch (FormatException) 131 | { 132 | Console.Error.WriteLine("Input data cannot be read as base64"); 133 | return; 134 | } 135 | } 136 | 137 | // Encryption 138 | byte[] entropy = null; 139 | byte[] encrypted = DPAPI.Encrypt(DPAPI.KeyType.UserKey, clearBytes, entropy, "Encrypted with Windows DPAPI through dpapibridge"); 140 | 141 | // Print result 142 | string encryptedBase64 = Convert.ToBase64String(encrypted); 143 | Console.Out.WriteLine(encryptedBase64); 144 | } 145 | 146 | /** 147 | * Decrypts encryptedBytes and prints clear data 148 | * @param byte[] encryptedBytes : Encrypted data to decrypt, base64-encoded 149 | * @param bool base64 : Encode output as base64 if true (useful when clear data contains non ASCII bytes) 150 | * @param string outputFile : File path to send output 151 | */ 152 | static void Decrypt(byte[] encryptedBytes, bool base64, string outputFile) 153 | { 154 | // base64 decode encryptedBytes 155 | string base64string = Encoding.UTF8.GetString(encryptedBytes); 156 | try 157 | { 158 | encryptedBytes = Convert.FromBase64String(base64string); 159 | } 160 | catch (FormatException) 161 | { 162 | Console.Error.WriteLine("Cannot base64-decode input"); 163 | return; 164 | } 165 | 166 | 167 | // Decryption 168 | byte[] entropy = null; 169 | string description; 170 | byte[] decrypted; 171 | try 172 | { 173 | decrypted = DPAPI.Decrypt(encryptedBytes, entropy, out description); 174 | } 175 | catch (Exception e) 176 | { 177 | Console.Error.WriteLine(e.Message); 178 | return; 179 | } 180 | 181 | // Save output to file 182 | string output; 183 | if (outputFile != null) 184 | { 185 | if (base64) 186 | { 187 | output = Convert.ToBase64String(decrypted); 188 | decrypted = Encoding.UTF8.GetBytes(output); 189 | } 190 | File.WriteAllBytes(outputFile, decrypted); 191 | Console.WriteLine("output saved to" + outputFile); 192 | return; 193 | } 194 | 195 | // Print result 196 | if (base64) 197 | { 198 | output = Convert.ToBase64String(decrypted); 199 | } 200 | else 201 | { 202 | output = Encoding.UTF8.GetString(decrypted); 203 | } 204 | Console.Out.WriteLine(output); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Les informations générales relatives à un assembly dépendent de 6 | // l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations 7 | // associées à un assembly. 8 | [assembly: AssemblyTitle("DPAPIbridge")] 9 | [assembly: AssemblyDescription("Data Protection API command line tool")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DPAPIbridge")] 13 | [assembly: AssemblyCopyright("Copyright © Vincent Paré 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly 18 | // aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de 19 | // COM, affectez la valeur true à l'attribut ComVisible sur ce type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM 23 | [assembly: Guid("ee8f752a-4bbb-4e0c-a34c-bfc3ae743102")] 24 | 25 | // Les informations de version pour un assembly se composent des quatre valeurs suivantes : 26 | // 27 | // Version principale 28 | // Version secondaire 29 | // Numéro de build 30 | // Révision 31 | // 32 | // Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut 33 | // en utilisant '*', comme indiqué ci-dessous : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/vendor/Options.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Options.cs 3 | // 4 | // Authors: 5 | // Jonathan Pryor 6 | // 7 | // Copyright (C) 2008 Novell (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | // Compile With: 30 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll 31 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll 32 | // 33 | // The LINQ version just changes the implementation of 34 | // OptionSet.Parse(IEnumerable), and confers no semantic changes. 35 | 36 | // 37 | // A Getopt::Long-inspired option parsing library for C#. 38 | // 39 | // NDesk.Options.OptionSet is built upon a key/value table, where the 40 | // key is a option format string and the value is a delegate that is 41 | // invoked when the format string is matched. 42 | // 43 | // Option format strings: 44 | // Regex-like BNF Grammar: 45 | // name: .+ 46 | // type: [=:] 47 | // sep: ( [^{}]+ | '{' .+ '}' )? 48 | // aliases: ( name type sep ) ( '|' name type sep )* 49 | // 50 | // Each '|'-delimited name is an alias for the associated action. If the 51 | // format string ends in a '=', it has a required value. If the format 52 | // string ends in a ':', it has an optional value. If neither '=' or ':' 53 | // is present, no value is supported. `=' or `:' need only be defined on one 54 | // alias, but if they are provided on more than one they must be consistent. 55 | // 56 | // Each alias portion may also end with a "key/value separator", which is used 57 | // to split option values if the option accepts > 1 value. If not specified, 58 | // it defaults to '=' and ':'. If specified, it can be any character except 59 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be 60 | // used (i.e. the separate values should be distinct arguments), then "{}" 61 | // should be used as the separator. 62 | // 63 | // Options are extracted either from the current option by looking for 64 | // the option name followed by an '=' or ':', or is taken from the 65 | // following option IFF: 66 | // - The current option does not contain a '=' or a ':' 67 | // - The current option requires a value (i.e. not a Option type of ':') 68 | // 69 | // The `name' used in the option format string does NOT include any leading 70 | // option indicator, such as '-', '--', or '/'. All three of these are 71 | // permitted/required on any named option. 72 | // 73 | // Option bundling is permitted so long as: 74 | // - '-' is used to start the option group 75 | // - all of the bundled options are a single character 76 | // - at most one of the bundled options accepts a value, and the value 77 | // provided starts from the next character to the end of the string. 78 | // 79 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' 80 | // as '-Dname=value'. 81 | // 82 | // Option processing is disabled by specifying "--". All options after "--" 83 | // are returned by OptionSet.Parse() unchanged and unprocessed. 84 | // 85 | // Unprocessed options are returned from OptionSet.Parse(). 86 | // 87 | // Examples: 88 | // int verbose = 0; 89 | // OptionSet p = new OptionSet () 90 | // .Add ("v", v => ++verbose) 91 | // .Add ("name=|value=", v => Console.WriteLine (v)); 92 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); 93 | // 94 | // The above would parse the argument string array, and would invoke the 95 | // lambda expression three times, setting `verbose' to 3 when complete. 96 | // It would also print out "A" and "B" to standard output. 97 | // The returned array would contain the string "extra". 98 | // 99 | // C# 3.0 collection initializers are supported and encouraged: 100 | // var p = new OptionSet () { 101 | // { "h|?|help", v => ShowHelp () }, 102 | // }; 103 | // 104 | // System.ComponentModel.TypeConverter is also supported, allowing the use of 105 | // custom data types in the callback type; TypeConverter.ConvertFromString() 106 | // is used to convert the value option to an instance of the specified 107 | // type: 108 | // 109 | // var p = new OptionSet () { 110 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, 111 | // }; 112 | // 113 | // Random other tidbits: 114 | // - Boolean options (those w/o '=' or ':' in the option format string) 115 | // are explicitly enabled if they are followed with '+', and explicitly 116 | // disabled if they are followed with '-': 117 | // string a = null; 118 | // var p = new OptionSet () { 119 | // { "a", s => a = s }, 120 | // }; 121 | // p.Parse (new string[]{"-a"}); // sets v != null 122 | // p.Parse (new string[]{"-a+"}); // sets v != null 123 | // p.Parse (new string[]{"-a-"}); // sets v == null 124 | // 125 | 126 | using System; 127 | using System.Collections; 128 | using System.Collections.Generic; 129 | using System.Collections.ObjectModel; 130 | using System.ComponentModel; 131 | using System.Globalization; 132 | using System.IO; 133 | using System.Runtime.Serialization; 134 | using System.Security.Permissions; 135 | using System.Text; 136 | using System.Text.RegularExpressions; 137 | 138 | #if LINQ 139 | using System.Linq; 140 | #endif 141 | 142 | #if TEST 143 | using NDesk.Options; 144 | #endif 145 | 146 | namespace NDesk.Options { 147 | 148 | public class OptionValueCollection : IList, IList { 149 | 150 | List values = new List (); 151 | OptionContext c; 152 | 153 | internal OptionValueCollection (OptionContext c) 154 | { 155 | this.c = c; 156 | } 157 | 158 | #region ICollection 159 | void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} 160 | bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} 161 | object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} 162 | #endregion 163 | 164 | #region ICollection 165 | public void Add (string item) {values.Add (item);} 166 | public void Clear () {values.Clear ();} 167 | public bool Contains (string item) {return values.Contains (item);} 168 | public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} 169 | public bool Remove (string item) {return values.Remove (item);} 170 | public int Count {get {return values.Count;}} 171 | public bool IsReadOnly {get {return false;}} 172 | #endregion 173 | 174 | #region IEnumerable 175 | IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} 176 | #endregion 177 | 178 | #region IEnumerable 179 | public IEnumerator GetEnumerator () {return values.GetEnumerator ();} 180 | #endregion 181 | 182 | #region IList 183 | int IList.Add (object value) {return (values as IList).Add (value);} 184 | bool IList.Contains (object value) {return (values as IList).Contains (value);} 185 | int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} 186 | void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} 187 | void IList.Remove (object value) {(values as IList).Remove (value);} 188 | void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} 189 | bool IList.IsFixedSize {get {return false;}} 190 | object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} 191 | #endregion 192 | 193 | #region IList 194 | public int IndexOf (string item) {return values.IndexOf (item);} 195 | public void Insert (int index, string item) {values.Insert (index, item);} 196 | public void RemoveAt (int index) {values.RemoveAt (index);} 197 | 198 | private void AssertValid (int index) 199 | { 200 | if (c.Option == null) 201 | throw new InvalidOperationException ("OptionContext.Option is null."); 202 | if (index >= c.Option.MaxValueCount) 203 | throw new ArgumentOutOfRangeException ("index"); 204 | if (c.Option.OptionValueType == OptionValueType.Required && 205 | index >= values.Count) 206 | throw new OptionException (string.Format ( 207 | c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 208 | c.OptionName); 209 | } 210 | 211 | public string this [int index] { 212 | get { 213 | AssertValid (index); 214 | return index >= values.Count ? null : values [index]; 215 | } 216 | set { 217 | values [index] = value; 218 | } 219 | } 220 | #endregion 221 | 222 | public List ToList () 223 | { 224 | return new List (values); 225 | } 226 | 227 | public string[] ToArray () 228 | { 229 | return values.ToArray (); 230 | } 231 | 232 | public override string ToString () 233 | { 234 | return string.Join (", ", values.ToArray ()); 235 | } 236 | } 237 | 238 | public class OptionContext { 239 | private Option option; 240 | private string name; 241 | private int index; 242 | private OptionSet set; 243 | private OptionValueCollection c; 244 | 245 | public OptionContext (OptionSet set) 246 | { 247 | this.set = set; 248 | this.c = new OptionValueCollection (this); 249 | } 250 | 251 | public Option Option { 252 | get {return option;} 253 | set {option = value;} 254 | } 255 | 256 | public string OptionName { 257 | get {return name;} 258 | set {name = value;} 259 | } 260 | 261 | public int OptionIndex { 262 | get {return index;} 263 | set {index = value;} 264 | } 265 | 266 | public OptionSet OptionSet { 267 | get {return set;} 268 | } 269 | 270 | public OptionValueCollection OptionValues { 271 | get {return c;} 272 | } 273 | } 274 | 275 | public enum OptionValueType { 276 | None, 277 | Optional, 278 | Required, 279 | } 280 | 281 | public abstract class Option { 282 | string prototype, description; 283 | string[] names; 284 | OptionValueType type; 285 | int count; 286 | string[] separators; 287 | 288 | protected Option (string prototype, string description) 289 | : this (prototype, description, 1) 290 | { 291 | } 292 | 293 | protected Option (string prototype, string description, int maxValueCount) 294 | { 295 | if (prototype == null) 296 | throw new ArgumentNullException ("prototype"); 297 | if (prototype.Length == 0) 298 | throw new ArgumentException ("Cannot be the empty string.", "prototype"); 299 | if (maxValueCount < 0) 300 | throw new ArgumentOutOfRangeException ("maxValueCount"); 301 | 302 | this.prototype = prototype; 303 | this.names = prototype.Split ('|'); 304 | this.description = description; 305 | this.count = maxValueCount; 306 | this.type = ParsePrototype (); 307 | 308 | if (this.count == 0 && type != OptionValueType.None) 309 | throw new ArgumentException ( 310 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + 311 | "OptionValueType.Optional.", 312 | "maxValueCount"); 313 | if (this.type == OptionValueType.None && maxValueCount > 1) 314 | throw new ArgumentException ( 315 | string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), 316 | "maxValueCount"); 317 | if (Array.IndexOf (names, "<>") >= 0 && 318 | ((names.Length == 1 && this.type != OptionValueType.None) || 319 | (names.Length > 1 && this.MaxValueCount > 1))) 320 | throw new ArgumentException ( 321 | "The default option handler '<>' cannot require values.", 322 | "prototype"); 323 | } 324 | 325 | public string Prototype {get {return prototype;}} 326 | public string Description {get {return description;}} 327 | public OptionValueType OptionValueType {get {return type;}} 328 | public int MaxValueCount {get {return count;}} 329 | 330 | public string[] GetNames () 331 | { 332 | return (string[]) names.Clone (); 333 | } 334 | 335 | public string[] GetValueSeparators () 336 | { 337 | if (separators == null) 338 | return new string [0]; 339 | return (string[]) separators.Clone (); 340 | } 341 | 342 | protected static T Parse (string value, OptionContext c) 343 | { 344 | TypeConverter conv = TypeDescriptor.GetConverter (typeof (T)); 345 | T t = default (T); 346 | try { 347 | if (value != null) 348 | t = (T) conv.ConvertFromString (value); 349 | } 350 | catch (Exception e) { 351 | throw new OptionException ( 352 | string.Format ( 353 | c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), 354 | value, typeof (T).Name, c.OptionName), 355 | c.OptionName, e); 356 | } 357 | return t; 358 | } 359 | 360 | internal string[] Names {get {return names;}} 361 | internal string[] ValueSeparators {get {return separators;}} 362 | 363 | static readonly char[] NameTerminator = new char[]{'=', ':'}; 364 | 365 | private OptionValueType ParsePrototype () 366 | { 367 | char type = '\0'; 368 | List seps = new List (); 369 | for (int i = 0; i < names.Length; ++i) { 370 | string name = names [i]; 371 | if (name.Length == 0) 372 | throw new ArgumentException ("Empty option names are not supported.", "prototype"); 373 | 374 | int end = name.IndexOfAny (NameTerminator); 375 | if (end == -1) 376 | continue; 377 | names [i] = name.Substring (0, end); 378 | if (type == '\0' || type == name [end]) 379 | type = name [end]; 380 | else 381 | throw new ArgumentException ( 382 | string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), 383 | "prototype"); 384 | AddSeparators (name, end, seps); 385 | } 386 | 387 | if (type == '\0') 388 | return OptionValueType.None; 389 | 390 | if (count <= 1 && seps.Count != 0) 391 | throw new ArgumentException ( 392 | string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), 393 | "prototype"); 394 | if (count > 1) { 395 | if (seps.Count == 0) 396 | this.separators = new string[]{":", "="}; 397 | else if (seps.Count == 1 && seps [0].Length == 0) 398 | this.separators = null; 399 | else 400 | this.separators = seps.ToArray (); 401 | } 402 | 403 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional; 404 | } 405 | 406 | private static void AddSeparators (string name, int end, ICollection seps) 407 | { 408 | int start = -1; 409 | for (int i = end+1; i < name.Length; ++i) { 410 | switch (name [i]) { 411 | case '{': 412 | if (start != -1) 413 | throw new ArgumentException ( 414 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 415 | "prototype"); 416 | start = i+1; 417 | break; 418 | case '}': 419 | if (start == -1) 420 | throw new ArgumentException ( 421 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 422 | "prototype"); 423 | seps.Add (name.Substring (start, i-start)); 424 | start = -1; 425 | break; 426 | default: 427 | if (start == -1) 428 | seps.Add (name [i].ToString ()); 429 | break; 430 | } 431 | } 432 | if (start != -1) 433 | throw new ArgumentException ( 434 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 435 | "prototype"); 436 | } 437 | 438 | public void Invoke (OptionContext c) 439 | { 440 | OnParseComplete (c); 441 | c.OptionName = null; 442 | c.Option = null; 443 | c.OptionValues.Clear (); 444 | } 445 | 446 | protected abstract void OnParseComplete (OptionContext c); 447 | 448 | public override string ToString () 449 | { 450 | return Prototype; 451 | } 452 | } 453 | 454 | [Serializable] 455 | public class OptionException : Exception { 456 | private string option; 457 | 458 | public OptionException () 459 | { 460 | } 461 | 462 | public OptionException (string message, string optionName) 463 | : base (message) 464 | { 465 | this.option = optionName; 466 | } 467 | 468 | public OptionException (string message, string optionName, Exception innerException) 469 | : base (message, innerException) 470 | { 471 | this.option = optionName; 472 | } 473 | 474 | protected OptionException (SerializationInfo info, StreamingContext context) 475 | : base (info, context) 476 | { 477 | this.option = info.GetString ("OptionName"); 478 | } 479 | 480 | public string OptionName { 481 | get {return this.option;} 482 | } 483 | 484 | [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] 485 | public override void GetObjectData (SerializationInfo info, StreamingContext context) 486 | { 487 | base.GetObjectData (info, context); 488 | info.AddValue ("OptionName", option); 489 | } 490 | } 491 | 492 | public delegate void OptionAction (TKey key, TValue value); 493 | 494 | public class OptionSet : KeyedCollection 495 | { 496 | public OptionSet () 497 | : this (delegate (string f) {return f;}) 498 | { 499 | } 500 | 501 | public OptionSet (Converter localizer) 502 | { 503 | this.localizer = localizer; 504 | } 505 | 506 | Converter localizer; 507 | 508 | public Converter MessageLocalizer { 509 | get {return localizer;} 510 | } 511 | 512 | protected override string GetKeyForItem (Option item) 513 | { 514 | if (item == null) 515 | throw new ArgumentNullException ("option"); 516 | if (item.Names != null && item.Names.Length > 0) 517 | return item.Names [0]; 518 | // This should never happen, as it's invalid for Option to be 519 | // constructed w/o any names. 520 | throw new InvalidOperationException ("Option has no names!"); 521 | } 522 | 523 | [Obsolete ("Use KeyedCollection.this[string]")] 524 | protected Option GetOptionForName (string option) 525 | { 526 | if (option == null) 527 | throw new ArgumentNullException ("option"); 528 | try { 529 | return base [option]; 530 | } 531 | catch (KeyNotFoundException) { 532 | return null; 533 | } 534 | } 535 | 536 | protected override void InsertItem (int index, Option item) 537 | { 538 | base.InsertItem (index, item); 539 | AddImpl (item); 540 | } 541 | 542 | protected override void RemoveItem (int index) 543 | { 544 | base.RemoveItem (index); 545 | Option p = Items [index]; 546 | // KeyedCollection.RemoveItem() handles the 0th item 547 | for (int i = 1; i < p.Names.Length; ++i) { 548 | Dictionary.Remove (p.Names [i]); 549 | } 550 | } 551 | 552 | protected override void SetItem (int index, Option item) 553 | { 554 | base.SetItem (index, item); 555 | RemoveItem (index); 556 | AddImpl (item); 557 | } 558 | 559 | private void AddImpl (Option option) 560 | { 561 | if (option == null) 562 | throw new ArgumentNullException ("option"); 563 | List added = new List (option.Names.Length); 564 | try { 565 | // KeyedCollection.InsertItem/SetItem handle the 0th name. 566 | for (int i = 1; i < option.Names.Length; ++i) { 567 | Dictionary.Add (option.Names [i], option); 568 | added.Add (option.Names [i]); 569 | } 570 | } 571 | catch (Exception) { 572 | foreach (string name in added) 573 | Dictionary.Remove (name); 574 | throw; 575 | } 576 | } 577 | 578 | public new OptionSet Add (Option option) 579 | { 580 | base.Add (option); 581 | return this; 582 | } 583 | 584 | sealed class ActionOption : Option { 585 | Action action; 586 | 587 | public ActionOption (string prototype, string description, int count, Action action) 588 | : base (prototype, description, count) 589 | { 590 | if (action == null) 591 | throw new ArgumentNullException ("action"); 592 | this.action = action; 593 | } 594 | 595 | protected override void OnParseComplete (OptionContext c) 596 | { 597 | action (c.OptionValues); 598 | } 599 | } 600 | 601 | public OptionSet Add (string prototype, Action action) 602 | { 603 | return Add (prototype, null, action); 604 | } 605 | 606 | public OptionSet Add (string prototype, string description, Action action) 607 | { 608 | if (action == null) 609 | throw new ArgumentNullException ("action"); 610 | Option p = new ActionOption (prototype, description, 1, 611 | delegate (OptionValueCollection v) { action (v [0]); }); 612 | base.Add (p); 613 | return this; 614 | } 615 | 616 | public OptionSet Add (string prototype, OptionAction action) 617 | { 618 | return Add (prototype, null, action); 619 | } 620 | 621 | public OptionSet Add (string prototype, string description, OptionAction action) 622 | { 623 | if (action == null) 624 | throw new ArgumentNullException ("action"); 625 | Option p = new ActionOption (prototype, description, 2, 626 | delegate (OptionValueCollection v) {action (v [0], v [1]);}); 627 | base.Add (p); 628 | return this; 629 | } 630 | 631 | sealed class ActionOption : Option { 632 | Action action; 633 | 634 | public ActionOption (string prototype, string description, Action action) 635 | : base (prototype, description, 1) 636 | { 637 | if (action == null) 638 | throw new ArgumentNullException ("action"); 639 | this.action = action; 640 | } 641 | 642 | protected override void OnParseComplete (OptionContext c) 643 | { 644 | action (Parse (c.OptionValues [0], c)); 645 | } 646 | } 647 | 648 | sealed class ActionOption : Option { 649 | OptionAction action; 650 | 651 | public ActionOption (string prototype, string description, OptionAction action) 652 | : base (prototype, description, 2) 653 | { 654 | if (action == null) 655 | throw new ArgumentNullException ("action"); 656 | this.action = action; 657 | } 658 | 659 | protected override void OnParseComplete (OptionContext c) 660 | { 661 | action ( 662 | Parse (c.OptionValues [0], c), 663 | Parse (c.OptionValues [1], c)); 664 | } 665 | } 666 | 667 | public OptionSet Add (string prototype, Action action) 668 | { 669 | return Add (prototype, null, action); 670 | } 671 | 672 | public OptionSet Add (string prototype, string description, Action action) 673 | { 674 | return Add (new ActionOption (prototype, description, action)); 675 | } 676 | 677 | public OptionSet Add (string prototype, OptionAction action) 678 | { 679 | return Add (prototype, null, action); 680 | } 681 | 682 | public OptionSet Add (string prototype, string description, OptionAction action) 683 | { 684 | return Add (new ActionOption (prototype, description, action)); 685 | } 686 | 687 | protected virtual OptionContext CreateOptionContext () 688 | { 689 | return new OptionContext (this); 690 | } 691 | 692 | #if LINQ 693 | public List Parse (IEnumerable arguments) 694 | { 695 | bool process = true; 696 | OptionContext c = CreateOptionContext (); 697 | c.OptionIndex = -1; 698 | var def = GetOptionForName ("<>"); 699 | var unprocessed = 700 | from argument in arguments 701 | where ++c.OptionIndex >= 0 && (process || def != null) 702 | ? process 703 | ? argument == "--" 704 | ? (process = false) 705 | : !Parse (argument, c) 706 | ? def != null 707 | ? Unprocessed (null, def, c, argument) 708 | : true 709 | : false 710 | : def != null 711 | ? Unprocessed (null, def, c, argument) 712 | : true 713 | : true 714 | select argument; 715 | List r = unprocessed.ToList (); 716 | if (c.Option != null) 717 | c.Option.Invoke (c); 718 | return r; 719 | } 720 | #else 721 | public List Parse (IEnumerable arguments) 722 | { 723 | OptionContext c = CreateOptionContext (); 724 | c.OptionIndex = -1; 725 | bool process = true; 726 | List unprocessed = new List (); 727 | Option def = Contains ("<>") ? this ["<>"] : null; 728 | foreach (string argument in arguments) { 729 | ++c.OptionIndex; 730 | if (argument == "--") { 731 | process = false; 732 | continue; 733 | } 734 | if (!process) { 735 | Unprocessed (unprocessed, def, c, argument); 736 | continue; 737 | } 738 | if (!Parse (argument, c)) 739 | Unprocessed (unprocessed, def, c, argument); 740 | } 741 | if (c.Option != null) 742 | c.Option.Invoke (c); 743 | return unprocessed; 744 | } 745 | #endif 746 | 747 | private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) 748 | { 749 | if (def == null) { 750 | extra.Add (argument); 751 | return false; 752 | } 753 | c.OptionValues.Add (argument); 754 | c.Option = def; 755 | c.Option.Invoke (c); 756 | return false; 757 | } 758 | 759 | private readonly Regex ValueOption = new Regex ( 760 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); 761 | 762 | protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) 763 | { 764 | if (argument == null) 765 | throw new ArgumentNullException ("argument"); 766 | 767 | flag = name = sep = value = null; 768 | Match m = ValueOption.Match (argument); 769 | if (!m.Success) { 770 | return false; 771 | } 772 | flag = m.Groups ["flag"].Value; 773 | name = m.Groups ["name"].Value; 774 | if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { 775 | sep = m.Groups ["sep"].Value; 776 | value = m.Groups ["value"].Value; 777 | } 778 | return true; 779 | } 780 | 781 | protected virtual bool Parse (string argument, OptionContext c) 782 | { 783 | if (c.Option != null) { 784 | ParseValue (argument, c); 785 | return true; 786 | } 787 | 788 | string f, n, s, v; 789 | if (!GetOptionParts (argument, out f, out n, out s, out v)) 790 | return false; 791 | 792 | Option p; 793 | if (Contains (n)) { 794 | p = this [n]; 795 | c.OptionName = f + n; 796 | c.Option = p; 797 | switch (p.OptionValueType) { 798 | case OptionValueType.None: 799 | c.OptionValues.Add (n); 800 | c.Option.Invoke (c); 801 | break; 802 | case OptionValueType.Optional: 803 | case OptionValueType.Required: 804 | ParseValue (v, c); 805 | break; 806 | } 807 | return true; 808 | } 809 | // no match; is it a bool option? 810 | if (ParseBool (argument, n, c)) 811 | return true; 812 | // is it a bundled option? 813 | if (ParseBundledValue (f, string.Concat (n + s + v), c)) 814 | return true; 815 | 816 | return false; 817 | } 818 | 819 | private void ParseValue (string option, OptionContext c) 820 | { 821 | if (option != null) 822 | foreach (string o in c.Option.ValueSeparators != null 823 | ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None) 824 | : new string[]{option}) { 825 | c.OptionValues.Add (o); 826 | } 827 | if (c.OptionValues.Count == c.Option.MaxValueCount || 828 | c.Option.OptionValueType == OptionValueType.Optional) 829 | c.Option.Invoke (c); 830 | else if (c.OptionValues.Count > c.Option.MaxValueCount) { 831 | throw new OptionException (localizer (string.Format ( 832 | "Error: Found {0} option values when expecting {1}.", 833 | c.OptionValues.Count, c.Option.MaxValueCount)), 834 | c.OptionName); 835 | } 836 | } 837 | 838 | private bool ParseBool (string option, string n, OptionContext c) 839 | { 840 | Option p; 841 | string rn; 842 | if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && 843 | Contains ((rn = n.Substring (0, n.Length-1)))) { 844 | p = this [rn]; 845 | string v = n [n.Length-1] == '+' ? option : null; 846 | c.OptionName = option; 847 | c.Option = p; 848 | c.OptionValues.Add (v); 849 | p.Invoke (c); 850 | return true; 851 | } 852 | return false; 853 | } 854 | 855 | private bool ParseBundledValue (string f, string n, OptionContext c) 856 | { 857 | if (f != "-") 858 | return false; 859 | for (int i = 0; i < n.Length; ++i) { 860 | Option p; 861 | string opt = f + n [i].ToString (); 862 | string rn = n [i].ToString (); 863 | if (!Contains (rn)) { 864 | if (i == 0) 865 | return false; 866 | throw new OptionException (string.Format (localizer ( 867 | "Cannot bundle unregistered option '{0}'."), opt), opt); 868 | } 869 | p = this [rn]; 870 | switch (p.OptionValueType) { 871 | case OptionValueType.None: 872 | Invoke (c, opt, n, p); 873 | break; 874 | case OptionValueType.Optional: 875 | case OptionValueType.Required: { 876 | string v = n.Substring (i+1); 877 | c.Option = p; 878 | c.OptionName = opt; 879 | ParseValue (v.Length != 0 ? v : null, c); 880 | return true; 881 | } 882 | default: 883 | throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); 884 | } 885 | } 886 | return true; 887 | } 888 | 889 | private static void Invoke (OptionContext c, string name, string value, Option option) 890 | { 891 | c.OptionName = name; 892 | c.Option = option; 893 | c.OptionValues.Add (value); 894 | option.Invoke (c); 895 | } 896 | 897 | private const int OptionWidth = 29; 898 | 899 | public void WriteOptionDescriptions (TextWriter o) 900 | { 901 | foreach (Option p in this) { 902 | int written = 0; 903 | if (!WriteOptionPrototype (o, p, ref written)) 904 | continue; 905 | 906 | if (written < OptionWidth) 907 | o.Write (new string (' ', OptionWidth - written)); 908 | else { 909 | o.WriteLine (); 910 | o.Write (new string (' ', OptionWidth)); 911 | } 912 | 913 | List lines = GetLines (localizer (GetDescription (p.Description))); 914 | o.WriteLine (lines [0]); 915 | string prefix = new string (' ', OptionWidth+2); 916 | for (int i = 1; i < lines.Count; ++i) { 917 | o.Write (prefix); 918 | o.WriteLine (lines [i]); 919 | } 920 | } 921 | } 922 | 923 | bool WriteOptionPrototype (TextWriter o, Option p, ref int written) 924 | { 925 | string[] names = p.Names; 926 | 927 | int i = GetNextOptionIndex (names, 0); 928 | if (i == names.Length) 929 | return false; 930 | 931 | if (names [i].Length == 1) { 932 | Write (o, ref written, " -"); 933 | Write (o, ref written, names [0]); 934 | } 935 | else { 936 | Write (o, ref written, " --"); 937 | Write (o, ref written, names [0]); 938 | } 939 | 940 | for ( i = GetNextOptionIndex (names, i+1); 941 | i < names.Length; i = GetNextOptionIndex (names, i+1)) { 942 | Write (o, ref written, ", "); 943 | Write (o, ref written, names [i].Length == 1 ? "-" : "--"); 944 | Write (o, ref written, names [i]); 945 | } 946 | 947 | if (p.OptionValueType == OptionValueType.Optional || 948 | p.OptionValueType == OptionValueType.Required) { 949 | if (p.OptionValueType == OptionValueType.Optional) { 950 | Write (o, ref written, localizer ("[")); 951 | } 952 | Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); 953 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 954 | ? p.ValueSeparators [0] 955 | : " "; 956 | for (int c = 1; c < p.MaxValueCount; ++c) { 957 | Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); 958 | } 959 | if (p.OptionValueType == OptionValueType.Optional) { 960 | Write (o, ref written, localizer ("]")); 961 | } 962 | } 963 | return true; 964 | } 965 | 966 | static int GetNextOptionIndex (string[] names, int i) 967 | { 968 | while (i < names.Length && names [i] == "<>") { 969 | ++i; 970 | } 971 | return i; 972 | } 973 | 974 | static void Write (TextWriter o, ref int n, string s) 975 | { 976 | n += s.Length; 977 | o.Write (s); 978 | } 979 | 980 | private static string GetArgumentName (int index, int maxIndex, string description) 981 | { 982 | if (description == null) 983 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 984 | string[] nameStart; 985 | if (maxIndex == 1) 986 | nameStart = new string[]{"{0:", "{"}; 987 | else 988 | nameStart = new string[]{"{" + index + ":"}; 989 | for (int i = 0; i < nameStart.Length; ++i) { 990 | int start, j = 0; 991 | do { 992 | start = description.IndexOf (nameStart [i], j); 993 | } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); 994 | if (start == -1) 995 | continue; 996 | int end = description.IndexOf ("}", start); 997 | if (end == -1) 998 | continue; 999 | return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); 1000 | } 1001 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1002 | } 1003 | 1004 | private static string GetDescription (string description) 1005 | { 1006 | if (description == null) 1007 | return string.Empty; 1008 | StringBuilder sb = new StringBuilder (description.Length); 1009 | int start = -1; 1010 | for (int i = 0; i < description.Length; ++i) { 1011 | switch (description [i]) { 1012 | case '{': 1013 | if (i == start) { 1014 | sb.Append ('{'); 1015 | start = -1; 1016 | } 1017 | else if (start < 0) 1018 | start = i + 1; 1019 | break; 1020 | case '}': 1021 | if (start < 0) { 1022 | if ((i+1) == description.Length || description [i+1] != '}') 1023 | throw new InvalidOperationException ("Invalid option description: " + description); 1024 | ++i; 1025 | sb.Append ("}"); 1026 | } 1027 | else { 1028 | sb.Append (description.Substring (start, i - start)); 1029 | start = -1; 1030 | } 1031 | break; 1032 | case ':': 1033 | if (start < 0) 1034 | goto default; 1035 | start = i + 1; 1036 | break; 1037 | default: 1038 | if (start < 0) 1039 | sb.Append (description [i]); 1040 | break; 1041 | } 1042 | } 1043 | return sb.ToString (); 1044 | } 1045 | 1046 | private static List GetLines (string description) 1047 | { 1048 | List lines = new List (); 1049 | if (string.IsNullOrEmpty (description)) { 1050 | lines.Add (string.Empty); 1051 | return lines; 1052 | } 1053 | int length = 80 - OptionWidth - 2; 1054 | int start = 0, end; 1055 | do { 1056 | end = GetLineEnd (start, length, description); 1057 | bool cont = false; 1058 | if (end < description.Length) { 1059 | char c = description [end]; 1060 | if (c == '-' || (char.IsWhiteSpace (c) && c != '\n')) 1061 | ++end; 1062 | else if (c != '\n') { 1063 | cont = true; 1064 | --end; 1065 | } 1066 | } 1067 | lines.Add (description.Substring (start, end - start)); 1068 | if (cont) { 1069 | lines [lines.Count-1] += "-"; 1070 | } 1071 | start = end; 1072 | if (start < description.Length && description [start] == '\n') 1073 | ++start; 1074 | } while (end < description.Length); 1075 | return lines; 1076 | } 1077 | 1078 | private static int GetLineEnd (int start, int length, string description) 1079 | { 1080 | int end = Math.Min (start + length, description.Length); 1081 | int sep = -1; 1082 | for (int i = start; i < end; ++i) { 1083 | switch (description [i]) { 1084 | case ' ': 1085 | case '\t': 1086 | case '\v': 1087 | case '-': 1088 | case ',': 1089 | case '.': 1090 | case ';': 1091 | sep = i; 1092 | break; 1093 | case '\n': 1094 | return i; 1095 | } 1096 | } 1097 | if (sep == -1 || end == description.Length) 1098 | return end; 1099 | return sep; 1100 | } 1101 | } 1102 | } 1103 | 1104 | --------------------------------------------------------------------------------