While taking an EnVar as the parameter is more clear it has the disadvantage, that passing anything outside of the range of the enumeration is undefined behavior. The compiler could then optimize the range check in getEnv away (perfectly legal due to UB), leading to buffer overreads.
457 lines
15 KiB
C++
457 lines
15 KiB
C++
// ***************************************************************** -*- C++ -*-
|
|
/*
|
|
* Copyright (C) 2004-2017 Andreas Huggel <ahuggel@gmx.net>
|
|
*
|
|
* This program is part of the Exiv2 distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
/*
|
|
File: futils.cpp
|
|
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
|
History: 08-Dec-03, ahu: created
|
|
02-Apr-05, ahu: moved to Exiv2 namespace
|
|
*/
|
|
// *****************************************************************************
|
|
// included header files
|
|
#include "config.h"
|
|
|
|
#include "futils.hpp"
|
|
#include "enforce.hpp"
|
|
|
|
// + standard includes
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifdef _MSC_VER
|
|
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
|
#endif
|
|
#ifdef EXV_HAVE_UNISTD_H
|
|
# include <unistd.h> // for stat()
|
|
#endif
|
|
|
|
#include <cerrno>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
|
|
#ifdef EXV_HAVE_STRERROR_R
|
|
#ifdef _GNU_SOURCE
|
|
extern char *strerror_r(int errnum, char *buf, size_t n);
|
|
#else
|
|
extern int strerror_r(int errnum, char *buf, size_t n);
|
|
#endif
|
|
#endif
|
|
|
|
namespace Exiv2 {
|
|
const char* ENVARDEF[] = {"/exiv2.php", "40"}; //!< @brief default URL for http exiv2 handler and time-out
|
|
const char* ENVARKEY[] = {"EXIV2_HTTP_POST", "EXIV2_TIMEOUT"}; //!< @brief request keys for http exiv2 handler and time-out
|
|
// *****************************************************************************
|
|
// free functions
|
|
std::string getEnv(int env_var)
|
|
{
|
|
// this check is relying on undefined behavior and might not be effective
|
|
if (env_var < envHTTPPOST || env_var > envTIMEOUT) {
|
|
throw std::out_of_range("Unexpected env variable");
|
|
}
|
|
return getenv(ENVARKEY[env_var]) ? getenv(ENVARKEY[env_var]) : ENVARDEF[env_var];
|
|
}
|
|
|
|
/// @brief Convert an integer value to its hex character.
|
|
char to_hex(char code) {
|
|
static char hex[] = "0123456789abcdef";
|
|
return hex[code & 15];
|
|
}
|
|
|
|
/// @brief Convert a hex character to its integer value.
|
|
char from_hex(char ch) {
|
|
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
|
|
}
|
|
|
|
std::string urlencode(const char* str) {
|
|
const char* pstr = str;
|
|
// \todo try to use std::string for buf and avoid the creation of another string for just
|
|
// returning the final value
|
|
char* buf = new char[strlen(str) * 3 + 1];
|
|
char* pbuf = buf;
|
|
while (*pstr) {
|
|
if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
|
|
*pbuf++ = *pstr;
|
|
else if (*pstr == ' ')
|
|
*pbuf++ = '+';
|
|
else
|
|
*pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
|
|
pstr++;
|
|
}
|
|
*pbuf = '\0';
|
|
std::string ret(buf);
|
|
delete [] buf;
|
|
return ret;
|
|
}
|
|
|
|
char* urldecode(const char* str) {
|
|
const char* pstr = str;
|
|
char* buf = new char [(strlen(str) + 1)];
|
|
char* pbuf = buf;
|
|
while (*pstr) {
|
|
if (*pstr == '%') {
|
|
if (pstr[1] && pstr[2]) {
|
|
*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
|
|
pstr += 2;
|
|
}
|
|
} else if (*pstr == '+') {
|
|
*pbuf++ = ' ';
|
|
} else {
|
|
*pbuf++ = *pstr;
|
|
}
|
|
pstr++;
|
|
}
|
|
*pbuf = '\0';
|
|
return buf;
|
|
}
|
|
|
|
void urldecode(std::string& str) {
|
|
char* decodeStr = Exiv2::urldecode(str.c_str());
|
|
str = std::string(decodeStr);
|
|
delete [] decodeStr;
|
|
}
|
|
|
|
int base64encode(const void* data_buf, size_t dataLength, char* result, size_t resultSize) {
|
|
const char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
const uint8_t* data = (const uint8_t*)data_buf;
|
|
size_t resultIndex = 0;
|
|
size_t x;
|
|
uint32_t n = 0;
|
|
size_t padCount = dataLength % 3;
|
|
uint8_t n0, n1, n2, n3;
|
|
|
|
/* increment over the length of the string, three characters at a time */
|
|
for (x = 0; x < dataLength; x += 3)
|
|
{
|
|
/* these three 8-bit (ASCII) characters become one 24-bit number */
|
|
n = data[x] << 16;
|
|
|
|
if((x+1) < dataLength)
|
|
n += data[x+1] << 8;
|
|
|
|
if((x+2) < dataLength)
|
|
n += data[x+2];
|
|
|
|
/* this 24-bit number gets separated into four 6-bit numbers */
|
|
n0 = (uint8_t)(n >> 18) & 63;
|
|
n1 = (uint8_t)(n >> 12) & 63;
|
|
n2 = (uint8_t)(n >> 6) & 63;
|
|
n3 = (uint8_t)n & 63;
|
|
|
|
/*
|
|
* if we have one byte available, then its encoding is spread
|
|
* out over two characters
|
|
*/
|
|
if(resultIndex >= resultSize) return 0; /* indicate failure: buffer too small */
|
|
result[resultIndex++] = base64chars[n0];
|
|
if(resultIndex >= resultSize) return 0; /* indicate failure: buffer too small */
|
|
result[resultIndex++] = base64chars[n1];
|
|
|
|
/*
|
|
* if we have only two bytes available, then their encoding is
|
|
* spread out over three chars
|
|
*/
|
|
if((x+1) < dataLength)
|
|
{
|
|
if(resultIndex >= resultSize) return 0; /* indicate failure: buffer too small */
|
|
result[resultIndex++] = base64chars[n2];
|
|
}
|
|
|
|
/*
|
|
* if we have all three bytes available, then their encoding is spread
|
|
* out over four characters
|
|
*/
|
|
if((x+2) < dataLength)
|
|
{
|
|
if(resultIndex >= resultSize) return 0; /* indicate failure: buffer too small */
|
|
result[resultIndex++] = base64chars[n3];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* create and add padding that is required if we did not have a multiple of 3
|
|
* number of characters available
|
|
*/
|
|
if (padCount > 0)
|
|
{
|
|
for (; padCount < 3; padCount++)
|
|
{
|
|
if(resultIndex >= resultSize) return 0; /* indicate failure: buffer too small */
|
|
result[resultIndex++] = '=';
|
|
}
|
|
}
|
|
if(resultIndex >= resultSize) return 0; /* indicate failure: buffer too small */
|
|
result[resultIndex] = 0;
|
|
return 1; /* indicate success */
|
|
} // base64encode
|
|
|
|
long base64decode(const char *in, char *out, size_t out_size) {
|
|
static const char decode[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW"
|
|
"$$$$$$XYZ[\\]^_`abcdefghijklmnopq";
|
|
long len;
|
|
long i;
|
|
long done = 0;
|
|
unsigned char v;
|
|
unsigned char quad[4];
|
|
|
|
while (*in) {
|
|
len = 0;
|
|
for (i = 0; i < 4 && *in; i++) {
|
|
v = 0;
|
|
while (*in && !v) {
|
|
v = *in++;
|
|
v = (v < 43 || v > 122) ? 0 : decode[v - 43];
|
|
if (v)
|
|
v = (v == '$') ? 0 : v - 61;
|
|
if (*in) {
|
|
len++;
|
|
if (v)
|
|
quad[i] = v - 1;
|
|
} else
|
|
quad[i] = 0;
|
|
}
|
|
}
|
|
if (!len)
|
|
continue;
|
|
if (out_size < (size_t) (done + len - 1))
|
|
/* out buffer is too small */
|
|
return -1;
|
|
if (len >= 2)
|
|
*out++ = quad[0] << 2 | quad[1] >> 4;
|
|
if (len >= 3)
|
|
*out++ = quad[1] << 4 | quad[2] >> 2;
|
|
if (len >= 4)
|
|
*out++ = ((quad[2] << 6) & 0xc0) | quad[3];
|
|
done += len - 1;
|
|
}
|
|
if ((size_t)(done + 1) >= out_size)
|
|
return -1;
|
|
*out++ = '\0';
|
|
return done;
|
|
} // base64decode
|
|
|
|
Protocol fileProtocol(const std::string& path) {
|
|
Protocol result = pFile ;
|
|
struct {
|
|
std::string name ;
|
|
Protocol prot ;
|
|
} prots[] =
|
|
{ { "http://" ,pHttp }
|
|
, { "https://" ,pHttps }
|
|
, { "ftp://" ,pFtp }
|
|
, { "sftp://" ,pSftp }
|
|
, { "ssh://" ,pSsh }
|
|
, { "file://" ,pFileUri }
|
|
, { "data://" ,pDataUri }
|
|
, { "-" ,pStdin }
|
|
};
|
|
for ( size_t i = 0 ; result == pFile && i < sizeof(prots)/sizeof(prots[0]) ; i ++ )
|
|
if ( path.find(prots[i].name) == 0 )
|
|
result = prots[i].prot;
|
|
|
|
return result;
|
|
} // fileProtocol
|
|
#ifdef EXV_UNICODE_PATH
|
|
Protocol fileProtocol(const std::wstring& wpath) {
|
|
Protocol result = pFile ;
|
|
struct {
|
|
std::wstring wname ;
|
|
Protocol prot ;
|
|
} prots[] =
|
|
{ { L"http://" ,pHttp }
|
|
, { L"https://" ,pHttps }
|
|
, { L"ftp://" ,pFtp }
|
|
, { L"sftp://" ,pSftp }
|
|
, { L"ssh://" ,pSsh }
|
|
, { L"file://" ,pFileUri }
|
|
, { L"data://" ,pDataUri }
|
|
, { L"-" ,pStdin }
|
|
};
|
|
for ( size_t i = 0 ; result == pFile && i < sizeof(prots)/sizeof(prots[0]) ; i ++ )
|
|
if ( wpath.find(prots[i].wname) == 0 )
|
|
result = prots[i].prot;
|
|
|
|
return result;
|
|
} // fileProtocol
|
|
#endif
|
|
bool fileExists(const std::string& path, bool ct)
|
|
{
|
|
// special case: accept "-" (means stdin)
|
|
if (path.compare("-") == 0 || fileProtocol(path) != pFile) {
|
|
return true;
|
|
}
|
|
|
|
struct stat buf;
|
|
int ret = ::stat(path.c_str(), &buf);
|
|
if (0 != ret) return false;
|
|
if (ct && !S_ISREG(buf.st_mode)) return false;
|
|
return true;
|
|
} // fileExists
|
|
|
|
#ifdef EXV_UNICODE_PATH
|
|
bool fileExists(const std::wstring& wpath, bool ct)
|
|
{
|
|
// special case: accept "-" (means stdin)
|
|
if (wpath.compare(L"-") == 0 || fileProtocol(wpath) != pFile) {
|
|
return true;
|
|
}
|
|
|
|
struct _stat buf;
|
|
int ret = _wstat(wpath.c_str(), &buf);
|
|
if (0 != ret) return false;
|
|
if (ct && !S_ISREG(buf.st_mode)) return false;
|
|
return true;
|
|
} // fileExists
|
|
|
|
#endif
|
|
std::string pathOfFileUrl(const std::string& url) {
|
|
std::string path = url.substr(7);
|
|
size_t found = path.find('/');
|
|
if (found == std::string::npos) return path;
|
|
else return path.substr(found);
|
|
}
|
|
#ifdef EXV_UNICODE_PATH
|
|
std::wstring pathOfFileUrl(const std::wstring& wurl) {
|
|
std::wstring path = wurl.substr(7);
|
|
size_t found = path.find('/');
|
|
if (found == std::wstring::npos) return path;
|
|
else return path.substr(found);
|
|
}
|
|
#endif
|
|
|
|
std::string strError()
|
|
{
|
|
int error = errno;
|
|
std::ostringstream os;
|
|
#ifdef EXV_HAVE_STRERROR_R
|
|
const size_t n = 1024;
|
|
#ifdef _GNU_SOURCE
|
|
char *buf = 0;
|
|
char buf2[n];
|
|
std::memset(buf2, 0x0, n);
|
|
buf = strerror_r(error, buf2, n);
|
|
#else
|
|
char buf[n];
|
|
std::memset(buf, 0x0, n);
|
|
const int ret = strerror_r(error, buf, n);
|
|
enforce(ret != ERANGE, Exiv2::kerCallFailed);
|
|
#endif
|
|
os << buf;
|
|
// Issue# 908.
|
|
// report strerror() if strerror_r() returns empty
|
|
if ( !buf[0] ) {
|
|
os << strerror(error);
|
|
}
|
|
#else
|
|
os << std::strerror(error);
|
|
#endif
|
|
os << " (errno = " << error << ")";
|
|
return os.str();
|
|
} // strError
|
|
|
|
void Uri::Decode(Uri& uri)
|
|
{
|
|
urldecode(uri.QueryString);
|
|
urldecode(uri.Path);
|
|
urldecode(uri.Host);
|
|
urldecode(uri.Username);
|
|
urldecode(uri.Password);
|
|
}
|
|
|
|
Uri Uri::Parse(const std::string &uri)
|
|
{
|
|
Uri result;
|
|
|
|
typedef std::string::const_iterator iterator_t;
|
|
|
|
if ( !uri.length() ) return result;
|
|
|
|
iterator_t uriEnd = uri.end();
|
|
|
|
// get query start
|
|
iterator_t queryStart = std::find(uri.begin(), uriEnd, '?');
|
|
|
|
// protocol
|
|
iterator_t protocolStart = uri.begin();
|
|
iterator_t protocolEnd = std::find(protocolStart, uriEnd, ':'); //"://");
|
|
|
|
if (protocolEnd != uriEnd)
|
|
{
|
|
std::string prot = &*(protocolEnd);
|
|
if ((prot.length() > 3) && (prot.substr(0, 3) == "://"))
|
|
{
|
|
result.Protocol = std::string(protocolStart, protocolEnd);
|
|
protocolEnd += 3; // ://
|
|
}
|
|
else
|
|
protocolEnd = uri.begin(); // no protocol
|
|
}
|
|
else
|
|
protocolEnd = uri.begin(); // no protocol
|
|
|
|
//username & password
|
|
iterator_t authStart = protocolEnd;
|
|
iterator_t authEnd = std::find(protocolEnd, uriEnd, '@');
|
|
if (authEnd != uriEnd) {
|
|
iterator_t userStart = authStart;
|
|
iterator_t userEnd = std::find(authStart, authEnd, ':');
|
|
if (userEnd != authEnd) {
|
|
result.Username = std::string(userStart, userEnd);
|
|
++userEnd;
|
|
result.Password = std::string(userEnd, authEnd);
|
|
} else {
|
|
result.Username = std::string(authStart, authEnd);
|
|
}
|
|
++authEnd;
|
|
} else {
|
|
authEnd = protocolEnd;
|
|
}
|
|
|
|
// host
|
|
iterator_t hostStart = authEnd;
|
|
iterator_t pathStart = std::find(hostStart, uriEnd, '/'); // get pathStart
|
|
|
|
iterator_t hostEnd = std::find(authEnd,
|
|
(pathStart != uriEnd) ? pathStart : queryStart,
|
|
':'); // check for port
|
|
|
|
result.Host = std::string(hostStart, hostEnd);
|
|
|
|
// port
|
|
if ((hostEnd != uriEnd) && ((&*(hostEnd))[0] == ':')) // we have a port
|
|
{
|
|
++hostEnd;
|
|
iterator_t portEnd = (pathStart != uriEnd) ? pathStart : queryStart;
|
|
result.Port = std::string(hostEnd, portEnd);
|
|
}
|
|
if ( !result.Port.length() && result.Protocol == "http" ) result.Port = "80";
|
|
|
|
// path
|
|
if (pathStart != uriEnd)
|
|
result.Path = std::string(pathStart, queryStart);
|
|
|
|
// query
|
|
if (queryStart != uriEnd)
|
|
result.QueryString = std::string(queryStart, uri.end());
|
|
|
|
return result;
|
|
} // Uri::Parse
|
|
} // namespace Exiv2
|