xref: /reactos/sdk/lib/ucrt/stdio/gets.cpp (revision 53d808d2)
1 //
2 // gets.cpp
3 //
4 //      Copyright (c) Microsoft Corporation.  All rights reserved.
5 //
6 // Defines gets() and gets_s(), which read a line from stdin.
7 //
8 #include <corecrt_internal_stdio.h>
9 #include <corecrt_internal_securecrt.h>
10 
11 
12 
13 // Reads a line of text from stdin, storing it in the 'result' buffer.  This
14 // function supports two modes:
15 //
16 // [1] Insecure, which is used by the abominable gets() and _getws() functions.
17 //     In this mode, the buffer is not bounds-checked; it is just assumed that
18 //     the buffer is large enough.  Text is read until a newline is reached or
19 //     EOF is reached.  This mode is enabled by passing _CRT_UNBOUNDED_BUFFER_SIZE
20 //     as the buffer size.
21 //
22 // [2] Secure, which is used by the gets_s() and _getws_s() functions.  In this
23 //     mode, the buffer is bound-checked.  If there is insufficient space in the
24 //     buffer for the entire line of text, the line is read and discarded, the
25 //     buffer is zero'ed, and nullptr is returned.  If the buffer is larger than
26 //     is required, the remaining space of the buffer is zero-filled.
27 //
28 // On success, 'result' is returned.  On failure, nullptr is returned.  If the
29 // 'return_early_if_eof_is_first' flag is true and the first read encounters EOF,
30 // the buffer is left unchanged and nullptr is returned.
31 template <typename Character>
32 _Success_(return != nullptr)
33 static Character* __cdecl common_gets(
34     _Out_writes_z_(result_size_in_characters) Character* const result,
35     _In_                                      size_t     const result_size_in_characters,
36     _In_                                      bool       const return_early_if_eof_is_first
37     ) throw()
38 {
39     typedef __acrt_stdio_char_traits<Character> stdio_traits;
40 
41     _VALIDATE_RETURN(result != nullptr,             EINVAL, nullptr);
42     _VALIDATE_RETURN(result_size_in_characters > 0, EINVAL, nullptr);
43 
44     Character* return_value = result;
45 
46     _lock_file(stdin);
47     __try
48     {
49         if (!stdio_traits::validate_stream_is_ansi_if_required(stdin))
50         {
51             return_value = nullptr;
52             __leave;
53         }
54 
55         // Special case:  if the first character is EOF, treat it specially if
56         // we were asked to do so:
57         typename stdio_traits::int_type c = stdio_traits::getc_nolock(stdin);
58         if (c == stdio_traits::eof)
59         {
60             return_value = nullptr;
61             if (return_early_if_eof_is_first)
62                 __leave;
63         }
64 
65         // For the insecure version, we do no buffer size check and no debug fill:
66         if (result_size_in_characters == _CRT_UNBOUNDED_BUFFER_SIZE)
67         {
68 #pragma warning(push)
69 #pragma warning(disable:__WARNING_POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY) // 26015 - knowingly unsafe
70             Character* result_it = result;
71             while (c != '\n' && c != stdio_traits::eof)
72             {
73                 *result_it++ = static_cast<Character>(c);
74                 c = stdio_traits::getc_nolock(stdin);
75             }
76             *result_it = '\0';
77 #pragma warning(pop)
78         }
79         // For the secure version, we track the buffer size.  If we run out of
80         // buffer space, we still read in the rest of the current line until we
81         // reach '\n' or EOF, but we discard the characters and reset the buffer
82         // to be zero-filled.
83         else
84         {
85             size_t available = result_size_in_characters;
86 
87             Character* result_it = result;
88             while (c != '\n' && c != stdio_traits::eof)
89             {
90                 if (available > 0)
91                 {
92                     --available;
93                     *result_it++ = static_cast<Character>(c);
94                 }
95 
96                 c = stdio_traits::getc_nolock(stdin);
97             }
98 
99             // If we ran out of space, clear the buffer and return failure:
100             if (available == 0)
101             {
102                 _RESET_STRING(result, result_size_in_characters);
103                 _RETURN_BUFFER_TOO_SMALL_ERROR(result, result_size_in_characters, nullptr);
104             }
105 
106             *result_it = '\0';
107             _FILL_STRING(result, result_size_in_characters, result_size_in_characters - available + 1);
108         }
109     }
110     __finally
111     {
112         _unlock_file(stdin);
113     }
114     __endtry
115 
116     return return_value;
117 }
118 
119 
120 
121 // Reads a line of text from stdin and stores it in the result buffer.  If the
122 // line is longer than will fit in the buffer, the line is discarded, the buffer
123 // is reset, and nullptr is returned.
124 extern "C" char* __cdecl gets_s(char* const result, size_t const result_size_in_characters)
125 {
126     return common_gets(result, result_size_in_characters, false);
127 }
128 
129 extern "C" wchar_t* __cdecl _getws_s(wchar_t* const result, size_t const result_size_in_characters)
130 {
131     return common_gets(result, result_size_in_characters, false);
132 }
133 
134 
135 
136 // Reads a line of text from stdin and stores it in the result buffer.  This
137 // function assumes there is sufficient room in the buffer.  Do not use this
138 // function; use gets_s(), fgets(), _getws_s(), or fgetws() instead.  If EOF
139 // is encountered on the first read from the stream, the buffer is left
140 // unmodified and nullptr is returned.
141 extern "C" char* __cdecl gets(char* const result)
142 {
143     return common_gets(result, _CRT_UNBOUNDED_BUFFER_SIZE, true);
144 }
145 
146 extern "C" wchar_t* __cdecl _getws(wchar_t* const result)
147 {
148     return common_gets(result, _CRT_UNBOUNDED_BUFFER_SIZE, true);
149 }
150