├── .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 |
--------------------------------------------------------------------------------