Merge 'remotes/trunk'
[0ad.git] / source / lib / secure_crt.cpp
blob6349c69953eba96593939537d36e5f1b397ccf84
1 /* Copyright (c) 2010 Wildfire Games
3 * Permission is hereby granted, free of charge, to any person obtaining
4 * a copy of this software and associated documentation files (the
5 * "Software"), to deal in the Software without restriction, including
6 * without limitation the rights to use, copy, modify, merge, publish,
7 * distribute, sublicense, and/or sell copies of the Software, and to
8 * permit persons to whom the Software is furnished to do so, subject to
9 * the following conditions:
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 * partial implementation of VC8's secure CRT functions
27 #include "precompiled.h"
29 #include <stdio.h>
30 #include <errno.h>
31 #include <stdarg.h>
33 #include "lib/secure_crt.h"
35 #if OS_ANDROID
36 # include <boost/algorithm/string/replace.hpp>
37 #endif
39 // we were included from wsecure_crt.cpp; skip all stuff that
40 // must only be done once.
41 #ifndef WSECURE_CRT
42 static const StatusDefinition secureCrtStatusDefinitions[] = {
43 { ERR::STRING_NOT_TERMINATED, L"Invalid string (no 0 terminator found in buffer)" }
45 STATUS_ADD_DEFINITIONS(secureCrtStatusDefinitions);
46 #endif
49 // written against http://std.dkuug.dk/jtc1/sc22/wg14/www/docs/n1031.pdf .
50 // optimized for size - e.g. strcpy calls strncpy with n = SIZE_MAX.
52 // since char and wide versions of these functions are basically the same,
53 // this source file implements generic versions and bridges the differences
54 // with these macros. wsecure_crt.cpp #defines WSECURE_CRT and
55 // includes this file.
57 // Note: These defines are all #undef:ed at the end of the file - remember to
58 // add a corresponding #undef when adding a #define.
59 #ifdef WSECURE_CRT
60 # define tchar wchar_t
61 # define tstring std::wstring
62 # define T(string_literal) L ## string_literal
63 # define tnlen wcsnlen
64 # define tncpy_s wcsncpy_s
65 # define tcpy_s wcscpy_s
66 # define tncat_s wcsncat_s
67 # define tcat_s wcscat_s
68 # define tcmp wcscmp
69 # define tcpy wcscpy
70 # define tvsnprintf vswprintf // used by implementation
71 # define tvsprintf_s vswprintf_s
72 # define tsprintf_s swprintf_s
73 #else
74 # define tchar char
75 # define tstring std::string
76 # define T(string_literal) string_literal
77 # define tnlen strnlen
78 # define tncpy_s strncpy_s
79 # define tcpy_s strcpy_s
80 # define tncat_s strncat_s
81 # define tcat_s strcat_s
82 # define tcmp strcmp
83 # define tcpy strcpy
84 # define tvsnprintf vsnprintf // used by implementation
85 # define tvsprintf_s vsprintf_s
86 # define tsprintf_s sprintf_s
87 #endif // #ifdef WSECURE_CRT
90 // return <retval> and raise an assertion if <condition> doesn't hold.
91 // usable as a statement.
92 #define ENFORCE(condition, err_to_warn, retval) STMT(\
93 if(!(condition)) \
94 { \
95 DEBUG_WARN_ERR(err_to_warn); \
96 return retval; \
97 } \
100 // raise a debug warning if <len> is the size of a pointer.
101 // catches bugs such as: tchar* s = ..; tcpy_s(s, sizeof(s), T(".."));
102 // if warnings get annoying, replace with debug_printf. usable as a statement.
104 // currently disabled due to high risk of false positives.
105 #define WARN_IF_PTR_LEN(len)\
107 ENSURE(len != sizeof(char*));
111 // skip our implementation if already available, but not the
112 // self-test and the t* defines (needed for test).
113 #if EMULATE_SECURE_CRT
115 #if !OS_UNIX || OS_MACOSX || OS_OPENBSD
116 // return length [in characters] of a string, not including the trailing
117 // null character. to protect against access violations, only the
118 // first <max_len> characters are examined; if the null character is
119 // not encountered by then, <max_len> is returned.
120 size_t tnlen(const tchar* str, size_t max_len)
122 // note: we can't bail - what would the return value be?
123 ENSURE(str != 0);
125 WARN_IF_PTR_LEN(max_len);
127 size_t len;
128 for(len = 0; len < max_len; len++)
129 if(*str++ == '\0')
130 break;
132 return len;
134 #endif // !OS_UNIX
136 #if OS_ANDROID
137 static tstring androidFormat(const tchar *fmt)
139 // FIXME handle %%hs, %%ls, etc
140 tstring ret(fmt);
141 boost::algorithm::replace_all(ret, T("%ls"), T("%S"));
142 boost::algorithm::replace_all(ret, T("%hs"), T("%s"));
143 return ret;
145 #endif
147 // copy at most <max_src_chars> (not including trailing null) from
148 // <src> into <dst>, which must not overlap.
149 // if thereby <max_dst_chars> (including null) would be exceeded,
150 // <dst> is set to the empty string and ERANGE returned; otherwise,
151 // 0 is returned to indicate success and that <dst> is null-terminated.
153 // note: padding with zeroes is not called for by N1031.
154 int tncpy_s(tchar* dst, size_t max_dst_chars, const tchar* src, size_t max_src_chars)
156 // the MS implementation returns EINVAL and allows dst = 0 if
157 // max_dst_chars = max_src_chars = 0. no mention of this in
158 // 3.6.2.1.1, so don't emulate that behavior.
159 ENFORCE(dst != 0, ERR::INVALID_POINTER, EINVAL);
160 ENFORCE(max_dst_chars != 0, ERR::INVALID_SIZE, EINVAL); // N1031 says ERANGE, MSDN/MSVC says EINVAL
161 *dst = '\0'; // in case src ENFORCE is triggered
162 ENFORCE(src != 0, ERR::INVALID_POINTER, EINVAL);
164 WARN_IF_PTR_LEN(max_dst_chars);
165 WARN_IF_PTR_LEN(max_src_chars);
167 // copy string until null character encountered or limit reached.
168 // optimized for size (less comparisons than MS impl) and
169 // speed (due to well-predicted jumps; we don't bother unrolling).
170 tchar* p = dst;
171 size_t chars_left = std::min(max_dst_chars, max_src_chars);
172 while(chars_left != 0)
174 // success: reached end of string normally.
175 if((*p++ = *src++) == '\0')
176 return 0;
177 chars_left--;
180 // which limit did we hit?
181 // .. dst, and last character wasn't null: overflow.
182 if(max_dst_chars <= max_src_chars)
184 *dst = '\0';
185 ENFORCE(0, ERR::INVALID_SIZE, ERANGE);
187 // .. source: success, but still need to null-terminate the destination.
188 *p = '\0';
189 return 0;
193 // copy <src> (including trailing null) into <dst>, which must not overlap.
194 // if thereby <max_dst_chars> (including null) would be exceeded,
195 // <dst> is set to the empty string and ERANGE returned; otherwise,
196 // 0 is returned to indicate success and that <dst> is null-terminated.
197 int tcpy_s(tchar* dst, size_t max_dst_chars, const tchar* src)
199 return tncpy_s(dst, max_dst_chars, src, SIZE_MAX);
203 // append <src> to <dst>, which must not overlap.
204 // if thereby <max_dst_chars> (including null) would be exceeded,
205 // <dst> is set to the empty string and ERANGE returned; otherwise,
206 // 0 is returned to indicate success and that <dst> is null-terminated.
207 int tncat_s(tchar* dst, size_t max_dst_chars, const tchar* src, size_t max_src_chars)
209 ENFORCE(dst != 0, ERR::INVALID_POINTER, EINVAL);
210 ENFORCE(max_dst_chars != 0, ERR::INVALID_SIZE, EINVAL); // N1031 says ERANGE, MSDN/MSVC says EINVAL
211 // src is checked in tncpy_s
213 // WARN_IF_PTR_LEN not necessary: both max_dst_chars and max_src_chars
214 // are checked by tnlen / tncpy_s (respectively).
216 const size_t dst_len = tnlen(dst, max_dst_chars);
217 if(dst_len == max_dst_chars)
219 *dst = '\0';
220 ENFORCE(0, ERR::STRING_NOT_TERMINATED, EINVAL); // N1031/MSDN says ERANGE, MSVC says EINVAL
223 tchar* const end = dst+dst_len;
224 const size_t chars_left = max_dst_chars-dst_len;
225 int ret = tncpy_s(end, chars_left, src, max_src_chars);
226 // if tncpy_s overflowed, we need to clear the start of our string
227 // (not just the appended part). can't do that by default, because
228 // the beginning of dst is not changed in normal operation.
229 if(ret != 0)
230 *dst = '\0';
231 return ret;
235 // append <src> to <dst>, which must not overlap.
236 // if thereby <max_dst_chars> (including null) would be exceeded,
237 // <dst> is set to the empty string and ERANGE returned; otherwise,
238 // 0 is returned to indicate success and that <dst> is null-terminated.
240 // note: implemented as tncat_s(dst, max_dst_chars, src, SIZE_MAX)
241 int tcat_s(tchar* dst, size_t max_dst_chars, const tchar* src)
243 return tncat_s(dst, max_dst_chars, src, SIZE_MAX);
247 int tvsprintf_s(tchar* dst, size_t max_dst_chars, const tchar* fmt, va_list ap)
249 if(!dst || !fmt || max_dst_chars == 0)
251 errno = EINVAL;
252 return -1;
255 #if OS_ANDROID
256 // Workaround for https://code.google.com/p/android/issues/detail?id=109074
257 // (vswprintf doesn't null-terminate strings)
258 memset(dst, 0, max_dst_chars * sizeof(tchar));
260 const int ret = tvsnprintf(dst, max_dst_chars, androidFormat(fmt).c_str(), ap);
261 #else
262 const int ret = tvsnprintf(dst, max_dst_chars, fmt, ap);
263 #endif
264 if(ret < 0 || ret >= int(max_dst_chars)) // not enough space
266 dst[0] = '\0';
267 return -1;
269 return ret; // negative if error, else # chars written (excluding '\0')
273 int tsprintf_s(tchar* buf, size_t max_chars, const tchar* fmt, ...)
275 va_list ap;
276 va_start(ap, fmt);
277 int len = tvsprintf_s(buf, max_chars, fmt, ap);
278 va_end(ap);
279 return len;
282 #endif // #if EMULATE_SECURE_CRT
284 #undef tchar
285 #undef T
286 #undef tnlen
287 #undef tncpy_s
288 #undef tcpy_s
289 #undef tncat_s
290 #undef tcat_s
291 #undef tcmp
292 #undef tcpy
293 #undef tvsnprintf
294 #undef tvsprintf_s
295 #undef tsprintf_s