• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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