1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 /// @file pw_string/util.h
16 ///
17 /// @brief The `pw::string::*` functions provide safer alternatives to
18 /// C++ standard library string functions.
19
20 #include <cctype>
21 #include <cstddef>
22 #include <string_view>
23
24 #include "pw_assert/assert.h"
25 #include "pw_polyfill/language_feature_macros.h"
26 #include "pw_result/result.h"
27 #include "pw_span/span.h"
28 #include "pw_status/status.h"
29 #include "pw_status/status_with_size.h"
30 #include "pw_string/internal/length.h"
31 #include "pw_string/string.h"
32
33 namespace pw {
34 namespace string {
35 namespace internal {
36
CopyToSpan(std::string_view source,span<char> dest)37 PW_CONSTEXPR_CPP20 inline StatusWithSize CopyToSpan(std::string_view source,
38 span<char> dest) {
39 if (dest.empty()) {
40 return StatusWithSize::ResourceExhausted();
41 }
42
43 const size_t copied = source.copy(dest.data(), dest.size() - 1);
44 dest[copied] = '\0';
45
46 return StatusWithSize(
47 copied == source.size() ? OkStatus() : Status::ResourceExhausted(),
48 copied);
49 }
50
51 } // namespace internal
52
53 /// @brief Safe alternative to the `string_view` constructor that avoids the
54 /// risk of an unbounded implicit or explicit use of `strlen`.
55 ///
56 /// This is strongly recommended over using something like C11's `strnlen_s` as
57 /// a `string_view` does not require null-termination.
ClampedCString(span<const char> str)58 constexpr std::string_view ClampedCString(span<const char> str) {
59 return std::string_view(str.data(),
60 internal::ClampedLength(str.data(), str.size()));
61 }
62
ClampedCString(const char * str,size_t max_len)63 constexpr std::string_view ClampedCString(const char* str, size_t max_len) {
64 return ClampedCString(span<const char>(str, max_len));
65 }
66
67 /// @brief `pw::string::NullTerminatedLength` is a safer alternative to
68 /// `strlen` for calculating the null-terminated length of the
69 /// string within the specified span, excluding the null terminator.
70 ///
71 /// Like `strnlen_s` in C11, the scan for the null-terminator is bounded.
72 ///
73 /// @pre The string shall be at a valid pointer.
74 ///
75 /// @returns @rst
76 ///
77 /// .. pw-status-codes::
78 ///
79 /// OK: Returns the null-terminated length of the string excluding the null
80 /// terminator.
81 ///
82 /// OUT_OF_RANGE: The string is not null-terminated.
83 ///
84 /// @endrst
NullTerminatedLength(span<const char> str)85 constexpr Result<size_t> NullTerminatedLength(span<const char> str) {
86 PW_DASSERT(str.data() != nullptr);
87
88 const size_t length = internal::ClampedLength(str.data(), str.size());
89 if (length == str.size()) {
90 return Status::OutOfRange();
91 }
92
93 return length;
94 }
95
NullTerminatedLength(const char * str,size_t max_len)96 constexpr Result<size_t> NullTerminatedLength(const char* str, size_t max_len) {
97 return NullTerminatedLength(span<const char>(str, max_len));
98 }
99
100 /// @brief `pw::string::Copy` is a safer alternative to `std::strncpy` as it
101 /// always null-terminates whenever the destination buffer has a non-zero size.
102 ///
103 /// Copies the `source` string to the `dest`, truncating if the full string does
104 /// not fit. Always null terminates if `dest.size()` or `num` is greater than 0.
105 ///
106 /// @pre The destination and source shall not overlap. The source
107 /// shall be a valid pointer.
108 ///
109 /// @returns @rst
110 ///
111 /// .. pw-status-codes::
112 ///
113 /// OK: Returns the number of characters written, excluding the null
114 /// terminator.
115 ///
116 /// RESOURCE_EXHAUSTED: The string is truncated.
117 ///
118 /// @endrst
119 template <typename Span>
Copy(std::string_view source,Span && dest)120 PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(std::string_view source,
121 Span&& dest) {
122 static_assert(
123 !std::is_base_of_v<InlineString<>, std::decay_t<Span>>,
124 "Do not use pw::string::Copy() with pw::InlineString<>. Instead, use "
125 "pw::InlineString<>'s assignment operator or assign() function, or "
126 "pw::string::Append().");
127 return internal::CopyToSpan(source, std::forward<Span>(dest));
128 }
129
130 template <typename Span>
Copy(const char * source,Span && dest)131 PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const char* source, Span&& dest) {
132 PW_DASSERT(source != nullptr);
133 return Copy(ClampedCString(source, std::size(dest)),
134 std::forward<Span>(dest));
135 }
136
Copy(const char * source,char * dest,size_t num)137 PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const char* source,
138 char* dest,
139 size_t num) {
140 return Copy(source, span<char>(dest, num));
141 }
142
143 /// Assigns a `std::string_view` to a `pw::InlineString`, truncating if it does
144 /// not fit. The `assign()` function of `pw::InlineString` asserts if the
145 /// string's requested size exceeds its capacity; `pw::string::Assign()`
146 /// returns a `Status` instead.
147 ///
148 /// @returns @rst
149 ///
150 /// .. pw-status-codes::
151 ///
152 /// OK: The entire ``std::string_view`` was copied to the end of the
153 /// ``pw::InlineString``.
154 ///
155 /// RESOURCE_EXHAUSTED: The ``std::string_view`` was truncated to fit.
156 ///
157 /// @endrst
Assign(InlineString<> & string,std::string_view view)158 inline Status Assign(InlineString<>& string, std::string_view view) {
159 const size_t chars_copied =
160 std::min(view.size(), static_cast<size_t>(string.capacity()));
161 string.assign(view, 0, static_cast<string_impl::size_type>(chars_copied));
162 return chars_copied == view.size() ? OkStatus() : Status::ResourceExhausted();
163 }
164
Assign(InlineString<> & string,const char * c_string)165 inline Status Assign(InlineString<>& string, const char* c_string) {
166 PW_DASSERT(c_string != nullptr);
167 // Clamp to capacity + 1 so strings larger than the capacity yield an error.
168 return Assign(string, ClampedCString(c_string, string.capacity() + 1));
169 }
170
171 /// Appends a `std::string_view` to a `pw::InlineString`, truncating if it
172 /// does not fit. The `append()` function of `pw::InlineString` asserts if the
173 /// string's requested size exceeds its capacity; `pw::string::Append()` returns
174 /// a `Status` instead.
175 ///
176 /// @returns @rst
177 ///
178 /// .. pw-status-codes::
179 ///
180 /// OK: The entire ``std::string_view`` was assigned.
181 ///
182 /// RESOURCE_EXHAUSTED: The ``std::string_view`` was truncated to fit.
183 ///
184 /// @endrst
Append(InlineString<> & string,std::string_view view)185 inline Status Append(InlineString<>& string, std::string_view view) {
186 const size_t chars_copied = std::min(
187 view.size(), static_cast<size_t>(string.capacity() - string.size()));
188 string.append(view, 0, static_cast<string_impl::size_type>(chars_copied));
189 return chars_copied == view.size() ? OkStatus() : Status::ResourceExhausted();
190 }
191
Append(InlineString<> & string,const char * c_string)192 inline Status Append(InlineString<>& string, const char* c_string) {
193 PW_DASSERT(c_string != nullptr);
194 // Clamp to capacity + 1 so strings larger than the capacity yield an error.
195 return Append(string, ClampedCString(c_string, string.capacity() + 1));
196 }
197
198 /// @brief Provides a safe, printable copy of a string.
199 ///
200 /// Copies the `source` string to the `dest` string with same behavior as
201 /// `pw::string::Copy`, with the difference that any non-printable characters
202 /// are changed to `.`.
PrintableCopy(std::string_view source,span<char> dest)203 PW_CONSTEXPR_CPP20 inline StatusWithSize PrintableCopy(std::string_view source,
204 span<char> dest) {
205 StatusWithSize copy_result = Copy(source, dest);
206 for (size_t i = 0; i < copy_result.size(); i++) {
207 dest[i] = std::isprint(dest[i]) ? dest[i] : '.';
208 }
209
210 return copy_result;
211 }
212
213 } // namespace string
214 } // namespace pw
215