Coverage Report

Created: 2026-03-25 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtorrent/src/string_util.cpp
Line
Count
Source
1
/*
2
3
Copyright (c) 2012, 2014-2020, Arvid Norberg
4
Copyright (c) 2016-2018, Alden Torres
5
Copyright (c) 2017, Andrei Kurushin
6
Copyright (c) 2017, Pavel Pimenov
7
All rights reserved.
8
9
Redistribution and use in source and binary forms, with or without
10
modification, are permitted provided that the following conditions
11
are met:
12
13
    * Redistributions of source code must retain the above copyright
14
      notice, this list of conditions and the following disclaimer.
15
    * Redistributions in binary form must reproduce the above copyright
16
      notice, this list of conditions and the following disclaimer in
17
      the documentation and/or other materials provided with the distribution.
18
    * Neither the name of the author nor the names of its
19
      contributors may be used to endorse or promote products derived
20
      from this software without specific prior written permission.
21
22
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
POSSIBILITY OF SUCH DAMAGE.
33
34
*/
35
36
#include "libtorrent/config.hpp"
37
#include "libtorrent/string_util.hpp"
38
#include "libtorrent/random.hpp"
39
#include "libtorrent/error_code.hpp"
40
#include "libtorrent/parse_url.hpp"
41
#include "libtorrent/address.hpp"
42
#include "libtorrent/assert.hpp"
43
44
#include <cstdlib> // for malloc
45
#include <cstring> // for strlen
46
#include <algorithm> // for search
47
48
namespace libtorrent {
49
50
  // We need well defined results that don't depend on locale
51
  std::array<char, 4 + std::numeric_limits<std::int64_t>::digits10>
52
    to_string(std::int64_t const n)
53
0
  {
54
0
    std::array<char, 4 + std::numeric_limits<std::int64_t>::digits10> ret;
55
0
    char *p = &ret.back();
56
0
    *p = '\0';
57
    // we want "un" to be the absolute value
58
    // since the absolute of INT64_MIN cannot be represented by a signed
59
    // int64, we calculate the abs in unsigned space
60
0
    std::uint64_t un = n < 0
61
0
      ? std::numeric_limits<std::uint64_t>::max() - std::uint64_t(n) + 1
62
0
      : std::uint64_t(n);
63
0
    do {
64
0
      *--p = '0' + un % 10;
65
0
      un /= 10;
66
0
    } while (un);
67
0
    if (n < 0) *--p = '-';
68
0
    std::memmove(ret.data(), p, std::size_t(&ret.back() - p + 1));
69
0
    return ret;
70
0
  }
71
72
  bool is_alpha(char c)
73
0
  {
74
0
    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
75
0
  }
76
77
  bool is_print(char c)
78
0
  {
79
0
    return c >= 32 && c < 127;
80
0
  }
81
82
  bool is_space(char c)
83
0
  {
84
0
    return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
85
0
  }
86
87
  char to_lower(char c)
88
0
  {
89
0
    return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c;
90
0
  }
91
92
  bool string_begins_no_case(char const* s1, char const* s2)
93
0
  {
94
0
    TORRENT_ASSERT(s1 != nullptr);
95
0
    TORRENT_ASSERT(s2 != nullptr);
96
97
0
    while (*s1 != 0)
98
0
    {
99
0
      if (to_lower(*s1) != to_lower(*s2)) return false;
100
0
      ++s1;
101
0
      ++s2;
102
0
    }
103
0
    return true;
104
0
  }
105
106
  bool string_equal_no_case(string_view s1, string_view s2)
107
0
  {
108
0
    if (s1.size() != s2.size()) return false;
109
0
    return std::equal(s1.begin(), s1.end(), s2.begin()
110
0
      , [] (char const c1, char const c2)
111
0
      { return to_lower(c1) == to_lower(c2); });
112
0
  }
113
114
  // generate a url-safe random string
115
  void url_random(span<char> dest)
116
0
  {
117
    // http-accepted characters:
118
    // excluding ', since some buggy trackers don't support that
119
0
    static char const printable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
120
0
      "abcdefghijklmnopqrstuvwxyz-_.!~*()";
121
122
    // the random number
123
0
    std::generate(dest.begin(), dest.end()
124
0
      , []{ return printable[random(sizeof(printable) - 2)]; });
125
0
  }
126
127
  bool string_ends_with(string_view s1, string_view s2)
128
0
  {
129
0
    return s1.size() >= s2.size() && std::equal(s2.rbegin(), s2.rend(), s1.rbegin());
130
0
  }
131
132
  int search(span<char const> src, span<char const> target)
133
0
  {
134
0
    TORRENT_ASSERT(!src.empty());
135
0
    TORRENT_ASSERT(!target.empty());
136
0
    TORRENT_ASSERT(target.size() >= src.size());
137
0
    TORRENT_ASSERT(target.size() < std::numeric_limits<int>::max());
138
139
0
    auto const it = std::search(target.begin(), target.end(), src.begin(), src.end());
140
141
    // no complete sync
142
0
    if (it == target.end()) return -1;
143
0
    return static_cast<int>(it - target.begin());
144
0
  }
145
146
  char* allocate_string_copy(string_view str)
147
0
  {
148
0
    if (str.empty()) return nullptr;
149
0
    auto* tmp = new char[str.size() + 1];
150
0
    std::copy(str.data(), str.data() + str.size(), tmp);
151
0
    tmp[str.size()] = '\0';
152
0
    return tmp;
153
0
  }
154
155
#if TORRENT_ABI_VERSION == 1 \
156
  || !defined TORRENT_DISABLE_LOGGING
157
  std::string print_listen_interfaces(std::vector<listen_interface_t> const& in)
158
0
  {
159
0
    std::string ret;
160
0
    for (auto const& i : in)
161
0
    {
162
0
      if (!ret.empty()) ret += ',';
163
164
0
      error_code ec;
165
0
      make_address_v6(i.device, ec);
166
0
      if (!ec)
167
0
      {
168
        // IPv6 addresses must be wrapped in square brackets
169
0
        ret += '[';
170
0
        ret += i.device;
171
0
        ret += ']';
172
0
      }
173
0
      else
174
0
      {
175
0
        ret += i.device;
176
0
      }
177
0
      ret += ':';
178
0
      ret += to_string(i.port).data();
179
0
      if (i.ssl) ret += 's';
180
0
      if (i.local) ret += 'l';
181
0
    }
182
183
0
    return ret;
184
0
  }
185
#endif
186
187
  string_view strip_string(string_view in)
188
0
  {
189
0
    while (!in.empty() && is_space(in.front()))
190
0
      in.remove_prefix(1);
191
192
0
    while (!in.empty() && is_space(in.back()))
193
0
      in.remove_suffix(1);
194
0
    return in;
195
0
  }
196
197
  // this parses the string that's used as the listen_interfaces setting.
198
  // it is a comma-separated list of IP or device names with ports. For
199
  // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881"
200
  std::vector<listen_interface_t> parse_listen_interfaces(std::string const& in
201
    , std::vector<std::string>& err)
202
0
  {
203
0
    std::vector<listen_interface_t> out;
204
205
0
    string_view rest = in;
206
0
    while (!rest.empty())
207
0
    {
208
0
      string_view element;
209
0
      std::tie(element, rest) = split_string_quotes(rest, ',');
210
211
0
      element = strip_string(element);
212
0
      if (element.size() > 1 && element.front() == '"' && element.back() == '"')
213
0
        element = element.substr(1, element.size() - 2);
214
0
      if (element.empty()) continue;
215
216
0
      listen_interface_t iface;
217
0
      iface.ssl = false;
218
0
      iface.local = false;
219
220
0
      string_view port;
221
0
      if (element.front() == '[')
222
0
      {
223
0
        auto const pos = find_first_of(element, ']', 0);
224
0
        if (pos == string_view::npos
225
0
          || pos+1 >= element.size()
226
0
          || element[pos+1] != ':')
227
0
        {
228
0
          err.emplace_back(element);
229
0
          continue;
230
0
        }
231
232
0
        iface.device = strip_string(element.substr(1, pos - 1)).to_string();
233
234
0
        port = strip_string(element.substr(pos + 2));
235
0
      }
236
0
      else
237
0
      {
238
        // consume device name
239
0
        auto const pos = find_first_of(element, ':', 0);
240
0
        iface.device = strip_string(element.substr(0, pos)).to_string();
241
0
        if (pos == string_view::npos)
242
0
        {
243
0
          err.emplace_back(element);
244
0
          continue;
245
0
        }
246
0
        port = strip_string(element.substr(pos + 1));
247
0
      }
248
249
      // consume port
250
0
      std::string port_str;
251
0
      for (std::size_t i = 0; i < port.size() && is_digit(port[i]); ++i)
252
0
        port_str += port[i];
253
254
0
      if (port_str.empty() || port_str.size() > 5)
255
0
      {
256
0
        err.emplace_back(element);
257
0
        continue;
258
0
      }
259
260
0
      iface.port = std::atoi(port_str.c_str());
261
0
      if (iface.port < 0 || iface.port > 65535)
262
0
      {
263
0
        err.emplace_back(element);
264
0
        continue;
265
0
      }
266
267
0
      port.remove_prefix(port_str.size());
268
0
      port = strip_string(port);
269
270
      // consume potential SSL 's'
271
0
      for (auto const c : port)
272
0
      {
273
0
        switch (c)
274
0
        {
275
0
          case 's': iface.ssl = true; break;
276
0
          case 'l': iface.local = true; break;
277
0
        }
278
0
      }
279
280
0
      TORRENT_ASSERT(iface.port >= 0);
281
0
      out.emplace_back(iface);
282
0
    }
283
284
0
    return out;
285
0
  }
286
287
  // this parses the string that's used as the dht_bootstrap setting.
288
  // it is a comma-separated list of IP or hostnames with ports. For
289
  // example: "router.bittorrent.com:6881,router.utorrent.com:6881" or "127.0.0.1:6881"
290
  void parse_comma_separated_string_port(std::string const& in
291
    , std::vector<std::pair<std::string, int>>& out)
292
0
  {
293
0
    out.clear();
294
295
0
    std::string::size_type start = 0;
296
0
    std::string::size_type end = 0;
297
298
0
    while (start < in.size())
299
0
    {
300
      // skip leading spaces
301
0
      while (start < in.size()
302
0
        && is_space(in[start]))
303
0
        ++start;
304
305
0
      end = in.find_first_of(',', start);
306
0
      if (end == std::string::npos) end = in.size();
307
308
0
      std::string::size_type colon = in.find_last_of(':', end);
309
310
0
      if (colon != std::string::npos && colon > start)
311
0
      {
312
0
        int port = std::atoi(in.substr(colon + 1, end - colon - 1).c_str());
313
314
        // skip trailing spaces
315
0
        std::string::size_type soft_end = colon;
316
0
        while (soft_end > start
317
0
          && is_space(in[soft_end-1]))
318
0
          --soft_end;
319
320
        // in case this is an IPv6 address, strip off the square brackets
321
        // to make it more easily parseable into an ip::address
322
0
        if (in[start] == '[') ++start;
323
0
        if (soft_end > start && in[soft_end-1] == ']') --soft_end;
324
325
0
        out.emplace_back(in.substr(start, soft_end - start), port);
326
0
      }
327
328
0
      start = end + 1;
329
0
    }
330
0
  }
331
332
  void parse_comma_separated_string(std::string const& in, std::vector<std::string>& out)
333
0
  {
334
0
    out.clear();
335
336
0
    std::string::size_type start = 0;
337
0
    std::string::size_type end = 0;
338
339
0
    while (start < in.size())
340
0
    {
341
      // skip leading spaces
342
0
      while (start < in.size()
343
0
        && is_space(in[start]))
344
0
        ++start;
345
346
0
      end = in.find_first_of(',', start);
347
0
      if (end == std::string::npos) end = in.size();
348
349
      // skip trailing spaces
350
0
      std::string::size_type soft_end = end;
351
0
      while (soft_end > start
352
0
        && is_space(in[soft_end - 1]))
353
0
        --soft_end;
354
355
0
      out.push_back(in.substr(start, soft_end - start));
356
0
      start = end + 1;
357
0
    }
358
0
  }
359
360
  std::pair<string_view, string_view> split_string(string_view last, char const sep)
361
0
  {
362
0
    auto const pos = last.find(sep);
363
0
    if (pos == string_view::npos) return {last, {}};
364
0
    else return {last.substr(0, pos), last.substr(pos + 1)};
365
0
  }
366
367
  std::pair<string_view, string_view> split_string_quotes(string_view last, char const sep)
368
0
  {
369
0
    if (last.empty()) return {{}, {}};
370
371
0
    std::size_t pos = 0;
372
0
    if (last[0] == '"' && sep != '"')
373
0
    {
374
0
      for (auto const c : last.substr(1))
375
0
      {
376
0
        ++pos;
377
0
        if (c == '"') break;
378
0
      }
379
0
    }
380
0
    std::size_t found_sep = 0;
381
0
    for (char const c : last.substr(pos))
382
0
    {
383
0
      if (c == sep)
384
0
      {
385
0
        found_sep = 1;
386
0
        break;
387
0
      }
388
0
      ++pos;
389
0
    }
390
0
    return {last.substr(0, pos), last.substr(pos + found_sep)};
391
0
  }
392
393
  void ltrim(std::string& s)
394
0
  {
395
0
    while (!s.empty() && is_space(s.front()))
396
0
      s.erase(s.begin());
397
0
  }
398
399
  std::string format_host_for_connect(std::string host, unsigned short const port)
400
0
  {
401
    // Handle edge case: if no port specified, return host as-is
402
0
    if (port == 0) return host;
403
404
    // Already bracketed IPv6 literal
405
0
    if (!host.empty() && host.front() == '[' && host.back() == ']')
406
0
    {
407
0
      host += ":";
408
0
      host += to_string(port).data();
409
0
      return host;
410
0
    }
411
412
    // Contains colons (unbracketed IPv6) - need to bracket
413
0
    if (host.find(':') != string_view::npos)
414
0
    {
415
0
      host = "[" + host;
416
0
      host += "]:";
417
0
      host += to_string(port).data();
418
0
      return host;
419
0
    }
420
421
    // Regular hostname or IPv4
422
0
    host += ":";
423
0
    host += to_string(port).data();
424
0
    return host;
425
0
  }
426
427
#if TORRENT_USE_I2P
428
429
  bool is_i2p_url(std::string const& url)
430
0
  {
431
0
    using std::ignore;
432
0
    std::string hostname;
433
0
    error_code ec;
434
0
    std::tie(ignore, ignore, hostname, ignore, ignore)
435
0
      = parse_url_components(url, ec);
436
0
    return string_ends_with(hostname, ".i2p");
437
0
  }
438
439
#endif
440
}