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(const std::string_view & source,span<char> dest)37 PW_CONSTEXPR_CPP20 inline StatusWithSize CopyToSpan(
38 const std::string_view& source, 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 the null-terminated length of the string excluding the null
76 /// terminator or `OutOfRange` if the string is not null-terminated.
NullTerminatedLength(span<const char> str)77 constexpr Result<size_t> NullTerminatedLength(span<const char> str) {
78 PW_DASSERT(str.data() != nullptr);
79
80 const size_t length = internal::ClampedLength(str.data(), str.size());
81 if (length == str.size()) {
82 return Status::OutOfRange();
83 }
84
85 return length;
86 }
87
NullTerminatedLength(const char * str,size_t max_len)88 constexpr Result<size_t> NullTerminatedLength(const char* str, size_t max_len) {
89 return NullTerminatedLength(span<const char>(str, max_len));
90 }
91
92 /// @brief `pw::string::Copy` is a safer alternative to `std::strncpy` as it
93 /// always null-terminates whenever the destination buffer has a non-zero size.
94 ///
95 /// Copies the `source` string to the `dest`, truncating if the full string does
96 /// not fit. Always null terminates if `dest.size()` or `num` is greater than 0.
97 ///
98 /// @pre The destination and source shall not overlap. The source
99 /// shall be a valid pointer.
100 ///
101 /// @returns the number of characters written, excluding the null terminator. If
102 /// the string is truncated, the status is `RESOURCE_EXHAUSTED`.
103 template <typename Span>
Copy(const std::string_view & source,Span && dest)104 PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const std::string_view& source,
105 Span&& dest) {
106 static_assert(
107 !std::is_base_of_v<InlineString<>, std::decay_t<Span>>,
108 "Do not use pw::string::Copy() with pw::InlineString<>. Instead, use "
109 "pw::InlineString<>'s assignment operator or assign() function, or "
110 "pw::string::Append().");
111 return internal::CopyToSpan(source, std::forward<Span>(dest));
112 }
113
114 template <typename Span>
Copy(const char * source,Span && dest)115 PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const char* source, Span&& dest) {
116 PW_DASSERT(source != nullptr);
117 return Copy(ClampedCString(source, std::size(dest)),
118 std::forward<Span>(dest));
119 }
120
Copy(const char * source,char * dest,size_t num)121 PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const char* source,
122 char* dest,
123 size_t num) {
124 return Copy(source, span<char>(dest, num));
125 }
126
127 /// Assigns a `std::string_view` to a `pw::InlineString`, truncating if it does
128 /// not fit. The `assign()` function of `pw::InlineString` asserts if the
129 /// string's requested size exceeds its capacity; `pw::string::Assign()`
130 /// returns a `Status` instead.
131 ///
132 /// @return `OK` if the entire `std::string_view` was copied to the end of the
133 /// `pw::InlineString`. `RESOURCE_EXHAUSTED` if the `std::string_view` was
134 /// truncated to fit.
Assign(InlineString<> & string,const std::string_view & view)135 inline Status Assign(InlineString<>& string, const std::string_view& view) {
136 const size_t chars_copied =
137 std::min(view.size(), static_cast<size_t>(string.capacity()));
138 string.assign(view, 0, static_cast<string_impl::size_type>(chars_copied));
139 return chars_copied == view.size() ? OkStatus() : Status::ResourceExhausted();
140 }
141
Assign(InlineString<> & string,const char * c_string)142 inline Status Assign(InlineString<>& string, const char* c_string) {
143 PW_DASSERT(c_string != nullptr);
144 // Clamp to capacity + 1 so strings larger than the capacity yield an error.
145 return Assign(string, ClampedCString(c_string, string.capacity() + 1));
146 }
147
148 /// Appends a `std::string_view` to a `pw::InlineString`, truncating if it
149 /// does not fit. The `append()` function of `pw::InlineString` asserts if the
150 /// string's requested size exceeds its capacity; `pw::string::Append()` returns
151 /// a `Status` instead.
152 ///
153 /// @return `OK` if the entire `std::string_view` was assigned.
154 /// `RESOURCE_EXHAUSTED` if the `std::string_view` was truncated to fit.
Append(InlineString<> & string,const std::string_view & view)155 inline Status Append(InlineString<>& string, const std::string_view& view) {
156 const size_t chars_copied = std::min(
157 view.size(), static_cast<size_t>(string.capacity() - string.size()));
158 string.append(view, 0, static_cast<string_impl::size_type>(chars_copied));
159 return chars_copied == view.size() ? OkStatus() : Status::ResourceExhausted();
160 }
161
Append(InlineString<> & string,const char * c_string)162 inline Status Append(InlineString<>& string, const char* c_string) {
163 PW_DASSERT(c_string != nullptr);
164 // Clamp to capacity + 1 so strings larger than the capacity yield an error.
165 return Append(string, ClampedCString(c_string, string.capacity() + 1));
166 }
167
168 /// @brief Provides a safe, printable copy of a string.
169 ///
170 /// Copies the `source` string to the `dest` string with same behavior as
171 /// `pw::string::Copy`, with the difference that any non-printable characters
172 /// are changed to `.`.
PrintableCopy(const std::string_view & source,span<char> dest)173 PW_CONSTEXPR_CPP20 inline StatusWithSize PrintableCopy(
174 const std::string_view& source, span<char> dest) {
175 StatusWithSize copy_result = Copy(source, dest);
176 for (size_t i = 0; i < copy_result.size(); i++) {
177 dest[i] = std::isprint(dest[i]) ? dest[i] : '.';
178 }
179
180 return copy_result;
181 }
182
183 } // namespace string
184 } // namespace pw
185