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