Coverage Report

Created: 2025-04-11 06:56

/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
static bool ConsoleOutputPresent=false;
8
9
static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false;
10
11
#ifdef _WIN_ALL
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
21
void InitConsole()
22
0
{
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
0
  StderrRedirected=!isatty(fileno(stderr));
47
0
  StdinRedirected=!isatty(fileno(stdin));
48
0
#endif
49
0
}
50
51
52
void SetConsoleMsgStream(MESSAGE_TYPE MsgStream)
53
0
{
54
0
  ::MsgStream=MsgStream;
55
0
}
56
57
58
void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset)
59
0
{
60
0
  ::RedirectCharset=RedirectCharset;
61
0
}
62
63
64
void ProhibitConsoleInput()
65
0
{
66
0
  ProhibitInput=true;
67
0
}
68
69
70
#ifndef SILENT
71
static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist)
72
{
73
  ConsoleOutputPresent=true;
74
  
75
  // No need for PrintfPrepareFmt here, vwstrprintf calls it.
76
  std::wstring s=vwstrprintf(fmt,arglist);
77
78
  ReplaceEsc(s);
79
80
#ifdef _WIN_ALL
81
  if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected)
82
  {
83
    HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE);
84
    DWORD Written;
85
    if (RedirectCharset==RCH_UNICODE)
86
      WriteFile(hOut,s.data(),(DWORD)s.size()*sizeof(s[0]),&Written,NULL);
87
    else
88
    {
89
      // Avoid Unicode for redirect in Windows, it does not work with pipes.
90
      std::string MsgA;
91
      if (RedirectCharset==RCH_UTF8)
92
        WideToUtf(s,MsgA);
93
      else
94
        WideToChar(s,MsgA);
95
      if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM)
96
        CharToOemA(&MsgA[0],&MsgA[0]); // 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.data(),(DWORD)MsgA.size(),&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
  HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE);
107
  DWORD Written;
108
  WriteConsole(hOut,s.data(),(DWORD)s.size(),&Written,NULL);
109
#else
110
  fputws(s.c_str(),dest);
111
  // We do not use setbuf(NULL) in Unix (see comments in InitConsole).
112
  fflush(dest);
113
#endif
114
}
115
116
117
void mprintf(const wchar *fmt,...)
118
{
119
  if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY)
120
    return;
121
122
  fflush(stderr); // Ensure proper message order.
123
124
  va_list arglist;
125
  va_start(arglist,fmt);
126
  FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout;
127
  cvt_wprintf(dest,fmt,arglist);
128
  va_end(arglist);
129
}
130
#endif
131
132
133
#ifndef SILENT
134
void eprintf(const wchar *fmt,...)
135
{
136
  if (MsgStream==MSG_NULL)
137
    return;
138
139
  fflush(stdout); // Ensure proper message order.
140
141
  va_list arglist;
142
  va_start(arglist,fmt);
143
  cvt_wprintf(stderr,fmt,arglist);
144
  va_end(arglist);
145
}
146
#endif
147
148
149
#ifndef SILENT
150
static void QuitIfInputProhibited()
151
{
152
  // We cannot handle user prompts if -si is used to read file or archive data
153
  // from stdin.
154
  if (ProhibitInput)
155
  {
156
    mprintf(St(MStdinNoInput));
157
    ErrHandler.Exit(RARX_FATAL);
158
  }
159
}
160
161
162
static void GetPasswordText(std::wstring &Str)
163
{
164
  QuitIfInputProhibited();
165
  if (StdinRedirected)
166
    getwstr(Str); // Read from pipe or redirected file.
167
  else
168
  {
169
#ifdef _WIN_ALL
170
    HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE);
171
    DWORD ConInMode;
172
    GetConsoleMode(hConIn,&ConInMode);
173
    SetConsoleMode(hConIn,ENABLE_LINE_INPUT); // Remove ENABLE_ECHO_INPUT.
174
175
    std::vector<wchar> Buf(MAXPASSWORD);
176
    
177
    // We prefer ReadConsole to ReadFile, so we can read Unicode input.
178
    DWORD Read=0;
179
    ReadConsole(hConIn,Buf.data(),(DWORD)Buf.size()-1,&Read,NULL);
180
    Buf[Read]=0;
181
    Str=Buf.data();
182
    cleandata(Buf.data(),Buf.size()*sizeof(Buf[0]));
183
184
    SetConsoleMode(hConIn,ConInMode);
185
186
    // 2023.03.12: Previously we checked for presence of "\n" in entered
187
    // passwords, supposing that truncated strings do not include it.
188
    // We did it to read the rest of excessively long string, so it is not
189
    // read later as the second password for -p switch. But this "\n" check
190
    // doesn't seem to work in Windows 10 anymore and "\r" is present even
191
    // in truncated strings. Also we increased MAXPASSWORD, so it is larger
192
    // than MAXPASSWORD_RAR. Thus we removed this check as not working
193
    // and not that necessary. Low level FlushConsoleInputBuffer doesn't help
194
    // for high level ReadConsole, which in line input mode seems to store
195
    // the rest of string in its own internal buffer.
196
#else
197
    std::vector<char> StrA(MAXPASSWORD*4); // "*4" for multibyte UTF-8 characters.
198
#ifdef __VMS
199
    fgets(StrA.data(),StrA.size()-1,stdin);
200
#elif defined(__sun)
201
    strncpyz(StrA.data(),getpassphrase(""),StrA.size());
202
#else
203
    strncpyz(StrA.data(),getpass(""),StrA.size());
204
#endif
205
    CharToWide(StrA.data(),Str);
206
    cleandata(StrA.data(),StrA.size()*sizeof(StrA[0]));
207
#endif
208
  }
