Line | Count | Source (jump to first uncovered line) |
1 | | #include "rar.hpp" |
2 | | #include "log.cpp" |
3 | | |
4 | | static MESSAGE_TYPE MsgStream=MSG_STDOUT; |
5 | | static RAR_CHARSET RedirectCharset=RCH_DEFAULT; |
6 | | static bool ProhibitInput=false; |
7 | | |
8 | | const int MaxMsgSize=2*NM+2048; |
9 | | |
10 | | static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false; |
11 | | |
12 | | #ifdef _WIN_ALL |
13 | | static bool IsRedirected(DWORD nStdHandle) |
14 | | { |
15 | | HANDLE hStd=GetStdHandle(nStdHandle); |
16 | | DWORD Mode; |
17 | | return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0; |
18 | | } |
19 | | #endif |
20 | | |
21 | | |
22 | | void InitConsole() |
23 | 0 | { |
24 | | #ifdef _WIN_ALL |
25 | | // We want messages like file names or progress percent to be printed |
26 | | // immediately. Use only in Windows, in Unix they can cause wprintf %ls |
27 | | // to fail with non-English strings. |
28 | | setbuf(stdout,NULL); |
29 | | setbuf(stderr,NULL); |
30 | | |
31 | | // Detect if output is redirected and set output mode properly. |
32 | | // We do not want to send Unicode output to files and especially to pipes |
33 | | // like '|more', which cannot handle them correctly in Windows. |
34 | | // In Unix console output is UTF-8 and it is handled correctly |
35 | | // when redirecting, so no need to perform any adjustments. |
36 | | StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE); |
37 | | StderrRedirected=IsRedirected(STD_ERROR_HANDLE); |
38 | | StdinRedirected=IsRedirected(STD_INPUT_HANDLE); |
39 | | #ifdef _MSC_VER |
40 | | if (!StdoutRedirected) |
41 | | _setmode(_fileno(stdout), _O_U16TEXT); |
42 | | if (!StderrRedirected) |
43 | | _setmode(_fileno(stderr), _O_U16TEXT); |
44 | | #endif |
45 | | #elif defined(_UNIX) |
46 | 0 | StdoutRedirected=!isatty(fileno(stdout)); |
47 | 0 | StderrRedirected=!isatty(fileno(stderr)); |
48 | 0 | StdinRedirected=!isatty(fileno(stdin)); |
49 | 0 | #endif |
50 | 0 | } |
51 | | |
52 | | |
53 | | void SetConsoleMsgStream(MESSAGE_TYPE MsgStream) |
54 | 0 | { |
55 | 0 | ::MsgStream=MsgStream; |
56 | 0 | } |
57 | | |
58 | | |
59 | | void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset) |
60 | 0 | { |
61 | 0 | ::RedirectCharset=RedirectCharset; |
62 | 0 | } |
63 | | |
64 | | |
65 | | void ProhibitConsoleInput() |
66 | 0 | { |
67 | 0 | ProhibitInput=true; |
68 | 0 | } |
69 | | |
70 | | |
71 | | #ifndef SILENT |
72 | | static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist) |
73 | | { |
74 | | // This buffer is for format string only, not for entire output, |
75 | | // so it can be short enough. |
76 | | wchar fmtw[1024]; |
77 | | PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw)); |
78 | | #ifdef _WIN_ALL |
79 | | safebuf wchar Msg[MaxMsgSize]; |
80 | | if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected) |
81 | | { |
82 | | HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE); |
83 | | vswprintf(Msg,ASIZE(Msg),fmtw,arglist); |
84 | | DWORD Written; |
85 | | if (RedirectCharset==RCH_UNICODE) |
86 | | WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL); |
87 | | else |
88 | | { |
89 | | // Avoid Unicode for redirect in Windows, it does not work with pipes. |
90 | | safebuf char MsgA[MaxMsgSize]; |
91 | | if (RedirectCharset==RCH_UTF8) |
92 | | WideToUtf(Msg,MsgA,ASIZE(MsgA)); |
93 | | else |
94 | | WideToChar(Msg,MsgA,ASIZE(MsgA)); |
95 | | if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM) |
96 | | CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding. |
97 | | |
98 | | // We already converted \n to \r\n above, so we use WriteFile instead |
99 | | // of C library to avoid unnecessary additional conversion. |
100 | | WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL); |
101 | | } |
102 | | return; |
103 | | } |
104 | | // MSVC2008 vfwprintf writes every character to console separately |
105 | | // and it is too slow. We use direct WriteConsole call instead. |
106 | | vswprintf(Msg,ASIZE(Msg),fmtw,arglist); |
107 | | HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE); |
108 | | DWORD Written; |
109 | | WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL); |
110 | | #else |
111 | | vfwprintf(dest,fmtw,arglist); |
112 | | // We do not use setbuf(NULL) in Unix (see comments in InitConsole). |
113 | | fflush(dest); |
114 | | #endif |
115 | | } |
116 | | |
117 | | |
118 | | void mprintf(const wchar *fmt,...) |
119 | | { |
120 | | if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY) |
121 | | return; |
122 | | |
123 | | fflush(stderr); // Ensure proper message order. |
124 | | |
125 | | va_list arglist; |
126 | | va_start(arglist,fmt); |
127 | | FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout; |
128 | | cvt_wprintf(dest,fmt,arglist); |
129 | | va_end(arglist); |
130 | | } |
131 | | #endif |
132 | | |
133 | | |
134 | | #ifndef SILENT |
135 | | void eprintf(const wchar *fmt,...) |
136 | | { |
137 | | if (MsgStream==MSG_NULL) |
138 | | return; |
139 | | |
140 | | fflush(stdout); // Ensure proper message order. |
141 | | |
142 | | va_list arglist; |
143 | | va_start(arglist,fmt); |
144 | | cvt_wprintf(stderr,fmt,arglist); |
145 | | va_end(arglist); |
146 | | } |
147 | | #endif |
148 | | |
149 | | |
150 | | #ifndef SILENT |
151 | | static void QuitIfInputProhibited() |
152 | | { |
153 | | // We cannot handle user prompts if -si is used to read file or archive data |
154 | | // from stdin. |
155 | | if (ProhibitInput) |
156 | | { |
157 | | mprintf(St(MStdinNoInput)); |
158 | | ErrHandler.Exit(RARX_FATAL); |
159 | | } |
160 | | } |
161 | | |
162 | | |
163 | | static void GetPasswordText(wchar *Str,uint MaxLength) |
164 | | { |
165 | | if (MaxLength==0) |
166 | | return; |
167 | | QuitIfInputProhibited(); |
168 | | if (StdinRedirected) |
169 | | getwstr(Str,MaxLength); // Read from pipe or redirected file. |
170 | | else |
171 | | { |
172 | | #ifdef _WIN_ALL |
173 | | HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE); |
174 | | DWORD ConInMode; |
175 | | GetConsoleMode(hConIn,&ConInMode); |
176 | | SetConsoleMode(hConIn,ENABLE_LINE_INPUT); // Remove ENABLE_ECHO_INPUT. |
177 | | |
178 | | // We prefer ReadConsole to ReadFile, so we can read Unicode input. |
179 | | DWORD Read=0; |
180 | | ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL); |
181 | | Str[Read]=0; |
182 | | SetConsoleMode(hConIn,ConInMode); |
183 | | |
184 | | // If entered password is longer than MAXPASSWORD and truncated, |
185 | | // read its unread part anyway, so it isn't read later as the second |
186 | | // password for -p switch. Low level FlushConsoleInputBuffer doesn't help |
187 | | // for high level ReadConsole, which in line input mode seems to store |
188 | | // the rest of string in its own internal buffer. |
189 | | if (wcschr(Str,'\r')==NULL) // If '\r' is missing, the password was truncated. |
190 | | while (true) |
191 | | { |
192 | | wchar Trail[64]; |
193 | | DWORD TrailRead=0; |
194 | | // Use ASIZE(Trail)-1 to reserve the space for trailing 0. |
195 | | ReadConsole(hConIn,Trail,ASIZE(Trail)-1,&TrailRead,NULL); |
196 | | Trail[TrailRead]=0; |
197 | | if (TrailRead==0 || wcschr(Trail,'\r')!=NULL) |
198 | | break; |
199 | | } |
200 | | |
201 | | #else |
202 | | char StrA[MAXPASSWORD*4]; // "*4" for multibyte UTF-8 characters. |
203 | | #if defined(_EMX) || defined (__VMS) |
204 | | fgets(StrA,ASIZE(StrA)-1,stdin); |
205 | | #elif defined(__sun) |
206 | | strncpyz(StrA,getpassphrase(""),ASIZE(StrA)); |
207 | | #else |
208 | | strncpyz(StrA,getpass(""),ASIZE(StrA)); |
209 | | #endif |
210 | | CharToWide(StrA,Str,MaxLength); |
211 | | cleandata(StrA,sizeof(StrA)); |
212 | | #endif |
213 | | } |
214 | | Str[MaxLength-1]=0; |
215 | | RemoveLF(Str); |
216 | | } |
217 | | #endif |
218 | | |
219 | | |
220 | | #ifndef SILENT |
221 | | bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password) |
222 | | { |
223 | | if (!StdinRedirected) |
224 | | uiAlarm(UIALARM_QUESTION); |
225 | | |
226 | | while (true) |
227 | | { |
228 | | // if (!StdinRedirected) |
229 | | if (Type==UIPASSWORD_GLOBAL) |
230 | | eprintf(L"\n%s: ",St(MAskPsw)); |
231 | | else |
232 | | eprintf(St(MAskPswFor),FileName); |
233 | | |
234 | | wchar PlainPsw[MAXPASSWORD+1]; |
235 | | GetPasswordText(PlainPsw,ASIZE(PlainPsw)); |
236 | | if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL) |
237 | | return false; |
238 | | if (wcslen(PlainPsw)>=MAXPASSWORD) |
239 | | { |
240 | | PlainPsw[MAXPASSWORD-1]=0; |
241 | | uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1); |
242 | | } |
243 | | if (!StdinRedirected && Type==UIPASSWORD_GLOBAL) |
244 | | { |
245 | | eprintf(St(MReAskPsw)); |
246 | | wchar CmpStr[MAXPASSWORD]; |
247 | | GetPasswordText(CmpStr,ASIZE(CmpStr)); |
248 | | if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0) |
249 | | { |
250 | | eprintf(St(MNotMatchPsw)); |
251 | | cleandata(PlainPsw,sizeof(PlainPsw)); |
252 | | cleandata(CmpStr,sizeof(CmpStr)); |
253 | | continue; |
254 | | } |
255 | | cleandata(CmpStr,sizeof(CmpStr)); |
256 | | } |
257 | | Password->Set(PlainPsw); |
258 | | cleandata(PlainPsw,sizeof(PlainPsw)); |
259 | | break; |
260 | | } |
261 | | return true; |
262 | | } |
263 | | #endif |
264 | | |
265 | | |
266 | | #ifndef SILENT |
267 | | bool getwstr(wchar *str,size_t n) |
268 | | { |
269 | | // Print buffered prompt title function before waiting for input. |
270 | | fflush(stderr); |
271 | | |
272 | | QuitIfInputProhibited(); |
273 | | |
274 | | *str=0; |
275 | | #if defined(_WIN_ALL) |
276 | | // fgetws does not work well with non-English text in Windows, |
277 | | // so we do not use it. |
278 | | if (StdinRedirected) // ReadConsole does not work if redirected. |
279 | | { |
280 | | // fgets does not work well with pipes in Windows in our test. |
281 | | // Let's use files. |
282 | | Array<char> StrA(n*4); // Up to 4 UTF-8 characters per wchar_t. |
283 | | File SrcFile; |
284 | | SrcFile.SetHandleType(FILE_HANDLESTD); |
285 | | SrcFile.SetLineInputMode(true); |
286 | | int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1); |
287 | | if (ReadSize<=0) |
288 | | { |
289 | | // Looks like stdin is a null device. We can enter to infinite loop |
290 | | // calling Ask(), so let's better exit. |
291 | | ErrHandler.Exit(RARX_USERBREAK); |
292 | | } |
293 | | StrA[ReadSize]=0; |
294 | | |
295 | | // We expect ANSI encoding here, but "echo text|rar ..." to pipe to RAR, |
296 | | // such as send passwords, we get OEM encoding by default, unless we |
297 | | // use "chcp" in console. But we avoid OEM to ANSI conversion, |
298 | | // because we also want to handle ANSI files redirection correctly, |
299 | | // like "rar ... < ansifile.txt". |
300 | | CharToWide(&StrA[0],str,n); |
301 | | cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords. |
302 | | } |
303 | | else |
304 | | { |
305 | | DWORD ReadSize=0; |
306 | | if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0) |
307 | | return false; |
308 | | str[ReadSize]=0; |
309 | | } |
310 | | #else |
311 | | if (fgetws(str,n,stdin)==NULL) |
312 | | ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop. |
313 | | #endif |
314 | | RemoveLF(str); |
315 | | return true; |
316 | | } |
317 | | #endif |
318 | | |
319 | | |
320 | | #ifndef SILENT |
321 | | // We allow this function to return 0 in case of invalid input, |
322 | | // because it might be convenient to press Enter to some not dangerous |
323 | | // prompts like "insert disk with next volume". We should call this function |
324 | | // again in case of 0 in dangerous prompt such as overwriting file. |
325 | | int Ask(const wchar *AskStr) |
326 | | { |
327 | | uiAlarm(UIALARM_QUESTION); |
328 | | |
329 | | const int MaxItems=10; |
330 | | wchar Item[MaxItems][40]; |
331 | | int ItemKeyPos[MaxItems],NumItems=0; |
332 | | |
333 | | for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_')) |
334 | | { |
335 | | wchar *CurItem=Item[NumItems]; |
336 | | wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0])); |
337 | | wchar *EndItem=wcschr(CurItem,'_'); |
338 | | if (EndItem!=NULL) |
339 | | *EndItem=0; |
340 | | int KeyPos=0,CurKey; |
341 | | while ((CurKey=CurItem[KeyPos])!=0) |
342 | | { |
343 | | bool Found=false; |
344 | | for (int I=0;I<NumItems && !Found;I++) |
345 | | if (toupperw(Item[I][ItemKeyPos[I]])==toupperw(CurKey)) |
346 | | Found=true; |
347 | | if (!Found && CurKey!=' ') |
348 | | break; |
349 | | KeyPos++; |
350 | | } |
351 | | ItemKeyPos[NumItems]=KeyPos; |
352 | | NumItems++; |
353 | | } |
354 | | |
355 | | for (int I=0;I<NumItems;I++) |
356 | | { |
357 | | eprintf(I==0 ? (NumItems>3 ? L"\n":L" "):L", "); |
358 | | int KeyPos=ItemKeyPos[I]; |
359 | | for (int J=0;J<KeyPos;J++) |
360 | | eprintf(L"%c",Item[I][J]); |
361 | | eprintf(L"[%c]%ls",Item[I][KeyPos],&Item[I][KeyPos+1]); |
362 | | } |
363 | | eprintf(L" "); |
364 | | wchar Str[50]; |
365 | | getwstr(Str,ASIZE(Str)); |
366 | | wchar Ch=toupperw(Str[0]); |
367 | | for (int I=0;I<NumItems;I++) |
368 | | if (Ch==Item[I][ItemKeyPos[I]]) |
369 | | return I+1; |
370 | | return 0; |
371 | | } |
372 | | #endif |
373 | | |
374 | | |
375 | | static bool IsCommentUnsafe(const wchar *Data,size_t Size) |
376 | 230 | { |
377 | 263k | for (size_t I=0;I<Size;I++) |
378 | 263k | if (Data[I]==27 && Data[I+1]=='[') |
379 | 13.2k | for (size_t J=I+2;J<Size;J++) |
380 | 13.2k | { |
381 | | // Return true for <ESC>[{key};"{string}"p used to redefine |
382 | | // a keyboard key on some terminals. |
383 | 13.2k | if (Data[J]=='\"') |
384 | 0 | return true; |
385 | 13.2k | if (!IsDigit(Data[J]) && Data[J]!=';') |
386 | 2.88k | break; |
387 | 13.2k | } |
388 | 230 | return false; |
389 | 230 | } |
390 | | |
391 | | |
392 | | void OutComment(const wchar *Comment,size_t Size) |
393 | 230 | { |
394 | 230 | if (IsCommentUnsafe(Comment,Size)) |
395 | 0 | return; |
396 | 230 | const size_t MaxOutSize=0x400; |
397 | 699 | for (size_t I=0;I<Size;I+=MaxOutSize) |
398 | 469 | { |
399 | 469 | wchar Msg[MaxOutSize+1]; |
400 | 469 | size_t CopySize=Min(MaxOutSize,Size-I); |
401 | 469 | wcsncpy(Msg,Comment+I,CopySize); |
402 | 469 | Msg[CopySize]=0; |
403 | 469 | mprintf(L"%s",Msg); |
404 | 469 | } |
405 | 230 | mprintf(L"\n"); |
406 | 230 | } |