mirror of
https://github.com/bkaradzic/bx.git
synced 2026-10-22 08:41:00 +00:00
Added ability to move file into trash. (#418)
This commit is contained in:
committed by
GitHub
parent
9e3fadf6f1
commit
9f874936e2
@@ -138,6 +138,18 @@ namespace bx
|
||||
///
|
||||
bool removeAll(const FilePath& _filePath, Error* _err = ErrorIgnore{});
|
||||
|
||||
/// Copies file contents.
|
||||
///
|
||||
bool copy(const FilePath& _from, const FilePath& _to, Error* _err = ErrorIgnore{});
|
||||
|
||||
/// Moves file or directory.
|
||||
///
|
||||
bool move(const FilePath& _from, const FilePath& _to, Error* _err = ErrorIgnore{});
|
||||
|
||||
/// Moves file or directory into OS trash (Recycle Bin).
|
||||
///
|
||||
bool moveToTrash(const FilePath& _filePath, Error* _err = ErrorIgnore{});
|
||||
|
||||
} // namespace bx
|
||||
|
||||
#endif // BX_FILE_H_HEADER_GUARD
|
||||
|
||||
@@ -10,33 +10,51 @@
|
||||
|
||||
namespace bx
|
||||
{
|
||||
/// Splits URL into tokens. Tokens are views into the parsed string, and they
|
||||
/// don't own any storage.
|
||||
///
|
||||
class UrlView
|
||||
{
|
||||
public:
|
||||
/// URL token.
|
||||
///
|
||||
enum Enum
|
||||
{
|
||||
Scheme,
|
||||
UserName,
|
||||
Password,
|
||||
Host,
|
||||
Port,
|
||||
Path,
|
||||
Query,
|
||||
Fragment,
|
||||
Scheme, //!< Scheme, without `://` separator.
|
||||
UserName, //!< User name from authority's user info.
|
||||
Password, //!< Password from authority's user info.
|
||||
Host, //!< Host name, or IP address.
|
||||
Port, //!< Port, as it appears in URL, without `:` separator.
|
||||
Path, //!< Path, including leading `/`.
|
||||
Query, //!< Query, without leading `?`.
|
||||
Fragment, //!< Fragment, without leading `#`.
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
/// Constructor. All tokens are empty until `parse` succeeds.
|
||||
///
|
||||
UrlView();
|
||||
|
||||
/// Reset all tokens to empty string view.
|
||||
///
|
||||
void clear();
|
||||
|
||||
/// Parse URL into tokens.
|
||||
///
|
||||
/// @param[in] _url URL to parse. It must have scheme, or path, or both.
|
||||
/// It's not copied, and it must outlive UrlView.
|
||||
///
|
||||
/// @returns True if URL is parsed successfully. Otherwise tokens are
|
||||
/// left in unspecified state, and must not be used.
|
||||
///
|
||||
bool parse(const StringView& _url);
|
||||
|
||||
/// Get token.
|
||||
///
|
||||
/// @param[in] _token Token to get.
|
||||
///
|
||||
/// @returns Token, or empty string view if URL doesn't contain it.
|
||||
///
|
||||
const StringView& get(Enum _token) const;
|
||||
|
||||
@@ -44,8 +62,39 @@ namespace bx
|
||||
StringView m_tokens[Count];
|
||||
};
|
||||
|
||||
/// Characters `urlEncode` passes through unescaped, in addition to the
|
||||
/// unreserved set `A-Z a-z 0-9 - _ . ~`.
|
||||
///
|
||||
void urlEncode(char* _out, uint32_t _max, const StringView& _str);
|
||||
struct UrlEncoding
|
||||
{
|
||||
/// Encoding.
|
||||
///
|
||||
enum Enum
|
||||
{
|
||||
Component, //!< Nothing. Use for single path segment, query value, etc.
|
||||
Path, //!< `/` separators. Use for whole path.
|
||||
|
||||
Count
|
||||
};
|
||||
};
|
||||
|
||||
/// Percent-encode string.
|
||||
///
|
||||
/// @param[out] _out Output buffer. It's always zero terminated, unless
|
||||
/// `_max` is zero. Encoding stops on truncation, and escape sequence is
|
||||
/// never split.
|
||||
/// @param[in] _max Size of output buffer, including zero terminator.
|
||||
/// @param[in] _str String to encode.
|
||||
/// @param[in] _encoding Characters to pass through unescaped.
|
||||
///
|
||||
/// @returns Number of characters written, excluding zero terminator.
|
||||
///
|
||||
int32_t urlEncode(
|
||||
char* _out
|
||||
, int32_t _max
|
||||
, const StringView& _str
|
||||
, UrlEncoding::Enum _encoding = UrlEncoding::Component
|
||||
);
|
||||
|
||||
} // namespace bx
|
||||
|
||||
|
||||
458
src/file.cpp
458
src/file.cpp
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
#include <bx/file.h>
|
||||
#include <bx/os.h>
|
||||
#include <bx/url.h>
|
||||
|
||||
#ifndef BX_CONFIG_CRT_FILE_READER_WRITER
|
||||
# define BX_CONFIG_CRT_FILE_READER_WRITER !BX_CRT_NONE
|
||||
@@ -19,15 +21,26 @@
|
||||
# if BX_CONFIG_CRT_DIRECTORY_READER
|
||||
# include <dirent.h>
|
||||
# endif // BX_CONFIG_CRT_DIRECTORY_READER
|
||||
# include <stdio.h> // remove
|
||||
# include <fcntl.h> // open
|
||||
# include <stdio.h> // remove, rename
|
||||
# include <sys/stat.h> // stat, mkdir
|
||||
# include <time.h> // time, localtime
|
||||
# if BX_CRT_MSVC
|
||||
# include <direct.h> // _getcwd
|
||||
# include <io.h> // _close, _open
|
||||
# else
|
||||
# include <unistd.h> // getcwd
|
||||
# include <unistd.h> // close, getcwd
|
||||
# endif // BX_CRT_MSVC
|
||||
#endif // !BX_CRT_NONE
|
||||
|
||||
#if BX_PLATFORM_WINDOWS
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif // WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
# include <shellapi.h> // SHFileOperationW
|
||||
#endif // BX_PLATFORM_WINDOWS
|
||||
|
||||
namespace bx
|
||||
{
|
||||
class NoopWriterImpl : public FileWriterI
|
||||
@@ -960,4 +973,445 @@ namespace bx
|
||||
return remove(_filePath, _err);
|
||||
}
|
||||
|
||||
#if BX_PLATFORM_WINDOWS
|
||||
static int32_t widen(wchar_t* _out, int32_t _max, const FilePath& _filePath)
|
||||
{
|
||||
return MultiByteToWideChar(CP_UTF8, 0, _filePath.getCPtr(), -1, _out, _max);
|
||||
}
|
||||
#endif // BX_PLATFORM_WINDOWS
|
||||
|
||||
bool copy(const FilePath& _from, const FilePath& _to, Error* _err)
|
||||
{
|
||||
BX_ERROR_SCOPE(_err);
|
||||
|
||||
if (!_err->isOk() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FileReader reader;
|
||||
|
||||
if (!reader.open(_from, _err) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FileWriter writer;
|
||||
|
||||
if (!writer.open(_to, false, _err) )
|
||||
{
|
||||
reader.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
uint8_t buffer[4096];
|
||||
|
||||
while (result)
|
||||
{
|
||||
Error err;
|
||||
const int32_t size = reader.read(buffer, sizeof(buffer), &err);
|
||||
|
||||
if (0 < size)
|
||||
{
|
||||
result = size == writer.write(buffer, size, _err);
|
||||
}
|
||||
|
||||
if (!err.isOk() )
|
||||
{
|
||||
result = result && err == kErrorReaderWriterEof;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.close();
|
||||
reader.close();
|
||||
|
||||
if (!result
|
||||
&& _err->isOk() )
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Failed to copy file.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool move(const FilePath& _from, const FilePath& _to, Error* _err)
|
||||
{
|
||||
BX_ERROR_SCOPE(_err);
|
||||
|
||||
if (!_err->isOk() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if BX_PLATFORM_WINDOWS
|
||||
wchar_t from[kMaxFilePath+1];
|
||||
wchar_t to[kMaxFilePath+1];
|
||||
|
||||
if (0 == widen(from, BX_COUNTOF(from), _from)
|
||||
|| 0 == widen(to, BX_COUNTOF(to), _to) )
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Failed to convert file path.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 == MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) )
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Failed to move file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
# if BX_CRT_NONE
|
||||
int32_t result = -1;
|
||||
# else
|
||||
int32_t result = ::rename(_from.getCPtr(), _to.getCPtr() );
|
||||
# endif // BX_CRT_NONE
|
||||
|
||||
if (0 == result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!copy(_from, _to, _err) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return remove(_from, _err);
|
||||
#endif // BX_PLATFORM_WINDOWS
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool claimFilePath(const FilePath& _filePath, bool _isDir)
|
||||
{
|
||||
if (_isDir)
|
||||
{
|
||||
return make(_filePath);
|
||||
}
|
||||
|
||||
#if BX_CRT_NONE
|
||||
BX_UNUSED(_filePath);
|
||||
return false;
|
||||
#else
|
||||
# if BX_CRT_MSVC
|
||||
const int32_t fd = ::_open(_filePath.getCPtr(), _O_WRONLY | _O_CREAT | _O_EXCL, _S_IREAD | _S_IWRITE);
|
||||
# else
|
||||
const int32_t fd = ::open(_filePath.getCPtr(), O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||
# endif // BX_CRT_MSVC
|
||||
|
||||
if (0 > fd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# if BX_CRT_MSVC
|
||||
::_close(fd);
|
||||
# else
|
||||
::close(fd);
|
||||
# endif // BX_CRT_MSVC
|
||||
|
||||
return true;
|
||||
#endif // BX_CRT_NONE
|
||||
}
|
||||
|
||||
#if BX_PLATFORM_WINDOWS
|
||||
static bool moveToTrashImpl(const FilePath& _filePath, Error* _err)
|
||||
{
|
||||
wchar_t from[kMaxFilePath+1] = {};
|
||||
|
||||
const int32_t num = widen(from, BX_COUNTOF(from)-1, _filePath);
|
||||
|
||||
if (0 == num)
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: Failed to convert file path.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int32_t ii = 0; ii < num; ++ii)
|
||||
{
|
||||
if (L'/' == from[ii])
|
||||
{
|
||||
from[ii] = L'\\';
|
||||
}
|
||||
}
|
||||
|
||||
void* shell32 = dlopen("shell32.dll");
|
||||
|
||||
if (NULL == shell32)
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: Failed to load `shell32.dll`.");
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef int (WINAPI *SHFileOperationWFn)(SHFILEOPSTRUCTW*);
|
||||
SHFileOperationWFn shFileOperationW = dlsym<SHFileOperationWFn>(shell32, "SHFileOperationW");
|
||||
|
||||
int32_t result = -1;
|
||||
|
||||
if (NULL != shFileOperationW)
|
||||
{
|
||||
SHFILEOPSTRUCTW op = {};
|
||||
op.wFunc = FO_DELETE;
|
||||
op.pFrom = from;
|
||||
op.fFlags = FOF_ALLOWUNDO | FOF_NO_UI;
|
||||
|
||||
result = shFileOperationW(&op);
|
||||
}
|
||||
|
||||
dlclose(shell32);
|
||||
|
||||
if (0 != result)
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: Failed to move file into Recycle Bin.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#elif BX_CRT_NONE
|
||||
static bool moveToTrashImpl(const FilePath& _filePath, Error* _err)
|
||||
{
|
||||
BX_UNUSED(_filePath);
|
||||
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: Not supported.");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
static bool reserveTrashPath(
|
||||
FilePath& _outFilePath
|
||||
, FilePath& _outInfoPath
|
||||
, const FilePath& _filesDir
|
||||
, const FilePath& _infoDir
|
||||
, const StringView& _fileName
|
||||
, const tm* _lt
|
||||
, bool _isDir
|
||||
, Error* _err
|
||||
)
|
||||
{
|
||||
#if BX_PLATFORM_OSX
|
||||
BX_UNUSED(_outInfoPath, _infoDir);
|
||||
#endif // BX_PLATFORM_OSX
|
||||
|
||||
const FilePath tmp(_fileName);
|
||||
const StringView baseName = tmp.getBaseName();
|
||||
const StringView ext = tmp.getExt();
|
||||
|
||||
for (int32_t ii = 0; ii < 8; ++ii)
|
||||
{
|
||||
char name[kMaxFilePath];
|
||||
|
||||
if (0 == ii)
|
||||
{
|
||||
strCopy(name, BX_COUNTOF(name), _fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(name, BX_COUNTOF(name), "%.*s.%04d%02d%02dT%02d%02d%02d-%d%.*s"
|
||||
, baseName.getLength()
|
||||
, baseName.getPtr()
|
||||
, _lt->tm_year + 1900
|
||||
, _lt->tm_mon + 1
|
||||
, _lt->tm_mday
|
||||
, _lt->tm_hour
|
||||
, _lt->tm_min
|
||||
, _lt->tm_sec
|
||||
, ii
|
||||
, ext.getLength()
|
||||
, ext.getPtr()
|
||||
);
|
||||
}
|
||||
|
||||
#if !BX_PLATFORM_OSX
|
||||
char infoName[kMaxFilePath];
|
||||
snprintf(infoName, BX_COUNTOF(infoName), "%s.trashinfo", name);
|
||||
|
||||
FilePath infoPath(_infoDir);
|
||||
infoPath.join(infoName);
|
||||
|
||||
if (!claimFilePath(infoPath, false) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif // !BX_PLATFORM_OSX
|
||||
|
||||
FilePath filePath(_filesDir);
|
||||
filePath.join(name);
|
||||
|
||||
if (!claimFilePath(filePath, _isDir) )
|
||||
{
|
||||
#if !BX_PLATFORM_OSX
|
||||
remove(infoPath);
|
||||
#endif // !BX_PLATFORM_OSX
|
||||
continue;
|
||||
}
|
||||
|
||||
_outFilePath = filePath;
|
||||
|
||||
#if !BX_PLATFORM_OSX
|
||||
_outInfoPath = infoPath;
|
||||
#endif // !BX_PLATFORM_OSX
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: Failed to reserve unique file name.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool moveToTrashImpl(const FilePath& _filePath, Error* _err)
|
||||
{
|
||||
FilePath trashDir;
|
||||
FilePath filesDir;
|
||||
FilePath infoDir;
|
||||
|
||||
#if BX_PLATFORM_OSX
|
||||
trashDir.set(Dir::Home);
|
||||
trashDir.join(".Trash");
|
||||
|
||||
filesDir = trashDir;
|
||||
#else
|
||||
// Reference(s):
|
||||
// - The FreeDesktop.org Trash specification
|
||||
// https://specifications.freedesktop.org/trash-spec/latest/
|
||||
//
|
||||
char tmp[kMaxFilePath];
|
||||
uint32_t size = BX_COUNTOF(tmp);
|
||||
|
||||
if (getEnv(tmp, &size, "XDG_DATA_HOME")
|
||||
&& 0 < size)
|
||||
{
|
||||
trashDir.set(StringView(tmp, size) );
|
||||
}
|
||||
else
|
||||
{
|
||||
trashDir.set(Dir::Home);
|
||||
trashDir.join(".local/share");
|
||||
}
|
||||
|
||||
trashDir.join("Trash");
|
||||
|
||||
filesDir = trashDir;
|
||||
filesDir.join("files");
|
||||
|
||||
infoDir = trashDir;
|
||||
infoDir.join("info");
|
||||
#endif // BX_PLATFORM_OSX
|
||||
|
||||
if (!makeAll(filesDir, _err) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !BX_PLATFORM_OSX
|
||||
if (!makeAll(infoDir, _err) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif // !BX_PLATFORM_OSX
|
||||
|
||||
const time_t tt = ::time(NULL);
|
||||
const tm* lt = ::localtime(&tt);
|
||||
|
||||
FileInfo fi;
|
||||
|
||||
if (!stat(fi, _filePath) )
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: File doesn't exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
FilePath dstPath;
|
||||
FilePath infoPath;
|
||||
|
||||
if (!reserveTrashPath(dstPath, infoPath, filesDir, infoDir, _filePath.getFileName(), lt, FileType::Dir == fi.type, _err) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !BX_PLATFORM_OSX
|
||||
{
|
||||
FileWriter writer;
|
||||
|
||||
if (!writer.open(infoPath, false, _err) )
|
||||
{
|
||||
remove(infoPath);
|
||||
remove(dstPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
char url[kMaxFilePath*3];
|
||||
urlEncode(url, BX_COUNTOF(url), _filePath, UrlEncoding::Path);
|
||||
|
||||
char info[BX_COUNTOF(url) + 128];
|
||||
const int32_t len = snprintf(info, BX_COUNTOF(info)
|
||||
, "[Trash Info]\nPath=%s\nDeletionDate=%04d-%02d-%02dT%02d:%02d:%02d\n"
|
||||
, url
|
||||
, lt->tm_year + 1900
|
||||
, lt->tm_mon + 1
|
||||
, lt->tm_mday
|
||||
, lt->tm_hour
|
||||
, lt->tm_min
|
||||
, lt->tm_sec
|
||||
);
|
||||
|
||||
write(&writer, info, len, _err);
|
||||
writer.close();
|
||||
|
||||
if (!_err->isOk() )
|
||||
{
|
||||
remove(infoPath);
|
||||
remove(dstPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif // !BX_PLATFORM_OSX
|
||||
|
||||
if (!move(_filePath, dstPath, _err) )
|
||||
{
|
||||
#if !BX_PLATFORM_OSX
|
||||
remove(infoPath);
|
||||
#endif // !BX_PLATFORM_OSX
|
||||
remove(dstPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // BX_PLATFORM_WINDOWS
|
||||
|
||||
bool moveToTrash(const FilePath& _filePath, Error* _err)
|
||||
{
|
||||
BX_ERROR_SCOPE(_err);
|
||||
|
||||
if (!_err->isOk() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FileInfo fi;
|
||||
|
||||
if (!stat(fi, _filePath) )
|
||||
{
|
||||
BX_ERROR_SET(_err, kErrorAccess, "Trash: File doesn't exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
FilePath filePath;
|
||||
|
||||
if (_filePath.isAbsolute() )
|
||||
{
|
||||
filePath = _filePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
filePath.set(Dir::Current);
|
||||
filePath.join(_filePath);
|
||||
}
|
||||
|
||||
return moveToTrashImpl(filePath, _err);
|
||||
}
|
||||
|
||||
} // namespace bx
|
||||
|
||||
61
src/url.cpp
61
src/url.cpp
@@ -127,42 +127,59 @@ namespace bx
|
||||
return m_tokens[_token];
|
||||
}
|
||||
|
||||
static char toHex(char _nible)
|
||||
static char toHex(uint8_t _nible)
|
||||
{
|
||||
return "0123456789ABCDEF"[_nible&0xf];
|
||||
}
|
||||
|
||||
// https://secure.wikimedia.org/wikipedia/en/wiki/URL_encoding
|
||||
void urlEncode(char* _out, uint32_t _max, const StringView& _str)
|
||||
int32_t urlEncode(char* _out, int32_t _max, const StringView& _str, UrlEncoding::Enum _encoding)
|
||||
{
|
||||
_max--; // need space for zero terminator
|
||||
|
||||
const char* str = _str.getPtr();
|
||||
const char* term = _str.getTerm();
|
||||
|
||||
uint32_t ii = 0;
|
||||
for (char ch = *str++
|
||||
; str <= term && ii < _max
|
||||
; ch = *str++
|
||||
)
|
||||
if (0 >= _max)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* str = _str.getPtr();
|
||||
|
||||
const int32_t max = _max-1; // need space for zero terminator
|
||||
int32_t len = 0;
|
||||
|
||||
for (int32_t ii = 0, num = _str.getLength(); ii < num; ++ii)
|
||||
{
|
||||
const char ch = str[ii];
|
||||
|
||||
if (isAlphaNum(ch)
|
||||
|| ch == '-'
|
||||
|| ch == '_'
|
||||
|| ch == '.'
|
||||
|| ch == '~')
|
||||
|| '-' == ch
|
||||
|| '_' == ch
|
||||
|| '.' == ch
|
||||
|| '~' == ch
|
||||
|| ('/' == ch && UrlEncoding::Path == _encoding) )
|
||||
{
|
||||
_out[ii++] = ch;
|
||||
if (max < len+1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_out[len++] = ch;
|
||||
}
|
||||
else if (ii+3 < _max)
|
||||
else
|
||||
{
|
||||
_out[ii++] = '%';
|
||||
_out[ii++] = toHex(ch>>4);
|
||||
_out[ii++] = toHex(ch);
|
||||
// Escape sequence must not be split by truncation.
|
||||
if (max < len+3)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_out[len++] = '%';
|
||||
_out[len++] = toHex(uint8_t(ch)>>4);
|
||||
_out[len++] = toHex(uint8_t(ch) );
|
||||
}
|
||||
}
|
||||
|
||||
_out[ii] = '\0';
|
||||
_out[len] = '\0';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
} // namespace bx
|
||||
|
||||
@@ -100,3 +100,74 @@ TEST_CASE("tokenizeUrl", "[url][string]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UrlEncodeTest
|
||||
{
|
||||
const char* input;
|
||||
bx::UrlEncoding::Enum encoding;
|
||||
const char* expected;
|
||||
};
|
||||
|
||||
static const UrlEncodeTest s_urlEncodeTest[] =
|
||||
{
|
||||
{ "", bx::UrlEncoding::Component, "" },
|
||||
{ "abcABC123", bx::UrlEncoding::Component, "abcABC123" },
|
||||
{ "-_.~", bx::UrlEncoding::Component, "-_.~" },
|
||||
{ "a b", bx::UrlEncoding::Component, "a%20b" },
|
||||
{ "100%", bx::UrlEncoding::Component, "100%25" },
|
||||
{ "a+b=c&d", bx::UrlEncoding::Component, "a%2Bb%3Dc%26d" },
|
||||
{ "\xc3\xa9", bx::UrlEncoding::Component, "%C3%A9" },
|
||||
{ "/tmp/a b.txt", bx::UrlEncoding::Component, "%2Ftmp%2Fa%20b.txt" },
|
||||
{ "/tmp/a b.txt", bx::UrlEncoding::Path, "/tmp/a%20b.txt" },
|
||||
{ "\xc3\xa9", bx::UrlEncoding::Path, "%C3%A9" },
|
||||
};
|
||||
|
||||
TEST_CASE("urlEncode", "[url][string]")
|
||||
{
|
||||
for (uint32_t ii = 0; ii < BX_COUNTOF(s_urlEncodeTest); ++ii)
|
||||
{
|
||||
const UrlEncodeTest& test = s_urlEncodeTest[ii];
|
||||
|
||||
char tmp[128];
|
||||
const int32_t len = bx::urlEncode(tmp, BX_COUNTOF(tmp), test.input, test.encoding);
|
||||
|
||||
REQUIRE(0 == bx::strCmp(test.expected, tmp) );
|
||||
REQUIRE(len == bx::strLen(test.expected) );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("urlEncode truncate", "[url][string]")
|
||||
{
|
||||
{
|
||||
char tmp[4];
|
||||
REQUIRE(3 == bx::urlEncode(tmp, BX_COUNTOF(tmp), "abcdef") );
|
||||
REQUIRE(0 == bx::strCmp("abc", tmp) );
|
||||
}
|
||||
|
||||
{
|
||||
char tmp[4];
|
||||
REQUIRE(3 == bx::urlEncode(tmp, BX_COUNTOF(tmp), " ") );
|
||||
REQUIRE(0 == bx::strCmp("%20", tmp) );
|
||||
}
|
||||
|
||||
{
|
||||
char tmp[3];
|
||||
REQUIRE(0 == bx::urlEncode(tmp, BX_COUNTOF(tmp), " ") );
|
||||
REQUIRE(0 == bx::strCmp("", tmp) );
|
||||
}
|
||||
|
||||
{
|
||||
char tmp[1];
|
||||
REQUIRE(0 == bx::urlEncode(tmp, BX_COUNTOF(tmp), "abc") );
|
||||
REQUIRE(0 == bx::strCmp("", tmp) );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("urlEncode StringView", "[url][string]")
|
||||
{
|
||||
const bx::StringView str("abc def", 3);
|
||||
|
||||
char tmp[128];
|
||||
REQUIRE(3 == bx::urlEncode(tmp, BX_COUNTOF(tmp), str) );
|
||||
REQUIRE(0 == bx::strCmp("abc", tmp) );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user