209
  RemoveLF(Str);
210
}
211
#endif
212
213
214
#ifndef SILENT
215
bool GetConsolePassword(UIPASSWORD_TYPE Type,const std::wstring &FileName,SecPassword *Password)
216
{
217
  if (!StdinRedirected)
218
    uiAlarm(UIALARM_QUESTION);
219
  
220
  while (true)
221
  {
222
//    if (!StdinRedirected)
223
      if (Type==UIPASSWORD_GLOBAL)
224
        eprintf(L"\n%s: ",St(MAskPsw));
225
      else
226
        eprintf(St(MAskPswFor),FileName.c_str());
227
228
    std::wstring PlainPsw;
229
    GetPasswordText(PlainPsw);
230
    if (PlainPsw.empty() && Type==UIPASSWORD_GLOBAL)
231
      return false;
232
    if (PlainPsw.size()>=MAXPASSWORD)
233
    {
234
      PlainPsw.erase(MAXPASSWORD-1);
235
      uiMsg(UIERROR_TRUNCPSW,MAXPASSWORD-1);
236
    }
237
    if (!StdinRedirected && Type==UIPASSWORD_GLOBAL)
238
    {
239
      eprintf(St(MReAskPsw));
240
      std::wstring CmpStr;
241
      GetPasswordText(CmpStr);
242
      if (CmpStr.empty() || PlainPsw!=CmpStr)
243
      {
244
        eprintf(St(MNotMatchPsw));
245
        cleandata(&PlainPsw[0],PlainPsw.size()*sizeof(PlainPsw[0]));
246
        cleandata(&CmpStr[0],CmpStr.size()*sizeof(CmpStr[0]));
247
        continue;
248
      }
249
      cleandata(&CmpStr[0],CmpStr.size()*sizeof(CmpStr[0]));
250
    }
251
    Password->Set(PlainPsw.c_str());
252
    cleandata(&PlainPsw[0],PlainPsw.size()*sizeof(PlainPsw[0]));
253
    break;
254
  }
255
  return true;
256
}
257
#endif
258
259
260
#ifndef SILENT
261
void getwstr(std::wstring &str)
262
{
263
  // Print buffered prompt title function before waiting for input.
264
  fflush(stderr);
265
266
  QuitIfInputProhibited();
267
268
  str.clear();
269
270
  const size_t MaxRead=MAXPATHSIZE; // Large enough to read a file name.
271
272
#if defined(_WIN_ALL)
273
  // fgetws does not work well with non-English text in Windows,
274
  // so we do not use it.
275
  if (StdinRedirected) // ReadConsole does not work if redirected.
276
  {
277
    // fgets does not work well with pipes in Windows in our test.
278
    // Let's use files.
279
    std::vector<char> StrA(MaxRead*4);  // Up to 4 UTF-8 characters per wchar_t.
280
    File SrcFile;
281
    SrcFile.SetHandleType(FILE_HANDLESTD);
282
    SrcFile.SetLineInputMode(true);
283
    int ReadSize=SrcFile.Read(&StrA[0],StrA.size()-1);
284
    if (ReadSize<=0)
285
    {
286
      // Looks like stdin is a null device. We can enter to infinite loop
287
      // calling Ask() or set an empty password, so let's better exit.
288
     ErrHandler.ReadError(L"stdin");
289
    }
290
    StrA[ReadSize]=0;
291
292
    // We expect ANSI encoding here, but "echo text|rar ..." to pipe to RAR,
293
    // such as send passwords, we get OEM encoding by default, unless we
294
    // use "chcp" in console. But we avoid OEM to ANSI conversion,
295
    // because we also want to handle ANSI files redirection correctly,
296
    // like "rar ... < ansifile.txt".
297
    CharToWide(&StrA[0],str);
298
    cleandata(&StrA[0],StrA.size()); // We can use this function to enter passwords.
299
  }
300
  else
301
  {
302
    std::vector<wchar> Buf(MaxRead);  // Up to 4 UTF-8 characters per wchar_t.
303
    DWORD SizeToRead=(DWORD)Buf.size()-1;
304
305
    // ReadConsole fails in Windows 7 for requested input exceeding 30 KB.
306
    // Not certain about Windows 8, so check for Windows 10 here.
307
    if (WinNT()<=WNT_W10)
308
      SizeToRead=Min(SizeToRead,0x4000);
309
310
    DWORD ReadSize=0;
311
    if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),&Buf[0],SizeToRead,&ReadSize,NULL)==0)
