1826 lines
51 KiB
C++
1826 lines
51 KiB
C++
// -*- mode: cpp; mode: fold -*-
|
||
// SPDX-License-Identifier: GPL-2.0+
|
||
// Description /*{{{*/
|
||
/* ######################################################################
|
||
|
||
String Util - Some useful string functions.
|
||
|
||
These have been collected from here and there to do all sorts of useful
|
||
things to strings. They are useful in file parsers, URI handlers and
|
||
especially in APT methods.
|
||
|
||
This file had this historic note, but now includes further changes
|
||
under the GPL-2.0+:
|
||
|
||
This source is placed in the Public Domain, do with it what you will
|
||
It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca>
|
||
|
||
##################################################################### */
|
||
/*}}}*/
|
||
// Includes /*{{{*/
|
||
#include <config.h>
|
||
|
||
#include <apt-pkg/error.h>
|
||
#include <apt-pkg/fileutl.h>
|
||
#include <apt-pkg/strutl.h>
|
||
|
||
#include <algorithm>
|
||
#include <array>
|
||
#include <cstdint>
|
||
#include <iomanip>
|
||
#include <limits>
|
||
#include <locale>
|
||
#include <sstream>
|
||
#include <memory>
|
||
#include <sstream>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include <cctype>
|
||
#include <cerrno>
|
||
#include <cstdarg>
|
||
#include <cstddef>
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
#include <cstring>
|
||
#include <ctime>
|
||
#include <cwchar>
|
||
#include <iconv.h>
|
||
#include <regex.h>
|
||
#include <unistd.h>
|
||
|
||
#include <apti18n.h>
|
||
/*}}}*/
|
||
using namespace std;
|
||
|
||
// Strip - Remove white space from the front and back of a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
namespace APT {
|
||
namespace String {
|
||
std::string_view Strip(std::string_view str)
|
||
{
|
||
while (!str.empty() && isspace(str[0]))
|
||
str.remove_prefix(1);
|
||
while (!str.empty() && isspace(str.back()))
|
||
str.remove_suffix(1);
|
||
return str;
|
||
}
|
||
|
||
bool Endswith(const std::string_view &s, const std::string_view &end)
|
||
{
|
||
if (end.size() > s.size())
|
||
return false;
|
||
return (s.compare(s.size() - end.size(), end.size(), end) == 0);
|
||
}
|
||
|
||
bool Startswith(const std::string_view &s, const std::string_view &start)
|
||
{
|
||
if (start.size() > s.size())
|
||
return false;
|
||
return (s.compare(0, start.size(), start) == 0);
|
||
}
|
||
|
||
std::string Join(std::vector<std::string> list, const std::string_view &sep)
|
||
{
|
||
std::ostringstream oss;
|
||
for (auto it = list.begin(); it != list.end(); it++)
|
||
{
|
||
if (it != list.begin()) oss << sep;
|
||
oss << *it;
|
||
}
|
||
return oss.str();
|
||
}
|
||
|
||
// Returns string display length honoring multi-byte characters
|
||
size_t DisplayLength(string_view str)
|
||
{
|
||
size_t len = 0;
|
||
|
||
const char *p = str.data();
|
||
const char *const end = str.end();
|
||
|
||
mbstate_t state{};
|
||
while (p < end)
|
||
{
|
||
wchar_t wch;
|
||
size_t res = mbrtowc(&wch, p, end - p, &state);
|
||
switch (res)
|
||
{
|
||
case 0:
|
||
// Null wide character (i.e. L'\0') - stop
|
||
p = end;
|
||
break;
|
||
|
||
case static_cast<size_t>(-1):
|
||
// Byte sequence is invalid. Assume that it's
|
||
// a single-byte single-width character.
|
||
len += 1;
|
||
p += 1;
|
||
|
||
// state is undefined in this case - reset it
|
||
state = {};
|
||
|
||
break;
|
||
|
||
case static_cast<size_t>(-2):
|
||
// Byte sequence is too short. Assume that it's
|
||
// an incomplete single-width character and stop.
|
||
len += 1;
|
||
p = end;
|
||
break;
|
||
|
||
default:
|
||
len += wcwidth(wch);
|
||
p += res;
|
||
}
|
||
}
|
||
|
||
return len;
|
||
}
|
||
|
||
}
|
||
}
|
||
/*}}}*/
|
||
// UTF8ToCodeset - Convert some UTF-8 string for some codeset /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is handy to use before display some information for enduser */
|
||
bool UTF8ToCodeset(const char *codeset, const string &orig, string *dest)
|
||
{
|
||
iconv_t cd;
|
||
const char *inbuf;
|
||
char *inptr, *outbuf;
|
||
size_t insize, bufsize;
|
||
dest->clear();
|
||
|
||
cd = iconv_open(codeset, "UTF-8");
|
||
if (cd == (iconv_t)(-1)) {
|
||
// Something went wrong
|
||
if (errno == EINVAL)
|
||
_error->Error("conversion from 'UTF-8' to '%s' not available",
|
||
codeset);
|
||
else
|
||
perror("iconv_open");
|
||
|
||
return false;
|
||
}
|
||
|
||
insize = bufsize = orig.size();
|
||
inbuf = orig.data();
|
||
inptr = (char *)inbuf;
|
||
outbuf = new char[bufsize];
|
||
size_t lastError = -1;
|
||
|
||
while (insize != 0)
|
||
{
|
||
char *outptr = outbuf;
|
||
size_t outsize = bufsize;
|
||
size_t const err = iconv(cd, &inptr, &insize, &outptr, &outsize);
|
||
dest->append(outbuf, outptr - outbuf);
|
||
if (err == (size_t)(-1))
|
||
{
|
||
switch (errno)
|
||
{
|
||
case EILSEQ:
|
||
insize--;
|
||
inptr++;
|
||
// replace a series of unknown multibytes with a single "?"
|
||
if (lastError != insize) {
|
||
lastError = insize - 1;
|
||
dest->append("?");
|
||
}
|
||
break;
|
||
case EINVAL:
|
||
insize = 0;
|
||
break;
|
||
case E2BIG:
|
||
if (outptr == outbuf)
|
||
{
|
||
bufsize *= 2;
|
||
delete[] outbuf;
|
||
outbuf = new char[bufsize];
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
delete[] outbuf;
|
||
|
||
iconv_close(cd);
|
||
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// strstrip - Remove white space from the front and back of a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is handy to use when parsing a file. It also removes \n's left
|
||
over from fgets and company */
|
||
char *_strstrip(char *String)
|
||
{
|
||
for (;*String != 0 && (*String == ' ' || *String == '\t'); String++);
|
||
|
||
if (*String == 0)
|
||
return String;
|
||
return _strrstrip(String);
|
||
}
|
||
/*}}}*/
|
||
// strrstrip - Remove white space from the back of a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
char *_strrstrip(char *String)
|
||
{
|
||
char *End = String + strlen(String) - 1;
|
||
for (;End != String - 1 && (*End == ' ' || *End == '\t' || *End == '\n' ||
|
||
*End == '\r'); End--);
|
||
End++;
|
||
*End = 0;
|
||
return String;
|
||
}
|
||
/*}}}*/
|
||
// ParseQuoteWord - Parse a single word out of a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This grabs a single word, converts any % escaped characters to their
|
||
proper values and advances the pointer. Double quotes are understood
|
||
and striped out as well. This is for URI/URL parsing. It also can
|
||
understand [] brackets.*/
|
||
bool ParseQuoteWord(const char *&String,string &Res)
|
||
{
|
||
// Skip leading whitespace
|
||
const char *C = String;
|
||
for (; *C == ' '; C++)
|
||
;
|
||
if (*C == 0)
|
||
return false;
|
||
|
||
// Jump to the next word
|
||
for (;*C != 0 && isspace(*C) == 0; C++)
|
||
{
|
||
if (*C == '"')
|
||
{
|
||
C = strchr(C + 1, '"');
|
||
if (C == NULL)
|
||
return false;
|
||
}
|
||
if (*C == '[')
|
||
{
|
||
C = strchr(C + 1, ']');
|
||
if (C == NULL)
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Now de-quote characters
|
||
Res.clear();
|
||
Res.reserve(C - String);
|
||
char Tmp[3];
|
||
const char *Start = String;
|
||
while (Start != C)
|
||
{
|
||
if (*Start == '%' && Start + 2 < C &&
|
||
isxdigit(Start[1]) && isxdigit(Start[2]))
|
||
{
|
||
Tmp[0] = Start[1];
|
||
Tmp[1] = Start[2];
|
||
Tmp[2] = 0;
|
||
Res.push_back(static_cast<char>(strtol(Tmp, 0, 16)));
|
||
Start += 3;
|
||
continue;
|
||
}
|
||
if (*Start != '"')
|
||
Res.push_back(*Start);
|
||
++Start;
|
||
}
|
||
|
||
// Skip ending white space
|
||
for (; isspace(*C) != 0; C++)
|
||
;
|
||
String = C;
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// ParseCWord - Parses a string like a C "" expression /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This expects a series of space separated strings enclosed in ""'s.
|
||
It concatenates the ""'s into a single string. */
|
||
bool ParseCWord(const char *&String,string &Res)
|
||
{
|
||
// Skip leading whitespace
|
||
const char *C = String;
|
||
for (; *C == ' '; C++)
|
||
;
|
||
if (*C == 0)
|
||
return false;
|
||
|
||
Res.clear();
|
||
Res.reserve(strlen(String));
|
||
for (; *C != 0; ++C)
|
||
{
|
||
if (*C == '"')
|
||
{
|
||
for (C++; *C != 0 && *C != '"'; C++)
|
||
Res.push_back(*C);
|
||
|
||
if (*C == 0)
|
||
return false;
|
||
|
||
continue;
|
||
}
|
||
|
||
if (C != String && isspace(*C) != 0 && isspace(C[-1]) != 0)
|
||
continue;
|
||
if (isspace(*C) == 0)
|
||
return false;
|
||
Res.push_back(' ');
|
||
}
|
||
String = C;
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// QuoteString - Convert a string into quoted from /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* */
|
||
string QuoteString(const string &Str, const char *Bad)
|
||
{
|
||
std::stringstream Res;
|
||
for (string::const_iterator I = Str.begin(); I != Str.end(); ++I)
|
||
{
|
||
if (strchr(Bad,*I) != 0 || isprint(*I) == 0 ||
|
||
*I == 0x25 || // percent '%' char
|
||
*I <= 0x20 || *I >= 0x7F) // control chars
|
||
{
|
||
ioprintf(Res, "%%%02hhx", *I);
|
||
}
|
||
else
|
||
Res << *I;
|
||
}
|
||
return Res.str();
|
||
}
|
||
/*}}}*/
|
||
// DeQuoteString - Convert a string from quoted from /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This undoes QuoteString */
|
||
string DeQuoteString(const string &Str)
|
||
{
|
||
return DeQuoteString(Str.begin(),Str.end());
|
||
}
|
||
string DeQuoteString(string::const_iterator const &begin,
|
||
string::const_iterator const &end)
|
||
{
|
||
string Res;
|
||
for (string::const_iterator I = begin; I != end; ++I)
|
||
{
|
||
if (*I == '%' && I + 2 < end &&
|
||
isxdigit(I[1]) && isxdigit(I[2]))
|
||
{
|
||
char Tmp[3];
|
||
Tmp[0] = I[1];
|
||
Tmp[1] = I[2];
|
||
Tmp[2] = 0;
|
||
Res += (char)strtol(Tmp,0,16);
|
||
I += 2;
|
||
continue;
|
||
}
|
||
else
|
||
Res += *I;
|
||
}
|
||
return Res;
|
||
}
|
||
|
||
/*}}}*/
|
||
// SizeToStr - Convert a long into a human readable size /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* A max of 4 digits are shown before conversion to the next highest unit.
|
||
The max length of the string will be 5 chars unless the size is > 10
|
||
YottaBytes (E24) */
|
||
string SizeToStr(double Size)
|
||
{
|
||
double ASize;
|
||
if (Size >= 0)
|
||
ASize = Size;
|
||
else
|
||
ASize = -1*Size;
|
||
|
||
/* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes,
|
||
ExaBytes, ZettaBytes, YottaBytes */
|
||
char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'};
|
||
int I = 0;
|
||
while (I <= 8)
|
||
{
|
||
if (ASize < 100 && I != 0)
|
||
{
|
||
std::string S;
|
||
strprintf(S, "%'.1f %c", ASize, Ext[I]);
|
||
return S;
|
||
}
|
||
|
||
if (ASize < 10000)
|
||
{
|
||
std::string S;
|
||
strprintf(S, "%'.0f %c", ASize, Ext[I]);
|
||
return S;
|
||
}
|
||
ASize /= 1000.0;
|
||
I++;
|
||
}
|
||
return "";
|
||
}
|
||
/*}}}*/
|
||
// TimeToStr - Convert the time into a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* Converts a number of seconds to a hms format */
|
||
string TimeToStr(unsigned long Sec)
|
||
{
|
||
std::string S;
|
||
if (Sec > 60*60*24)
|
||
{
|
||
//TRANSLATOR: d means days, h means hours, min means minutes, s means seconds
|
||
strprintf(S,_("%lid %lih %limin %lis"),Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60);
|
||
}
|
||
else if (Sec > 60*60)
|
||
{
|
||
//TRANSLATOR: h means hours, min means minutes, s means seconds
|
||
strprintf(S,_("%lih %limin %lis"),Sec/60/60,(Sec/60) % 60,Sec % 60);
|
||
}
|
||
else if (Sec > 60)
|
||
{
|
||
//TRANSLATOR: min means minutes, s means seconds
|
||
strprintf(S,_("%limin %lis"),Sec/60,Sec % 60);
|
||
}
|
||
else
|
||
{
|
||
//TRANSLATOR: s means seconds
|
||
strprintf(S,_("%lis"),Sec);
|
||
}
|
||
return S;
|
||
}
|
||
/*}}}*/
|
||
// SubstVar - Substitute a string for another string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This replaces all occurrences of Subst with Contents in Str. */
|
||
string SubstVar(const string_view &Str,const string_view &Subst,const string_view &Contents)
|
||
{
|
||
if (Subst.empty() == true)
|
||
return std::string{Str};
|
||
|
||
string::size_type Pos = 0;
|
||
string::size_type OldPos = 0;
|
||
string Temp;
|
||
|
||
while (OldPos < Str.length() &&
|
||
(Pos = Str.find(Subst,OldPos)) != string::npos)
|
||
{
|
||
if (OldPos != Pos)
|
||
Temp.append(Str, OldPos, Pos - OldPos);
|
||
if (Contents.empty() == false)
|
||
Temp.append(Contents);
|
||
OldPos = Pos + Subst.length();
|
||
}
|
||
|
||
if (OldPos == 0)
|
||
return std::string{Str};
|
||
|
||
if (OldPos >= Str.length())
|
||
return Temp;
|
||
|
||
Temp.append(Str, OldPos, string::npos);
|
||
return Temp;
|
||
}
|
||
string SubstVar(string Str,const struct SubstVar *Vars)
|
||
{
|
||
for (; Vars->Subst != 0; Vars++)
|
||
Str = SubstVar(Str,Vars->Subst,*Vars->Contents);
|
||
return Str;
|
||
}
|
||
/*}}}*/
|
||
// OutputInDepth - return a string with separator multiplied with depth /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* Returns a string with the supplied separator depth + 1 times in it */
|
||
std::string OutputInDepth(const unsigned long Depth, const char* Separator)
|
||
{
|
||
std::string output = "";
|
||
for(unsigned long d=Depth+1; d > 0; d--)
|
||
output.append(Separator);
|
||
return output;
|
||
}
|
||
/*}}}*/
|
||
// URItoFileName - Convert the uri into a unique file name /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This converts a URI into a safe filename. It quotes all unsafe characters
|
||
and converts / to _ and removes the scheme identifier. The resulting
|
||
file name should be unique and never occur again for a different file */
|
||
string URItoFileName(const string &URI)
|
||
{
|
||
// Nuke 'sensitive' items
|
||
::URI U(URI);
|
||
U.User.clear();
|
||
U.Password.clear();
|
||
U.Access.clear();
|
||
|
||
// "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF";
|
||
string NewURI = QuoteString(U,"\\|{}[]<>\"^~_=!@#$%^&*");
|
||
replace(NewURI.begin(),NewURI.end(),'/','_');
|
||
return NewURI;
|
||
}
|
||
/*}}}*/
|
||
// Base64Encode - Base64 Encoding routine for short strings /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This routine performs a base64 transformation on a string. It was ripped
|
||
from wget and then patched and bug fixed.
|
||
|
||
This spec can be found in rfc2045 */
|
||
string Base64Encode(const string &S)
|
||
{
|
||
// Conversion table.
|
||
static char tbl[64] = {'A','B','C','D','E','F','G','H',
|
||
'I','J','K','L','M','N','O','P',
|
||
'Q','R','S','T','U','V','W','X',
|
||
'Y','Z','a','b','c','d','e','f',
|
||
'g','h','i','j','k','l','m','n',
|
||
'o','p','q','r','s','t','u','v',
|
||
'w','x','y','z','0','1','2','3',
|
||
'4','5','6','7','8','9','+','/'};
|
||
|
||
// Pre-allocate some space
|
||
string Final;
|
||
Final.reserve((4*S.length() + 2)/3 + 2);
|
||
|
||
/* Transform the 3x8 bits to 4x6 bits, as required by
|
||
base64. */
|
||
for (string::const_iterator I = S.begin(); I < S.end(); I += 3)
|
||
{
|
||
uint8_t Bits[3] = {0,0,0};
|
||
Bits[0] = I[0];
|
||
if (I + 1 < S.end())
|
||
Bits[1] = I[1];
|
||
if (I + 2 < S.end())
|
||
Bits[2] = I[2];
|
||
|
||
Final += tbl[Bits[0] >> 2];
|
||
Final += tbl[((Bits[0] & 3) << 4) + (Bits[1] >> 4)];
|
||
|
||
if (I + 1 >= S.end())
|
||
break;
|
||
|
||
Final += tbl[((Bits[1] & 0xf) << 2) + (Bits[2] >> 6)];
|
||
|
||
if (I + 2 >= S.end())
|
||
break;
|
||
|
||
Final += tbl[Bits[2] & 0x3f];
|
||
}
|
||
|
||
/* Apply the padding elements, this tells how many bytes the remote
|
||
end should discard */
|
||
if (S.length() % 3 == 2)
|
||
Final += '=';
|
||
if (S.length() % 3 == 1)
|
||
Final += "==";
|
||
|
||
return Final;
|
||
}
|
||
/*}}}*/
|
||
// Base64Decode - Base64 decoding routine for short strings /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
// Taken from mjg59's dpkg code: https://lists.debian.org/debian-dpkg/2018/05/msg00002.html
|
||
std::string Base64Decode(const std::string_view in)
|
||
{
|
||
constexpr std::array<unsigned char, 256> mime_base64_rank{
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
|
||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
|
||
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
|
||
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};
|
||
std::string out;
|
||
unsigned char rank;
|
||
unsigned char last[2];
|
||
unsigned int v;
|
||
int i;
|
||
|
||
/* convert 4 base64 bytes to 3 normal bytes */
|
||
v = 0;
|
||
i = 0;
|
||
|
||
last[0] = last[1] = 0;
|
||
|
||
/* we use the sign in the state to determine if we got a padding character
|
||
in the previous sequence */
|
||
if (i < 0)
|
||
{
|
||
i = -i;
|
||
last[0] = '=';
|
||
}
|
||
|
||
for (const unsigned char c : in)
|
||
{
|
||
rank = mime_base64_rank[c];
|
||
if (rank != 0xff)
|
||
{
|
||
last[1] = last[0];
|
||
last[0] = c;
|
||
v = (v << 6) | rank;
|
||
i++;
|
||
if (i == 4)
|
||
{
|
||
out += v >> 16;
|
||
if (last[1] != '=')
|
||
out += v >> 8;
|
||
if (last[0] != '=')
|
||
out += v;
|
||
i = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
return out;
|
||
}
|
||
|
||
/*}}}*/
|
||
// stringcmp - Arbitrary string compare /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This safely compares two non-null terminated strings of arbitrary
|
||
length */
|
||
int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
|
||
{
|
||
for (; A != AEnd && B != BEnd; A++, B++)
|
||
if (*A != *B)
|
||
break;
|
||
|
||
if (A == AEnd && B == BEnd)
|
||
return 0;
|
||
if (A == AEnd)
|
||
return 1;
|
||
if (B == BEnd)
|
||
return -1;
|
||
if (*A < *B)
|
||
return -1;
|
||
return 1;
|
||
}
|
||
|
||
#if __GNUC__ >= 3
|
||
int stringcmp(string::const_iterator A,string::const_iterator AEnd,
|
||
const char *B,const char *BEnd)
|
||
{
|
||
for (; A != AEnd && B != BEnd; A++, B++)
|
||
if (*A != *B)
|
||
break;
|
||
|
||
if (A == AEnd && B == BEnd)
|
||
return 0;
|
||
if (A == AEnd)
|
||
return 1;
|
||
if (B == BEnd)
|
||
return -1;
|
||
if (*A < *B)
|
||
return -1;
|
||
return 1;
|
||
}
|
||
int stringcmp(string::const_iterator A,string::const_iterator AEnd,
|
||
string::const_iterator B,string::const_iterator BEnd)
|
||
{
|
||
for (; A != AEnd && B != BEnd; A++, B++)
|
||
if (*A != *B)
|
||
break;
|
||
|
||
if (A == AEnd && B == BEnd)
|
||
return 0;
|
||
if (A == AEnd)
|
||
return 1;
|
||
if (B == BEnd)
|
||
return -1;
|
||
if (*A < *B)
|
||
return -1;
|
||
return 1;
|
||
}
|
||
#endif
|
||
/*}}}*/
|
||
// stringcasecmp - Arbitrary case insensitive string compare /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* */
|
||
int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
|
||
{
|
||
for (; A != AEnd && B != BEnd; A++, B++)
|
||
if (tolower_ascii(*A) != tolower_ascii(*B))
|
||
break;
|
||
|
||
if (A == AEnd && B == BEnd)
|
||
return 0;
|
||
if (A == AEnd)
|
||
return 1;
|
||
if (B == BEnd)
|
||
return -1;
|
||
if (tolower_ascii(*A) < tolower_ascii(*B))
|
||
return -1;
|
||
return 1;
|
||
}
|
||
#if __GNUC__ >= 3
|
||
int stringcasecmp(string::const_iterator A,string::const_iterator AEnd,
|
||
const char *B,const char *BEnd)
|
||
{
|
||
for (; A != AEnd && B != BEnd; A++, B++)
|
||
if (tolower_ascii(*A) != tolower_ascii(*B))
|
||
break;
|
||
|
||
if (A == AEnd && B == BEnd)
|
||
return 0;
|
||
if (A == AEnd)
|
||
return 1;
|
||
if (B == BEnd)
|
||
return -1;
|
||
if (tolower_ascii(*A) < tolower_ascii(*B))
|
||
return -1;
|
||
return 1;
|
||
}
|
||
int stringcasecmp(string::const_iterator A,string::const_iterator AEnd,
|
||
string::const_iterator B,string::const_iterator BEnd)
|
||
{
|
||
for (; A != AEnd && B != BEnd; A++, B++)
|
||
if (tolower_ascii(*A) != tolower_ascii(*B))
|
||
break;
|
||
|
||
if (A == AEnd && B == BEnd)
|
||
return 0;
|
||
if (A == AEnd)
|
||
return 1;
|
||
if (B == BEnd)
|
||
return -1;
|
||
if (tolower_ascii(*A) < tolower_ascii(*B))
|
||
return -1;
|
||
return 1;
|
||
}
|
||
#endif
|
||
/*}}}*/
|
||
// LookupTag - Lookup the value of a tag in a tagged string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* The format is like those used in package files and the method
|
||
communication system */
|
||
std::string LookupTag(const std::string &Message, const char *TagC, const char *Default)
|
||
{
|
||
std::string tag = std::string("\n") + TagC + ":";
|
||
if (Default == nullptr)
|
||
Default = "";
|
||
if (Message.length() < tag.length())
|
||
return Default;
|
||
std::transform(tag.begin(), tag.end(), tag.begin(), tolower_ascii);
|
||
auto valuestart = Message.cbegin();
|
||
// maybe the message starts directly with tag
|
||
if (Message[tag.length() - 2] == ':')
|
||
{
|
||
std::string lowstart = std::string("\n") + Message.substr(0, tag.length() - 1);
|
||
std::transform(lowstart.begin(), lowstart.end(), lowstart.begin(), tolower_ascii);
|
||
if (lowstart == tag)
|
||
valuestart = std::next(valuestart, tag.length() - 1);
|
||
}
|
||
// the tag is somewhere in the message
|
||
if (valuestart == Message.cbegin())
|
||
{
|
||
auto const tagbegin = std::search(Message.cbegin(), Message.cend(), tag.cbegin(), tag.cend(),
|
||
[](char const a, char const b) { return tolower_ascii(a) == b; });
|
||
if (tagbegin == Message.cend())
|
||
return Default;
|
||
valuestart = std::next(tagbegin, tag.length());
|
||
}
|
||
auto const is_whitespace = [](char const c) { return isspace_ascii(c) != 0 && c != '\n'; };
|
||
auto const is_newline = [](char const c) { return c == '\n'; };
|
||
std::string result;
|
||
valuestart = std::find_if_not(valuestart, Message.cend(), is_whitespace);
|
||
// is the first line of the value empty?
|
||
if (valuestart != Message.cend() && *valuestart == '\n')
|
||
{
|
||
valuestart = std::next(valuestart);
|
||
if (valuestart != Message.cend() && *valuestart == ' ')
|
||
valuestart = std::next(valuestart);
|
||
}
|
||
// extract the value over multiple lines removing trailing whitespace
|
||
while (valuestart < Message.cend())
|
||
{
|
||
auto const linebreak = std::find_if(valuestart, Message.cend(), is_newline);
|
||
auto valueend = std::prev(linebreak);
|
||
// skip spaces at the end of the line
|
||
while (valueend > valuestart && is_whitespace(*valueend))
|
||
valueend = std::prev(valueend);
|
||
// append found line to result
|
||
{
|
||
std::string tmp(valuestart, std::next(valueend));
|
||
if (tmp != ".")
|
||
{
|
||
if (result.empty())
|
||
result.assign(std::move(tmp));
|
||
else
|
||
result.append(tmp);
|
||
}
|
||
}
|
||
// see if the value is multiline
|
||
if (linebreak == Message.cend())
|
||
break;
|
||
valuestart = std::next(linebreak);
|
||
if (valuestart == Message.cend() || *valuestart != ' ')
|
||
break;
|
||
result.append("\n");
|
||
// skip the space leading a multiline (Keep all other whitespaces in the value)
|
||
valuestart = std::next(valuestart);
|
||
}
|
||
auto const valueend = result.find_last_not_of("\n");
|
||
if (valueend == std::string::npos)
|
||
result.clear();
|
||
else
|
||
result.erase(valueend + 1);
|
||
return result;
|
||
}
|
||
/*}}}*/
|
||
// StringToBool - Converts a string into a boolean /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This inspects the string to see if it is true or if it is false and
|
||
then returns the result. Several variants on true/false are checked. */
|
||
int StringToBool(const string &Text,int Default)
|
||
{
|
||
char *ParseEnd;
|
||
int Res = strtol(Text.c_str(),&ParseEnd,0);
|
||
// ensure that the entire string was converted by strtol to avoid
|
||
// failures on "apt-cache show -a 0ad" where the "0" is converted
|
||
const char *TextEnd = Text.c_str()+Text.size();
|
||
if (ParseEnd == TextEnd && Res >= 0 && Res <= 1)
|
||
return Res;
|
||
|
||
// Check for positives
|
||
if (strcasecmp(Text.c_str(),"no") == 0 ||
|
||
strcasecmp(Text.c_str(),"false") == 0 ||
|
||
strcasecmp(Text.c_str(),"without") == 0 ||
|
||
strcasecmp(Text.c_str(),"off") == 0 ||
|
||
strcasecmp(Text.c_str(),"disable") == 0)
|
||
return 0;
|
||
|
||
// Check for negatives
|
||
if (strcasecmp(Text.c_str(),"yes") == 0 ||
|
||
strcasecmp(Text.c_str(),"true") == 0 ||
|
||
strcasecmp(Text.c_str(),"with") == 0 ||
|
||
strcasecmp(Text.c_str(),"on") == 0 ||
|
||
strcasecmp(Text.c_str(),"enable") == 0)
|
||
return 1;
|
||
|
||
return Default;
|
||
}
|
||
/*}}}*/
|
||
// TimeRFC1123 - Convert a time_t into RFC1123 format /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This converts a time_t into a string time representation that is
|
||
year 2000 compliant and timezone neutral */
|
||
string TimeRFC1123(time_t Date, bool const NumericTimezone)
|
||
{
|
||
struct tm Conv;
|
||
if (gmtime_r(&Date, &Conv) == NULL)
|
||
return "";
|
||
|
||
auto const posix = std::locale::classic();
|
||
std::ostringstream datestr;
|
||
datestr.imbue(posix);
|
||
std::string_view const fmt("%a, %d %b %Y %H:%M:%S");
|
||
std::use_facet<std::time_put<char>>(posix).put(
|
||
std::ostreambuf_iterator<char>(datestr),
|
||
datestr, ' ', &Conv, fmt.data(), fmt.data() + fmt.size());
|
||
if (NumericTimezone)
|
||
datestr << " +0000";
|
||
else
|
||
datestr << " GMT";
|
||
return datestr.str();
|
||
}
|
||
/*}}}*/
|
||
// ReadMessages - Read messages from the FD /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This pulls full messages from the input FD into the message buffer.
|
||
It assumes that messages will not pause during transit so no
|
||
fancy buffering is used.
|
||
|
||
In particular: this reads blocks from the input until it believes
|
||
that it's run out of input text. Each block is terminated by a
|
||
double newline ('\n' followed by '\n').
|
||
*/
|
||
bool ReadMessages(int Fd, vector<string> &List)
|
||
{
|
||
char Buffer[64000];
|
||
// Represents any left-over from the previous iteration of the
|
||
// parse loop. (i.e., if a message is split across the end
|
||
// of the buffer, it goes here)
|
||
string PartialMessage;
|
||
|
||
do {
|
||
int const Res = read(Fd, Buffer, sizeof(Buffer));
|
||
if (Res < 0 && errno == EINTR)
|
||
continue;
|
||
|
||
// process we read from has died
|
||
if (Res == 0)
|
||
return false;
|
||
|
||
// No data
|
||
#if EAGAIN != EWOULDBLOCK
|
||
if (Res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
||
#else
|
||
if (Res < 0 && errno == EAGAIN)
|
||
#endif
|
||
return true;
|
||
if (Res < 0)
|
||
return false;
|
||
|
||
// extract the message(s) from the buffer
|
||
char const *Start = Buffer;
|
||
char const * const End = Buffer + Res;
|
||
|
||
char const * NL = (char const *) memchr(Start, '\n', End - Start);
|
||
if (NL == NULL)
|
||
{
|
||
// end of buffer: store what we have so far and read new data in
|
||
PartialMessage.append(Start, End - Start);
|
||
Start = End;
|
||
}
|
||
else
|
||
++NL;
|
||
|
||
if (PartialMessage.empty() == false && Start < End)
|
||
{
|
||
// if we start with a new line, see if the partial message we have ended with one
|
||
// so that we properly detect records ending between two read() runs
|
||
// cases are: \n|\n , \r\n|\r\n and \r\n\r|\n
|
||
// the case \r|\n\r\n is handled by the usual double-newline handling
|
||
if ((NL - Start) == 1 || ((NL - Start) == 2 && *Start == '\r'))
|
||
{
|
||
if (APT::String::Endswith(PartialMessage, "\n") || APT::String::Endswith(PartialMessage, "\r\n\r"))
|
||
{
|
||
PartialMessage.erase(PartialMessage.find_last_not_of("\r\n") + 1);
|
||
List.push_back(PartialMessage);
|
||
PartialMessage.clear();
|
||
while (NL < End && (*NL == '\n' || *NL == '\r')) ++NL;
|
||
Start = NL;
|
||
}
|
||
}
|
||
}
|
||
|
||
while (Start < End) {
|
||
char const * NL2 = (char const *) memchr(NL, '\n', End - NL);
|
||
if (NL2 == NULL)
|
||
{
|
||
// end of buffer: store what we have so far and read new data in
|
||
PartialMessage.append(Start, End - Start);
|
||
break;
|
||
}
|
||
++NL2;
|
||
|
||
// did we find a double newline?
|
||
if ((NL2 - NL) == 1 || ((NL2 - NL) == 2 && *NL == '\r'))
|
||
{
|
||
PartialMessage.append(Start, NL2 - Start);
|
||
PartialMessage.erase(PartialMessage.find_last_not_of("\r\n") + 1);
|
||
List.push_back(PartialMessage);
|
||
PartialMessage.clear();
|
||
while (NL2 < End && (*NL2 == '\n' || *NL2 == '\r')) ++NL2;
|
||
Start = NL2;
|
||
}
|
||
NL = NL2;
|
||
}
|
||
|
||
// we have read at least one complete message and nothing left
|
||
if (PartialMessage.empty() == true)
|
||
return true;
|
||
|
||
if (WaitFd(Fd) == false)
|
||
return false;
|
||
} while (true);
|
||
}
|
||
/*}}}*/
|
||
// MonthConv - Converts a month string into a number /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This was lifted from the boa webserver which lifted it from 'wn-v1.07'
|
||
Made it a bit more robust with a few tolower_ascii though. */
|
||
static int MonthConv(char const * const Month)
|
||
{
|
||
switch (tolower_ascii(*Month))
|
||
{
|
||
case 'a':
|
||
return tolower_ascii(Month[1]) == 'p'?3:7;
|
||
case 'd':
|
||
return 11;
|
||
case 'f':
|
||
return 1;
|
||
case 'j':
|
||
if (tolower_ascii(Month[1]) == 'a')
|
||
return 0;
|
||
return tolower_ascii(Month[2]) == 'n'?5:6;
|
||
case 'm':
|
||
return tolower_ascii(Month[2]) == 'r'?2:4;
|
||
case 'n':
|
||
return 10;
|
||
case 'o':
|
||
return 9;
|
||
case 's':
|
||
return 8;
|
||
|
||
// Pretend it is January..
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
/*}}}*/
|
||
// timegm - Internal timegm if the gnu version is not available /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* Converts struct tm to time_t, assuming the data in tm is UTC rather
|
||
than local timezone (mktime assumes the latter).
|
||
|
||
This function is a nonstandard GNU extension that is also present on
|
||
the BSDs and maybe other systems. For others we follow the advice of
|
||
the manpage of timegm and use his portable replacement. */
|
||
#ifndef HAVE_TIMEGM
|
||
static time_t timegm(struct tm *t)
|
||
{
|
||
char *tz = getenv("TZ");
|
||
setenv("TZ", "", 1);
|
||
tzset();
|
||
time_t ret = mktime(t);
|
||
if (tz)
|
||
setenv("TZ", tz, 1);
|
||
else
|
||
unsetenv("TZ");
|
||
tzset();
|
||
return ret;
|
||
}
|
||
#endif
|
||
/*}}}*/
|
||
// RFC1123StrToTime - Converts an HTTP1.1 full date strings into a time_t /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* tries to parses a full date as specified in RFC7231 §7.1.1.1
|
||
with one exception: HTTP/1.1 valid dates need to have GMT as timezone.
|
||
As we encounter dates from UTC or with a numeric timezone in other places,
|
||
we allow them here to to be able to reuse the method. Either way, a date
|
||
must be in UTC or parsing will fail. Previous implementations of this
|
||
method used to ignore the timezone and assume always UTC. */
|
||
bool RFC1123StrToTime(std::string const &str,time_t &time)
|
||
{
|
||
unsigned short day = 0;
|
||
signed int year = 0; // yes, Y23K problem – we going to worry then…
|
||
std::string weekday, month, datespec, timespec, zone;
|
||
std::istringstream ss(str);
|
||
auto const &posix = std::locale::classic();
|
||
ss.imbue(posix);
|
||
ss >> weekday;
|
||
// we only superficially check weekday, mostly to avoid accepting localized
|
||
// weekdays here and take only its length to decide which datetime format we
|
||
// encounter here. The date isn't stored.
|
||
std::transform(weekday.begin(), weekday.end(), weekday.begin(), ::tolower);
|
||
std::array<char const * const, 7> c_weekdays = {{ "sun", "mon", "tue", "wed", "thu", "fri", "sat" }};
|
||
if (std::find(c_weekdays.begin(), c_weekdays.end(), weekday.substr(0,3)) == c_weekdays.end())
|
||
return false;
|
||
|
||
switch (weekday.length())
|
||
{
|
||
case 4:
|
||
// Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
|
||
if (weekday[3] != ',')
|
||
return false;
|
||
ss >> day >> month >> year >> timespec >> zone;
|
||
break;
|
||
case 3:
|
||
// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||
ss >> month >> day >> timespec >> year;
|
||
zone = "UTC";
|
||
break;
|
||
case 0:
|
||
case 1:
|
||
case 2:
|
||
return false;
|
||
default:
|
||
// Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
|
||
if (weekday[weekday.length() - 1] != ',')
|
||
return false;
|
||
ss >> datespec >> timespec >> zone;
|
||
auto const expldate = VectorizeString(datespec, '-');
|
||
if (expldate.size() != 3)
|
||
return false;
|
||
try {
|
||
size_t pos;
|
||
day = std::stoi(expldate[0], &pos);
|
||
if (pos != expldate[0].length())
|
||
return false;
|
||
year = 1900 + std::stoi(expldate[2], &pos);
|
||
if (pos != expldate[2].length())
|
||
return false;
|
||
strprintf(datespec, "%.4d-%.2d-%.2d", year, MonthConv(expldate[1].c_str()) + 1, day);
|
||
} catch (...) {
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (ss.fail() || ss.bad() || !ss.eof())
|
||
return false;
|
||
|
||
if (zone != "GMT" && zone != "UTC" && zone != "Z") // RFC 822
|
||
{
|
||
// numeric timezones as a should of RFC 1123 and generally preferred
|
||
try {
|
||
size_t pos;
|
||
auto const z = std::stoi(zone, &pos);
|
||
if (z != 0 || pos != zone.length())
|
||
return false;
|
||
} catch (...) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (datespec.empty())
|
||
{
|
||
if (month.empty())
|
||
return false;
|
||
strprintf(datespec, "%.4d-%.2d-%.2d", year, MonthConv(month.c_str()) + 1, day);
|
||
}
|
||
|
||
std::string const datetime = datespec + ' ' + timespec;
|
||
struct tm Tm;
|
||
if (strptime(datetime.c_str(), "%Y-%m-%d %H:%M:%S", &Tm) == nullptr)
|
||
return false;
|
||
time = timegm(&Tm);
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// StrToNum - Convert a fixed length string to a number /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is used in decoding the crazy fixed length string headers in
|
||
tar and ar files. */
|
||
bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base)
|
||
{
|
||
unsigned long long BigRes;
|
||
if (not StrToNum(Str, BigRes, Len, Base))
|
||
return false;
|
||
|
||
if (std::numeric_limits<unsigned long>::max() < BigRes)
|
||
return false;
|
||
|
||
Res = BigRes;
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// StrToNum - Convert a fixed length string to a number /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is used in decoding the crazy fixed length string headers in
|
||
tar and ar files. */
|
||
bool StrToNum(const char *Str,unsigned long long &Res,unsigned Len,unsigned Base)
|
||
{
|
||
char S[30];
|
||
if (Len >= sizeof(S))
|
||
return false;
|
||
memcpy(S,Str,Len);
|
||
S[Len] = 0;
|
||
|
||
// All spaces is a zero
|
||
Res = 0;
|
||
unsigned I;
|
||
for (I = 0; S[I] == ' '; ++I);
|
||
if (S[I] == 0)
|
||
return true;
|
||
if (S[I] == '-')
|
||
return false;
|
||
|
||
char *End;
|
||
errno = 0;
|
||
Res = strtoull(S,&End,Base);
|
||
return not (End == S || errno != 0);
|
||
}
|
||
/*}}}*/
|
||
|
||
// Base256ToNum - Convert a fixed length binary to a number /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is used in decoding the 256bit encoded fixed length fields in
|
||
tar files */
|
||
bool Base256ToNum(const char *Str,unsigned long long &Res,unsigned int Len)
|
||
{
|
||
if ((Str[0] & 0x80) == 0)
|
||
return false;
|
||
else
|
||
{
|
||
Res = Str[0] & 0x7F;
|
||
for(unsigned int i = 1; i < Len; ++i)
|
||
Res = (Res<<8) + Str[i];
|
||
return true;
|
||
}
|
||
}
|
||
/*}}}*/
|
||
// Base256ToNum - Convert a fixed length binary to a number /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is used in decoding the 256bit encoded fixed length fields in
|
||
tar files */
|
||
bool Base256ToNum(const char *Str,unsigned long &Res,unsigned int Len)
|
||
{
|
||
unsigned long long Num = 0;
|
||
bool rc;
|
||
|
||
rc = Base256ToNum(Str, Num, Len);
|
||
// rudimentary check for overflow (Res = ulong, Num = ulonglong)
|
||
Res = Num;
|
||
if (Res != Num)
|
||
return false;
|
||
|
||
return rc;
|
||
}
|
||
/*}}}*/
|
||
// HexDigit - Convert a hex character into an integer /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* Helper for Hex2Num */
|
||
static int HexDigit(int c)
|
||
{
|
||
if (c >= '0' && c <= '9')
|
||
return c - '0';
|
||
if (c >= 'a' && c <= 'f')
|
||
return c - 'a' + 10;
|
||
if (c >= 'A' && c <= 'F')
|
||
return c - 'A' + 10;
|
||
return -1;
|
||
}
|
||
/*}}}*/
|
||
// Hex2Num - Convert a long hex number into a buffer /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* The length of the buffer must be exactly 1/2 the length of the string. */
|
||
bool Hex2Num(const std::string_view Str,unsigned char *Num,unsigned int Length)
|
||
{
|
||
if (Str.length() != Length*2)
|
||
return false;
|
||
|
||
// Convert each digit. We store it in the same order as the string
|
||
int J = 0;
|
||
for (auto I = Str.begin(); I != Str.end();J++, I += 2)
|
||
{
|
||
int first_half = HexDigit(I[0]);
|
||
int second_half;
|
||
if (first_half < 0)
|
||
return false;
|
||
|
||
second_half = HexDigit(I[1]);
|
||
if (second_half < 0)
|
||
return false;
|
||
Num[J] = first_half << 4;
|
||
Num[J] += second_half;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// TokSplitString - Split a string up by a given token /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is intended to be a faster splitter, it does not use dynamic
|
||
memories. Input is changed to insert nulls at each token location. */
|
||
bool TokSplitString(char Tok,char *Input,char **List,
|
||
unsigned long ListMax)
|
||
{
|
||
// Strip any leading spaces
|
||
char *Start = Input;
|
||
char *Stop = Start + strlen(Start);
|
||
for (; *Start != 0 && isspace(*Start) != 0; Start++);
|
||
|
||
unsigned long Count = 0;
|
||
char *Pos = Start;
|
||
while (Pos != Stop)
|
||
{
|
||
// Skip to the next Token
|
||
for (; Pos != Stop && *Pos != Tok; Pos++);
|
||
|
||
// Back remove spaces
|
||
char *End = Pos;
|
||
for (; End > Start && (End[-1] == Tok || isspace(End[-1]) != 0); End--);
|
||
*End = 0;
|
||
|
||
List[Count++] = Start;
|
||
if (Count >= ListMax)
|
||
{
|
||
List[Count-1] = 0;
|
||
return false;
|
||
}
|
||
|
||
// Advance pos
|
||
for (; Pos != Stop && (*Pos == Tok || isspace(*Pos) != 0 || *Pos == 0); Pos++);
|
||
Start = Pos;
|
||
}
|
||
|
||
List[Count] = 0;
|
||
return true;
|
||
}
|
||
/*}}}*/
|
||
// VectorizeString - Split a string up into a vector of strings /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This can be used to split a given string up into a vector, so the
|
||
propose is the same as in the method above and this one is a bit slower
|
||
also, but the advantage is that we have an iteratable vector */
|
||
vector<string> VectorizeString(string_view const &haystack, char const &split)
|
||
{
|
||
vector<string> exploded;
|
||
if (haystack.empty() == true)
|
||
return exploded;
|
||
auto start = haystack.begin();
|
||
auto end = start;
|
||
do {
|
||
for (; end != haystack.end() && *end != split; ++end);
|
||
exploded.push_back(string(start, end));
|
||
start = end + 1;
|
||
} while (end != haystack.end() && (++end) != haystack.end());
|
||
return exploded;
|
||
}
|
||
/*}}}*/
|
||
// StringSplit - split a string into a string vector by token /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* See header for details.
|
||
*/
|
||
vector<string> StringSplit(std::string_view const &s, std::string_view const &sep,
|
||
unsigned int maxsplit)
|
||
{
|
||
vector<string> split;
|
||
size_t start, pos;
|
||
|
||
// no separator given, this is bogus
|
||
if(sep.size() == 0)
|
||
return split;
|
||
|
||
start = pos = 0;
|
||
while (pos != string::npos)
|
||
{
|
||
pos = s.find(sep, start);
|
||
split.emplace_back(s.substr(start, pos-start));
|
||
|
||
// if maxsplit is reached, the remaining string is the last item
|
||
if(split.size() >= maxsplit)
|
||
{
|
||
split[split.size()-1] = s.substr(start);
|
||
break;
|
||
}
|
||
start = pos+sep.size();
|
||
}
|
||
return split;
|
||
}
|
||
/*}}}*/
|
||
// RegexChoice - Simple regex list/list matcher /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* */
|
||
unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin,
|
||
const char **ListEnd)
|
||
{
|
||
for (RxChoiceList *R = Rxs; R->Str != 0; R++)
|
||
R->Hit = false;
|
||
|
||
unsigned long Hits = 0;
|
||
for (; ListBegin < ListEnd; ++ListBegin)
|
||
{
|
||
// Check if the name is a regex
|
||
const char *I;
|
||
bool Regex = true;
|
||
for (I = *ListBegin; *I != 0; I++)
|
||
if (*I == '.' || *I == '?' || *I == '*' || *I == '|')
|
||
break;
|
||
if (*I == 0)
|
||
Regex = false;
|
||
|
||
// Compile the regex pattern
|
||
regex_t Pattern;
|
||
if (Regex == true)
|
||
if (regcomp(&Pattern,*ListBegin,REG_EXTENDED | REG_ICASE |
|
||
REG_NOSUB) != 0)
|
||
Regex = false;
|
||
|
||
// Search the list
|
||
bool Done = false;
|
||
for (RxChoiceList *R = Rxs; R->Str != 0; R++)
|
||
{
|
||
if (R->Str[0] == 0)
|
||
continue;
|
||
|
||
if (strcasecmp(R->Str,*ListBegin) != 0)
|
||
{
|
||
if (Regex == false)
|
||
continue;
|
||
if (regexec(&Pattern,R->Str,0,0,0) != 0)
|
||
continue;
|
||
}
|
||
Done = true;
|
||
|
||
if (R->Hit == false)
|
||
Hits++;
|
||
|
||
R->Hit = true;
|
||
}
|
||
|
||
if (Regex == true)
|
||
regfree(&Pattern);
|
||
|
||
if (Done == false)
|
||
_error->Warning(_("Selection %s not found"),*ListBegin);
|
||
}
|
||
|
||
return Hits;
|
||
}
|
||
/*}}}*/
|
||
// {str,io}printf - C format string outputter to C++ strings/iostreams /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is used to make the internationalization strings easier to translate
|
||
and to allow reordering of parameters */
|
||
bool iovprintf(std::ostream &out, const char *format,
|
||
va_list &args, ssize_t &size) {
|
||
auto S = std::unique_ptr<char,FreeDeleter>{static_cast<char*>(malloc(size))};
|
||
ssize_t const n = vsnprintf(S.get(), size, format, args);
|
||
if (n > -1 && n < size) {
|
||
out << S.get();
|
||
return true;
|
||
} else {
|
||
if (n > -1)
|
||
size = n + 1;
|
||
else
|
||
size *= 2;
|
||
}
|
||
return false;
|
||
}
|
||
void ioprintf(ostream &out,const char *format,...)
|
||
{
|
||
va_list args;
|
||
ssize_t size = 400;
|
||
while (true) {
|
||
bool ret;
|
||
va_start(args,format);
|
||
ret = iovprintf(out, format, args, size);
|
||
va_end(args);
|
||
if (ret == true)
|
||
return;
|
||
}
|
||
}
|
||
void strprintf(string &out,const char *format,...)
|
||
{
|
||
va_list args;
|
||
ssize_t size = 400;
|
||
std::ostringstream outstr;
|
||
while (true) {
|
||
bool ret;
|
||
va_start(args,format);
|
||
ret = iovprintf(outstr, format, args, size);
|
||
va_end(args);
|
||
if (ret == true)
|
||
break;
|
||
}
|
||
out = outstr.str();
|
||
}
|
||
/*}}}*/
|
||
// safe_snprintf - Safer snprintf /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This is a snprintf that will never (ever) go past 'End' and returns a
|
||
pointer to the end of the new string. The returned string is always null
|
||
terminated unless Buffer == end. This is a better alterantive to using
|
||
consecutive snprintfs. */
|
||
char *safe_snprintf(char *Buffer,char *End,const char *Format,...)
|
||
{
|
||
va_list args;
|
||
int Did;
|
||
|
||
if (End <= Buffer)
|
||
return End;
|
||
va_start(args,Format);
|
||
Did = vsnprintf(Buffer,End - Buffer,Format,args);
|
||
va_end(args);
|
||
|
||
if (Did < 0 || Buffer + Did > End)
|
||
return End;
|
||
return Buffer + Did;
|
||
}
|
||
/*}}}*/
|
||
// StripEpoch - Remove the version "epoch" from a version string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
string StripEpoch(const string &VerStr)
|
||
{
|
||
size_t i = VerStr.find(":");
|
||
if (i == string::npos)
|
||
return VerStr;
|
||
return VerStr.substr(i+1);
|
||
}
|
||
/*}}}*/
|
||
|
||
// tolower_ascii - tolower() function that ignores the locale /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This little function is the most called method we have and tries
|
||
therefore to do the absolute minimum - and is notable faster than
|
||
standard tolower/toupper and as a bonus avoids problems with different
|
||
locales - we only operate on ascii chars anyway. */
|
||
#undef tolower_ascii
|
||
int tolower_ascii(int const c) APT_PURE APT_COLD;
|
||
int tolower_ascii(int const c)
|
||
{
|
||
return tolower_ascii_inline(c);
|
||
}
|
||
/*}}}*/
|
||
|
||
// isspace_ascii - isspace() function that ignores the locale /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This little function is one of the most called methods we have and tries
|
||
therefore to do the absolute minimum - and is notable faster than
|
||
standard isspace() and as a bonus avoids problems with different
|
||
locales - we only operate on ascii chars anyway. */
|
||
#undef isspace_ascii
|
||
int isspace_ascii(int const c) APT_PURE APT_COLD;
|
||
int isspace_ascii(int const c)
|
||
{
|
||
return isspace_ascii_inline(c);
|
||
}
|
||
/*}}}*/
|
||
|
||
// CheckDomainList - See if Host is in a , separate list /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* The domain list is a comma separate list of domains that are suffix
|
||
matched against the argument */
|
||
bool CheckDomainList(const string &Host,const string &List)
|
||
{
|
||
string::const_iterator Start = List.begin();
|
||
for (string::const_iterator Cur = List.begin(); Cur <= List.end(); ++Cur)
|
||
{
|
||
if (Cur < List.end() && *Cur != ',')
|
||
continue;
|
||
|
||
// Match the end of the string..
|
||
if ((Host.size() >= (unsigned)(Cur - Start)) &&
|
||
Cur - Start != 0 &&
|
||
stringcasecmp(Host.end() - (Cur - Start),Host.end(),Start,Cur) == 0)
|
||
return true;
|
||
|
||
Start = Cur + 1;
|
||
}
|
||
return false;
|
||
}
|
||
/*}}}*/
|
||
// strv_length - Return the length of a NULL-terminated string array /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* */
|
||
size_t strv_length(const char **str_array)
|
||
{
|
||
size_t i;
|
||
for (i=0; str_array[i] != NULL; i++)
|
||
/* nothing */
|
||
;
|
||
return i;
|
||
}
|
||
/*}}}*/
|
||
// DeEscapeString - unescape (\0XX and \xXX) from a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* */
|
||
string DeEscapeString(const string &input)
|
||
{
|
||
char tmp[3];
|
||
string::const_iterator it;
|
||
string output;
|
||
for (it = input.begin(); it != input.end(); ++it)
|
||
{
|
||
// just copy non-escape chars
|
||
if (*it != '\\')
|
||
{
|
||
output += *it;
|
||
continue;
|
||
}
|
||
|
||
// deal with double escape
|
||
if (*it == '\\' &&
|
||
(it + 1 < input.end()) && it[1] == '\\')
|
||
{
|
||
// copy
|
||
output += *it;
|
||
// advance iterator one step further
|
||
++it;
|
||
continue;
|
||
}
|
||
|
||
// ensure we have a char to read
|
||
if (it + 1 == input.end())
|
||
continue;
|
||
|
||
// read it
|
||
++it;
|
||
switch (*it)
|
||
{
|
||
case '0':
|
||
if (it + 2 < input.end()) {
|
||
tmp[0] = it[1];
|
||
tmp[1] = it[2];
|
||
tmp[2] = 0;
|
||
output += (char)strtol(tmp, 0, 8);
|
||
it += 2;
|
||
} else {
|
||
// FIXME: raise exception here?
|
||
}
|
||
break;
|
||
case 'x':
|
||
if (it + 2 < input.end()) {
|
||
tmp[0] = it[1];
|
||
tmp[1] = it[2];
|
||
tmp[2] = 0;
|
||
output += (char)strtol(tmp, 0, 16);
|
||
it += 2;
|
||
} else {
|
||
// FIXME: raise exception here?
|
||
}
|
||
break;
|
||
default:
|
||
// FIXME: raise exception here?
|
||
break;
|
||
}
|
||
}
|
||
return output;
|
||
}
|
||
/*}}}*/
|
||
// URI::CopyFrom - Copy from an object /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* This parses the URI into all of its components */
|
||
void URI::CopyFrom(const string &U)
|
||
{
|
||
string::const_iterator I = U.begin();
|
||
|
||
// Locate the first colon, this separates the scheme
|
||
for (; I < U.end() && *I != ':' ; ++I);
|
||
string::const_iterator FirstColon = I;
|
||
|
||
/* Determine if this is a host type URI with a leading double //
|
||
and then search for the first single / */
|
||
string::const_iterator SingleSlash = I;
|
||
if (I + 3 < U.end() && I[1] == '/' && I[2] == '/')
|
||
SingleSlash += 3;
|
||
|
||
/* Find the / indicating the end of the hostname, ignoring /'s in the
|
||
square brackets */
|
||
bool InBracket = false;
|
||
for (; SingleSlash < U.end() && (*SingleSlash != '/' || InBracket == true); ++SingleSlash)
|
||
{
|
||
if (*SingleSlash == '[')
|
||
InBracket = true;
|
||
if (InBracket == true && *SingleSlash == ']')
|
||
InBracket = false;
|
||
}
|
||
|
||
if (SingleSlash > U.end())
|
||
SingleSlash = U.end();
|
||
|
||
// We can now write the access and path specifiers
|
||
Access.assign(U.begin(),FirstColon);
|
||
if (SingleSlash != U.end())
|
||
Path.assign(SingleSlash,U.end());
|
||
if (Path.empty() == true)
|
||
Path = "/";
|
||
|
||
// Now we attempt to locate a user:pass@host fragment
|
||
if (FirstColon + 2 <= U.end() && FirstColon[1] == '/' && FirstColon[2] == '/')
|
||
FirstColon += 3;
|
||
else
|
||
FirstColon += 1;
|
||
if (FirstColon >= U.end())
|
||
return;
|
||
|
||
if (FirstColon > SingleSlash)
|
||
FirstColon = SingleSlash;
|
||
|
||
// Find the colon...
|
||
I = FirstColon + 1;
|
||
if (I > SingleSlash)
|
||
I = SingleSlash;
|
||
|
||
// Search for the @ separating user:pass from host
|
||
auto const RevAt = std::find(
|
||
std::string::const_reverse_iterator(SingleSlash),
|
||
std::string::const_reverse_iterator(I), '@');
|
||
string::const_iterator const At = RevAt.base() == I ? SingleSlash : std::prev(RevAt.base());
|
||
// and then look for the colon between user and pass
|
||
string::const_iterator const SecondColon = std::find(I, At, ':');
|
||
|
||
// Now write the host and user/pass
|
||
if (At == SingleSlash)
|
||
{
|
||
if (FirstColon < SingleSlash)
|
||
Host.assign(FirstColon,SingleSlash);
|
||
}
|
||
else
|
||
{
|
||
Host.assign(At+1,SingleSlash);
|
||
// username and password must be encoded (RFC 3986)
|
||
User.assign(DeQuoteString(FirstColon,SecondColon));
|
||
if (SecondColon < At)
|
||
Password.assign(DeQuoteString(SecondColon+1,At));
|
||
}
|
||
|
||
// Now we parse the RFC 2732 [] hostnames.
|
||
unsigned long PortEnd = 0;
|
||
InBracket = false;
|
||
for (unsigned I = 0; I != Host.length();)
|
||
{
|
||
if (Host[I] == '[')
|
||
{
|
||
InBracket = true;
|
||
Host.erase(I,1);
|
||
continue;
|
||
}
|
||
|
||
if (InBracket == true && Host[I] == ']')
|
||
{
|
||
InBracket = false;
|
||
Host.erase(I,1);
|
||
PortEnd = I;
|
||
continue;
|
||
}
|
||
I++;
|
||
}
|
||
|
||
// Tsk, weird.
|
||
if (InBracket == true)
|
||
{
|
||
Host.clear();
|
||
return;
|
||
}
|
||
|
||
// Now we parse off a port number from the hostname
|
||
Port = 0;
|
||
string::size_type Pos = Host.rfind(':');
|
||
if (Pos == string::npos || Pos < PortEnd)
|
||
return;
|
||
|
||
Port = atoi(string(Host,Pos+1).c_str());
|
||
Host.assign(Host,0,Pos);
|
||
}
|
||
/*}}}*/
|
||
// URI::operator string - Convert the URI to a string /*{{{*/
|
||
// ---------------------------------------------------------------------
|
||
/* */
|
||
URI::operator string()
|
||
{
|
||
std::stringstream Res;
|
||
|
||
if (Access.empty() == false)
|
||
Res << Access << ':';
|
||
|
||
if (Host.empty() == false)
|
||
{
|
||
if (Access.empty() == false)
|
||
Res << "//";
|
||
|
||
if (User.empty() == false)
|
||
{
|
||
// FIXME: Technically userinfo is permitted even less
|
||
// characters than these, but this is not conveniently
|
||
// expressed with a denylist.
|
||
Res << QuoteString(User, ":/?#[]@");
|
||
if (Password.empty() == false)
|
||
Res << ":" << QuoteString(Password, ":/?#[]@");
|
||
Res << "@";
|
||
}
|
||
|
||
// Add RFC 2732 escaping characters
|
||
if (Access.empty() == false && Host.find_first_of("/:") != string::npos)
|
||
Res << '[' << Host << ']';
|
||
else
|
||
Res << Host;
|
||
|
||
if (Port != 0)
|
||
Res << ':' << std::to_string(Port);
|
||
}
|
||
|
||
if (Path.empty() == false)
|
||
{
|
||
if (Path[0] != '/')
|
||
Res << "/" << Path;
|
||
else
|
||
Res << Path;
|
||
}
|
||
|
||
return Res.str();
|
||
}
|
||
/*}}}*/
|
||
// URI::SiteOnly - Return the schema and site for the URI /*{{{*/
|
||
string URI::SiteOnly(const string &URI)
|
||
{
|
||
::URI U(URI);
|
||
U.User.clear();
|
||
U.Password.clear();
|
||
U.Path.clear();
|
||
return U;
|
||
}
|
||
/*}}}*/
|
||
// URI::ArchiveOnly - Return the schema, site and cleaned path for the URI /*{{{*/
|
||
string URI::ArchiveOnly(const string &URI)
|
||
{
|
||
::URI U(URI);
|
||
U.User.clear();
|
||
U.Password.clear();
|
||
if (U.Path.empty() == false && U.Path[U.Path.length() - 1] == '/')
|
||
U.Path.erase(U.Path.length() - 1);
|
||
return U;
|
||
}
|
||
/*}}}*/
|
||
// URI::NoUserPassword - Return the schema, site and path for the URI /*{{{*/
|
||
string URI::NoUserPassword(const string &URI)
|
||
{
|
||
::URI U(URI);
|
||
U.User.clear();
|
||
U.Password.clear();
|
||
return U;
|
||
}
|
||
/*}}}*/
|