1
- using System . Diagnostics ;
2
1
using System . Text ;
3
2
using D2L . Bmx . Okta . Models ;
4
3
@@ -20,22 +19,10 @@ internal class ConsolePrompter : IConsolePrompter {
20
19
private const char CTRL_C = '\u0003 ' ;
21
20
private const char CTRL_U = '\u0015 ' ;
22
21
private const char DEL = '\u007f ' ;
23
- private static readonly bool IS_WINDOWS = OperatingSystem . IsWindows ( ) ;
24
-
25
- // When taking user console input on Unix-y platforms, .NET copies the input data to stdout, leading to incorrect
26
- // `bmx print` output. See https://github.com/dotnet/runtime/issues/22314.
27
- // We read from stdin (fd = 0) directly on these platforms to bypass .NET's incorrect handling.
28
- private readonly TextReader _stdinReader =
29
- IS_WINDOWS
30
- ? Console . In
31
- : new StreamReader ( new FileStream (
32
- new Microsoft . Win32 . SafeHandles . SafeFileHandle ( 0 , ownsHandle : false ) ,
33
- FileAccess . Read
34
- ) ) ;
35
22
36
23
string IConsolePrompter . PromptOrg ( bool allowEmptyInput ) {
37
24
Console . Error . Write ( $ "{ ParameterDescriptions . Org } { ( allowEmptyInput ? " (optional): " : ": " ) } " ) ;
38
- string ? org = _stdinReader . ReadLine ( ) ;
25
+ string ? org = Console . ReadLine ( ) ;
39
26
if ( org is null || ( string . IsNullOrWhiteSpace ( org ) && ! allowEmptyInput ) ) {
40
27
throw new BmxException ( "Invalid org input" ) ;
41
28
}
@@ -45,7 +32,7 @@ string IConsolePrompter.PromptOrg( bool allowEmptyInput ) {
45
32
46
33
string IConsolePrompter . PromptProfile ( ) {
47
34
Console . Error . Write ( $ "{ ParameterDescriptions . Profile } : " ) ;
48
- string ? profile = _stdinReader . ReadLine ( ) ;
35
+ string ? profile = Console . ReadLine ( ) ;
49
36
if ( string . IsNullOrEmpty ( profile ) ) {
50
37
throw new BmxException ( "Invalid profile input" ) ;
51
38
}
@@ -55,7 +42,7 @@ string IConsolePrompter.PromptProfile() {
55
42
56
43
string IConsolePrompter . PromptUser ( bool allowEmptyInput ) {
57
44
Console . Error . Write ( $ "{ ParameterDescriptions . User } { ( allowEmptyInput ? " (optional): " : ": " ) } " ) ;
58
- string ? user = _stdinReader . ReadLine ( ) ;
45
+ string ? user = Console . ReadLine ( ) ;
59
46
if ( user is null || ( string . IsNullOrWhiteSpace ( user ) && ! allowEmptyInput ) ) {
60
47
throw new BmxException ( "Invalid user input" ) ;
61
48
}
@@ -69,7 +56,7 @@ string IConsolePrompter.PromptPassword() {
69
56
70
57
int ? IConsolePrompter . PromptDuration ( ) {
71
58
Console . Error . Write ( $ "{ ParameterDescriptions . Duration } (optional, default: 60): " ) ;
72
- string ? input = _stdinReader . ReadLine ( ) ;
59
+ string ? input = Console . ReadLine ( ) ;
73
60
if ( input is null || ! int . TryParse ( input , out int duration ) || duration <= 0 ) {
74
61
return null ;
75
62
}
@@ -91,7 +78,7 @@ string IConsolePrompter.PromptAccount( string[] accounts ) {
91
78
Console . Error . WriteLine ( $ "[{ i + 1 } ] { accounts [ i ] } " ) ;
92
79
}
93
80
Console . Error . Write ( "Select an account: " ) ;
94
- if ( ! int . TryParse ( _stdinReader . ReadLine ( ) , out int index ) || index > accounts . Length || index < 1 ) {
81
+ if ( ! int . TryParse ( Console . ReadLine ( ) , out int index ) || index > accounts . Length || index < 1 ) {
95
82
throw new BmxException ( "Invalid account selection" ) ;
96
83
}
97
84
@@ -113,7 +100,7 @@ string IConsolePrompter.PromptRole( string[] roles ) {
113
100
Console . Error . WriteLine ( $ "[{ i + 1 } ] { roles [ i ] } " ) ;
114
101
}
115
102
Console . Error . Write ( "Select a role: " ) ;
116
- if ( ! int . TryParse ( _stdinReader . ReadLine ( ) , out int index ) || index > roles . Length || index < 1 ) {
103
+ if ( ! int . TryParse ( Console . ReadLine ( ) , out int index ) || index > roles . Length || index < 1 ) {
117
104
throw new BmxException ( "Invalid role selection" ) ;
118
105
}
119
106
@@ -136,7 +123,7 @@ OktaMfaFactor IConsolePrompter.SelectMfa( OktaMfaFactor[] mfaOptions ) {
136
123
Console . Error . WriteLine ( $ "[{ i + 1 } ] { mfaOptions [ i ] . Provider } : { mfaOptions [ i ] . FactorName } " ) ;
137
124
}
138
125
Console . Error . Write ( "Select an available MFA option: " ) ;
139
- if ( ! int . TryParse ( _stdinReader . ReadLine ( ) , out int index ) || index > mfaOptions . Length || index < 1 ) {
126
+ if ( ! int . TryParse ( Console . ReadLine ( ) , out int index ) || index > mfaOptions . Length || index < 1 ) {
140
127
throw new BmxException ( "Invalid MFA selection" ) ;
141
128
}
142
129
return mfaOptions [ index - 1 ] ;
@@ -149,7 +136,7 @@ string IConsolePrompter.GetMfaResponse( string mfaInputPrompt, bool maskInput )
149
136
mfaInput = GetMaskedInput ( $ "{ mfaInputPrompt } : " ) ;
150
137
} else {
151
138
Console . Error . Write ( $ "{ mfaInputPrompt } : " ) ;
152
- mfaInput = _stdinReader . ReadLine ( ) ;
139
+ mfaInput = Console . ReadLine ( ) ;
153
140
}
154
141
155
142
if ( mfaInput is not null ) {
@@ -159,91 +146,53 @@ string IConsolePrompter.GetMfaResponse( string mfaInputPrompt, bool maskInput )
159
146
}
160
147
161
148
private string GetMaskedInput ( string prompt ) {
162
- Func < char > readKey ;
163
- if ( IS_WINDOWS ) {
164
- // On Windows , Console.ReadKey calls native console API, and will fail without a console attached
165
- if ( Console . IsInputRedirected ) {
166
- if ( Environment . GetEnvironmentVariable ( "TERM_PROGRAM" ) == "mintty" ) {
167
- Console . Error . WriteLine ( "\x1b [93m" + """
149
+ Console . Error . Write ( prompt ) ;
150
+
151
+ // If input is redirected , Console.ReadKey( intercept: true ) doesn't work, because it calls system console/terminal APIs.
152
+ if ( Console . IsInputRedirected ) {
153
+ if ( OperatingSystem . IsWindows ( ) && Environment . GetEnvironmentVariable ( "TERM_PROGRAM" ) == "mintty" ) {
154
+ Console . Error . WriteLine ( "\x1b [93m" + """
168
155
====== WARNING ======
169
156
Secret input won't be masked on screen!
170
157
This is because you are using mintty (possibly via Git Bash, Cygwin, MSYS2 etc.).
171
158
Consider switching to Windows Terminal for a better experience.
172
159
If you must use mintty, prefix your bmx command with 'winpty '.
173
160
=====================
174
161
""" + "\x1b [0m" ) ;
175
- }
176
- readKey = ( ) => ( char ) _stdinReader . Read ( ) ;
177
- } else {
178
- readKey = ( ) => Console . ReadKey ( intercept : true ) . KeyChar ;
179
162
}
180
- } else {
181
- readKey = ( ) => ( char ) _stdinReader . Read ( ) ;
163
+ return Console . ReadLine ( ) ?? string . Empty ;
182
164
}
183
165
184
- Console . Error . Write ( prompt ) ;
166
+ var passwordBuilder = new StringBuilder ( ) ;
167
+ while ( true ) {
168
+ char key = Console . ReadKey ( intercept : true ) . KeyChar ;
185
169
186
- string ? originalTerminalSettings = null ;
187
- try {
188
- if ( ! IS_WINDOWS ) {
189
- originalTerminalSettings = GetCurrentTerminalSettings ( ) ;
190
- EnableTerminalRawMode ( ) ;
170
+ if ( key == CTRL_C ) {
171
+ // Ctrl+C should terminate the program.
172
+ // Using an empty string as the exception message because this message is displayed to the user,
173
+ // but the user doesn't need to see anything when they themselves ended the program.
174
+ throw new BmxException ( string . Empty ) ;
191
175
}
192
-
193
- var passwordBuilder = new StringBuilder ( ) ;
194
- while ( true ) {
195
- char key = readKey ( ) ;
196
-
197
- if ( key == CTRL_C ) {
198
- // Ctrl+C should terminate the program.
199
- // Using an empty string as the exception message because this message is displayed to the user,
200
- // but the user doesn't need to see anything when they themselves ended the program.
201
- throw new BmxException ( string . Empty ) ;
202
- }
203
- if ( key == '\n ' || key == '\r ' ) {
204
- // when the terminal is in raw mode, writing \r is needed to start the new line properly
205
- Console . Error . Write ( "\r \n " ) ;
206
- return passwordBuilder . ToString ( ) ;
207
- }
208
-
209
- if ( key == CTRL_U ) {
210
- string moveLeftString = new ( '\b ' , passwordBuilder . Length ) ;
211
- string emptyString = new ( ' ' , passwordBuilder . Length ) ;
212
- Console . Error . Write ( moveLeftString + emptyString + moveLeftString ) ;
213
- passwordBuilder . Clear ( ) ;
214
- } else
215
- // The backspace key is received as the DEL character in raw mode
216
- if ( ( key == '\b ' || key == DEL ) && passwordBuilder . Length > 0 ) {
217
- Console . Error . Write ( "\b \b " ) ;
218
- passwordBuilder . Length -- ;
219
- } else if ( ! char . IsControl ( key ) ) {
220
- Console . Error . Write ( '*' ) ;
221
- passwordBuilder . Append ( key ) ;
222
- }
176
+ if ( key == '\n ' || key == '\r ' ) {
177
+ // when the terminal is in raw mode, writing \r is needed to start the new line properly
178
+ Console . Error . Write ( "\r \n " ) ;
179
+ return passwordBuilder . ToString ( ) ;
223
180
}
224
- } finally {
225
- if ( ! IS_WINDOWS && ! string . IsNullOrEmpty ( originalTerminalSettings ) ) {
226
- SetTerminalSettings ( originalTerminalSettings ) ;
181
+
182
+ if ( key == CTRL_U ) {
183
+ string moveLeftString = new ( '\b ' , passwordBuilder . Length ) ;
184
+ string emptyString = new ( ' ' , passwordBuilder . Length ) ;
185
+ Console . Error . Write ( moveLeftString + emptyString + moveLeftString ) ;
186
+ passwordBuilder . Clear ( ) ;
187
+ } else
188
+ // The backspace key is received as the DEL character in raw mode
189
+ if ( ( key == '\b ' || key == DEL ) && passwordBuilder . Length > 0 ) {
190
+ Console . Error . Write ( "\b \b " ) ;
191
+ passwordBuilder . Length -- ;
192
+ } else if ( ! char . IsControl ( key ) ) {
193
+ Console . Error . Write ( '*' ) ;
194
+ passwordBuilder . Append ( key ) ;
227
195
}
228
196
}
229
197
}
230
-
231
- private static string GetCurrentTerminalSettings ( ) {
232
- var startInfo = new ProcessStartInfo ( "stty" ) ;
233
- startInfo . ArgumentList . Add ( "-g" ) ;
234
- startInfo . RedirectStandardOutput = true ;
235
- using var p = Process . Start ( startInfo ) ?? throw new BmxException ( "Terminal error" ) ;
236
- p . WaitForExit ( ) ;
237
- return p . StandardOutput . ReadToEnd ( ) . Trim ( ) ;
238
- }
239
-
240
- private static void EnableTerminalRawMode ( ) {
241
- using var p = Process . Start ( "stty" , new [ ] { "raw" , "-echo" } ) ;
242
- p . WaitForExit ( ) ;
243
- }
244
-
245
- private static void SetTerminalSettings ( string settings ) {
246
- using var p = Process . Start ( "stty" , settings ) ;
247
- p . WaitForExit ( ) ;
248
- }
249
198
}
0 commit comments