312
      ErrHandler.ReadError(L"stdin"); // Unknown user input, safer to abort.
313
    Buf[ReadSize]=0;
314
    str=Buf.data();
315
  }
316
#else
317
  std::vector<wchar> Buf(MaxRead);  // Up to 4 UTF-8 characters per wchar_t.
318
  if (fgetws(&Buf[0],Buf.size(),stdin)==NULL)
319
    ErrHandler.ReadError(L"stdin"); // Avoid infinite Ask() loop.
320
  str=Buf.data();
321
#endif
322
  RemoveLF(str);
323
}
324
#endif
325
326
327
#ifndef SILENT
328
// We allow this function to return 0 in case of invalid input,
329
// because it might be convenient to press Enter to some not dangerous
330
// prompts like "insert disk with next volume". We should call this function
331
// again in case of 0 in dangerous prompt such as overwriting file.
332
int Ask(const wchar *AskStr)
333
{
334
  uiAlarm(UIALARM_QUESTION);
335
336
  const uint MaxItems=10;
337
  wchar Item[MaxItems][40];
338
  uint ItemKeyPos[MaxItems],NumItems=0;
339
340
  for (const wchar *NextItem=AskStr;NextItem!=nullptr;NextItem=wcschr(NextItem+1,'_'))
341
  {
342
    wchar *CurItem=Item[NumItems];
343
    wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0]));
344
    wchar *EndItem=wcschr(CurItem,'_');
345
    if (EndItem!=nullptr)
346
      *EndItem=0;
347
    uint KeyPos=0,CurKey;
348
    while ((CurKey=CurItem[KeyPos])!=0)
349
    {
350
      bool Found=false;
351
      for (uint I=0;I<NumItems && !Found;I++)
352
        if (toupperw(Item[I][ItemKeyPos[I]])==toupperw(CurKey))
353
          Found=true;
354
      if (!Found && CurKey!=' ')
355
        break;
356
      KeyPos++;
357
    }
358
    ItemKeyPos[NumItems]=KeyPos;
359
    NumItems++;
360
  }
361
362
  for (uint I=0;I<NumItems;I++)
363
  {
364
    eprintf(I==0 ? (NumItems>3 ? L"\n":L" "):L", ");
365
    uint KeyPos=ItemKeyPos[I];
366
    for (uint J=0;J<KeyPos;J++)
367
      eprintf(L"%c",Item[I][J]);
368
    eprintf(L"[%c]%ls",Item[I][KeyPos],&Item[I][KeyPos+1]);
369
  }
370
  eprintf(L" ");
371
  std::wstring Str;
372
  getwstr(Str);
373
  wchar Ch=toupperw(Str[0]);
374
  for (uint I=0;I<NumItems;I++)
375
    if (Ch==Item[I][ItemKeyPos[I]])
376
      return I+1;
377
  return 0;
378
}
379
#endif
380
381
382
static bool IsCommentUnsafe(const std::wstring &Data)
383
563
{
384
421k
  for (size_t I=0;I<Data.size();I++)
385
420k
    if (Data[I]==27 && Data[I+1]=='[')
386
4.23k
      for (size_t J=I+2;J<Data.size();J++)
387
4.22k
      {
388
        // Return true for <ESC>[{key};"{string}"p used to redefine
389
        // a keyboard key on some terminals.
390
4.22k
        if (Data[J]=='\"')
391
2
          return true;
392
4.22k
        if (!IsDigit(Data[J]) && Data[J]!=';')
393
2.56k
          break;
394
4.22k
      }
395
561
  return false;
396
563
}
397
398
399
void OutComment(const std::wstring &Comment)
400
563
{
401
563
  if (IsCommentUnsafe(Comment))
402
2
    return;
403
561
  const size_t MaxOutSize=0x400;
404
1.49k
  for (size_t I=0;I<Comment.size();I+=MaxOutSize)
405
937
  {
406
937
    size_t CopySize=Min(MaxOutSize,Comment.size()-I);
407
937
    mprintf(L"%s",Comment.substr(I,CopySize).c_str());
408
937
  }
409
561
  mprintf(L"\n");
410
561
}
411
412
413
bool IsConsoleOutputPresent()
414
0
{
415
0
  return ConsoleOutputPresent;
416
0
}