Coverage Report

Created: 2024-04-23 06:19

/src/unrar/consio.cpp
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
}