ardour/libs/aaf/utils.c

852 lines
16 KiB
C

/*
* Copyright (C) 2017-2024 Adrien Gesta-Fline
*
* This file is part of libAAF.
*
* 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, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#if defined(__linux__)
#include <linux/limits.h>
#include <arpa/inet.h>
#include <mntent.h>
#include <unistd.h> /* access() */
#elif defined(__APPLE__)
#include <sys/syslimits.h>
#include <unistd.h> /* access() */
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#include <limits.h>
#define R_OK 4 /* Test for read permission. */
#define W_OK 2 /* Test for write permission. */
#define F_OK 0 /* Test for existence. */
#ifndef _MSC_VER
#include <unistd.h> // access()
#endif
#endif
#include "aaf/utils.h"
#define BUILD_PATH_DEFAULT_BUF_SIZE 1024
static int
utf8CodeLen (const uint16_t* u16Code);
static int
utf16CodeLen (const uint16_t* u16Code);
static int
utf16CodeToUTF8 (const uint16_t* u16Code, char* u8Code, int* u16Len, int* u8Len);
static long
utf8strLen (const uint16_t* u16str);
#ifdef _WIN32
wchar_t*
laaf_util_windows_utf8toutf16 (const char* str)
{
if (!str) {
return NULL;
}
int needed = MultiByteToWideChar (CP_UTF8, 0, str, -1, NULL, 0);
if (needed == 0) {
return NULL;
}
wchar_t* wbuf = malloc ((size_t)needed * sizeof (wchar_t));
if (!wbuf) {
return NULL;
}
int rc = MultiByteToWideChar (CP_UTF8, 0, str, -1, wbuf, needed);
if (rc == 0) {
free (wbuf);
return NULL;
}
return wbuf;
}
char*
laaf_util_windows_utf16toutf8 (const wchar_t* wstr)
{
if (!wstr) {
return NULL;
}
int needed = WideCharToMultiByte (CP_UTF8, 0, wstr, -1, NULL, 0, 0, 0);
if (needed == 0) {
return NULL;
}
char* buf = malloc ((size_t)needed * sizeof (char));
if (!buf) {
return NULL;
}
int rc = WideCharToMultiByte (CP_UTF8, 0, wstr, -1, buf, needed, 0, 0);
if (rc == 0) {
free (buf);
return NULL;
}
return buf;
}
#endif
char*
laaf_util_clean_filename (char* fname)
{
/*
* sanitize file/dir name
* https://stackoverflow.com/a/31976060
*/
if (!fname) {
return NULL;
}
char* p = fname;
while (*p) {
if (*p == '/' ||
*p == '<' ||
*p == '>' ||
*p == ':' ||
*p == '"' ||
*p == '|' ||
*p == '?' ||
*p == '*' ||
*p == '\\' ||
(*p > 0 && *p < 0x20)) {
*p = '_';
}
p++;
}
/* windows filenames can't end with ' ' or '.' */
p = fname + strlen (fname) - 1;
while (*p && (*p == ' ' || *p == '.')) {
*p = '\0';
p--;
}
if (*fname == '\0')
return NULL;
return fname;
}
int
laaf_util_is_fileext (const char* filepath, const char* ext)
{
if (!filepath || !ext) {
return 0;
}
const char* end = filepath + strlen (filepath);
size_t extlen = 0;
while (end > filepath && (*end) != '.') {
--end;
extlen++;
}
if ((*end) == '.') {
end++;
extlen--;
}
if (!extlen || extlen != strlen (ext)) {
return 0;
}
for (size_t i = 0; i < extlen; i++) {
if (tolower (*(end + i)) != tolower (*(ext + i))) {
return 0;
}
}
return 1;
}
char*
laaf_util_build_path (const char* sep, const char* first, ...)
{
char* str = malloc (BUILD_PATH_DEFAULT_BUF_SIZE);
if (!str) {
return NULL;
}
size_t len = BUILD_PATH_DEFAULT_BUF_SIZE;
size_t offset = 0;
va_list args;
if (!sep) {
sep = DIR_SEP_STR;
}
int element_count = 0;
va_start (args, first);
const char* arg = first;
do {
size_t arglen = strlen (arg);
size_t argstart = 0;
int has_leading_sep = 0;
/* trim leading DIR_SEP */
for (int i = 0; arg[i] != 0x00; i++) {
if (IS_ANY_DIR_SEP (arg[i])) {
has_leading_sep = 1;
argstart++;
} else {
break;
}
}
/* trim trailing DIR_SEP */
for (size_t i = arglen - 1; i >= argstart; i--) {
if (IS_ANY_DIR_SEP (arg[i])) {
arglen--;
} else {
break;
}
}
size_t reqlen = (arglen - argstart) + 2;
if (offset + reqlen >= len) {
reqlen = ((offset + reqlen) > (len + BUILD_PATH_DEFAULT_BUF_SIZE)) ? (reqlen) : (len + BUILD_PATH_DEFAULT_BUF_SIZE);
char* tmp = realloc (str, (offset + reqlen));
if (!tmp) {
free (str);
return NULL;
}
str = tmp;
len = (offset + reqlen);
}
int written = snprintf (str + offset, len - offset, "%s%.*s",
((element_count == 0 && has_leading_sep) || (element_count > 0)) ? sep : "",
(uint32_t) (arglen - argstart),
arg + argstart);
if (written < 0 || (size_t)written >= (len - offset)) {
free (str);
return NULL;
}
offset += (size_t)written;
element_count++;
} while ((arg = va_arg (args, char*)) != NULL);
va_end (args);
/* do not mix between different dirseps and removes any consecutive dirseps */
char* i = str;
char* o = str;
int dirseppassed = 0;
while (*i) {
if (!dirseppassed && IS_ANY_DIR_SEP (*i)) {
*o = *sep;
o++;
dirseppassed = 1;
} else if (!IS_ANY_DIR_SEP (*i)) {
dirseppassed = 0;
*o = *i;
o++;
}
i++;
}
*o = '\0';
return str;
}
char*
laaf_util_relative_path (const char* filepath, const char* refpath)
{
if (!filepath || !refpath || filepath[0] == '\0' || refpath[0] == '\0') {
return NULL;
}
int isWindowsPath = 0;
int aWindowsPath = 0;
int bWindowsPath = 0;
char* relpath = NULL;
if (filepath[0] != '\0' && isalpha (filepath[0]) && filepath[1] == ':') {
aWindowsPath = 1;
}
if (refpath[0] != '\0' && isalpha (refpath[0]) && refpath[1] == ':') {
bWindowsPath = 1;
}
isWindowsPath = (aWindowsPath + bWindowsPath);
if (isWindowsPath == 1) {
// fprintf( stderr, "Trying to make a relative path out of a windows path and a non-windows path\n" );
return NULL;
}
if (isWindowsPath == 2) {
if (tolower (filepath[0]) != tolower (refpath[0])) {
// fprintf( stderr, "Both paths target different drives\n" );
return NULL;
}
}
int winDriveLetterOffset = isWindowsPath;
if (strncmp (filepath + winDriveLetterOffset, refpath + winDriveLetterOffset, strlen (refpath)) == 0) {
relpath = laaf_util_build_path ("/", "./", filepath + strlen (refpath), NULL);
return relpath;
}
char* _filepath = laaf_util_build_path ("/", filepath, NULL);
char* _refpath = laaf_util_build_path ("/", refpath, "/", NULL); /* ensures there is always a trailing '/' */
if (!_filepath || !_refpath) {
return NULL;
}
char* parents = NULL;
size_t parentsLen = 0;
size_t parentsOffset = 0;
char* p = _refpath + strlen (_refpath);
while (p > (_refpath + winDriveLetterOffset)) {
while (p > (_refpath + winDriveLetterOffset) && !IS_DIR_SEP (*p)) {
*p = '\0';
p--;
}
if (strncmp (_filepath + winDriveLetterOffset, _refpath + winDriveLetterOffset, strlen (_refpath + winDriveLetterOffset)) == 0) {
if (!parents) {
relpath = laaf_util_build_path ("/", "./", _filepath + strlen (_refpath), NULL);
goto end;
} else {
relpath = laaf_util_build_path ("/", parents, _filepath + strlen (_refpath), NULL);
goto end;
}
}
int ret = laaf_util_snprintf_realloc (&parents, &parentsLen, parentsOffset, "../");
assert (ret >= 0);
parentsOffset += (size_t)ret;
p--;
}
end:
free (parents);
free (_filepath);
free (_refpath);
return relpath;
}
char*
laaf_util_absolute_path (const char* relpath)
{
if (!relpath) {
return NULL;
}
#ifdef _WIN32
// char *abspath = NULL;
wchar_t buf[_MAX_PATH];
wchar_t* wrelpath = laaf_util_windows_utf8toutf16 (relpath);
if (!wrelpath) {
return NULL;
}
if (!_wfullpath (buf, wrelpath, _MAX_PATH)) {
free (wrelpath);
return NULL;
}
char* abspath = laaf_util_windows_utf16toutf8 (buf);
if (!abspath) {
free (wrelpath);
return NULL;
}
free (wrelpath);
return abspath;
#else
char buf[PATH_MAX + 1];
if (!realpath (relpath, buf)) {
return NULL;
}
return laaf_util_c99strdup (buf);
#endif
}
int
laaf_util_snprintf_realloc (char** str, size_t* size, size_t offset, const char* format, ...)
{
size_t tmpsize = 0;
if (!size) {
size = &tmpsize;
}
int retval = 0;
size_t needed = 0;
va_list ap;
va_start (ap, format);
while (0 <= (retval = vsnprintf ((*str) + offset, (*size) - offset, format, ap)) && ((*size) - offset) < (needed = (unsigned)retval + 1)) {
va_end (ap);
*size *= 2;
if (((*size) - offset) < needed)
*size = needed + offset;
char* p = realloc (*str, *size);
if (p) {
*str = p;
} else {
free (*str);
*str = NULL;
*size = 0;
return 0;
}
va_start (ap, format);
}
va_end (ap);
return (retval > 0) ? retval : 0;
}
int
laaf_util_file_exists (const char* filepath)
{
#ifdef _WIN32
int needed = MultiByteToWideChar (CP_UTF8, 0, filepath, -1, NULL, 0);
if (needed == 0) {
return -1;
}
wchar_t* wfilepath = malloc ((size_t)needed * sizeof (wchar_t));
if (!wfilepath) {
return -1;
}
int rc = MultiByteToWideChar (CP_UTF8, 0, filepath, -1, wfilepath, needed);
if (rc == 0) {
free (wfilepath);
return -1;
}
if (_waccess (wfilepath, F_OK) == 0) {
free (wfilepath);
return 1;
}
free (wfilepath);
#else
if (access (filepath, F_OK) == 0) {
return 1;
}
#endif
return 0;
}
static int
utf8CodeLen (const uint16_t* u16Code)
{
if (u16Code[0] < 0x80) {
return 1;
} else if (u16Code[0] < 0x800) {
return 2;
} else if (u16Code[0] < 0xD800 ||
u16Code[0] > 0xDFFF) {
return 3;
} else if (((u16Code[0] & 0xFC00) == 0xD800) &&
((u16Code[1] & 0xFC00) == 0xDC00)) {
return 4;
} else {
return -1;
}
}
static int
utf16CodeLen (const uint16_t* u16Code)
{
if (u16Code[0] < 0xD800 ||
u16Code[0] > 0xDFFF) {
return 1;
} else if (((u16Code[0] & 0xFC00) == 0xD800) &&
((u16Code[1] & 0xFC00) == 0xDC00)) {
return 2;
} else {
return -1;
}
}
static int
utf16CodeToUTF8 (const uint16_t* u16Code, char* u8Code, int* u16Len, int* u8Len)
{
int len8 = utf8CodeLen (u16Code);
int len16 = utf16CodeLen (u16Code);
if (len8 < 0 || len16 < 0) {
return -1;
}
*u8Len = len8;
*u16Len = len16;
if (len8 == 1) {
u8Code[0] = (char)(u16Code[0]);
} else if (len8 == 2) {
u8Code[0] = (char)(0xC0 | (u16Code[0] >> 6));
u8Code[1] = (char)(0x80 | (u16Code[0] & 0x3F));
} else if (len8 == 3) {
u8Code[0] = (char)(0xE0 | (u16Code[0] >> 12));
u8Code[1] = (char)(0x80 | ((u16Code[0] >> 6) & 0x3F));
u8Code[2] = (char)(0x80 | (u16Code[0] & 0x3F));
} else {
uint32_t c = (u16Code[0] & 0x03FF) << 10;
c |= (u16Code[1] & 0x03FF);
c += 0x10000;
u8Code[0] = (char)(0xF0 | ((c >> 18) & 0x07));
u8Code[1] = (char)(0x80 | ((c >> 12) & 0x3F));
u8Code[2] = (char)(0x80 | ((c >> 6) & 0x3F));
u8Code[3] = (char)(0x80 | (c & 0x3F));
}
return *u8Len;
}
static long
utf8strLen (const uint16_t* u16str)
{
long len = 0;
const uint16_t* p = u16str;
while (*p != 0x0000) {
int u8CodeLen = utf8CodeLen (p);
int u16CodeLen = utf16CodeLen (p);
if (u8CodeLen < 0 || u16CodeLen < 0) {
len = -1;
break;
}
p += u16CodeLen;
len += u8CodeLen;
}
return len;
}
size_t
laaf_util_utf8strCharLen (const char* u8str)
{
size_t count = 0;
while (*u8str) {
count += (*u8str++ & 0xC0) != 0x80;
}
return count;
}
char*
laaf_util_utf16Toutf8 (const uint16_t* u16str)
{
long u8len = utf8strLen (u16str);
if (u8len < 0) {
return 0;
}
char* u8str = calloc ((size_t)u8len + 1, sizeof (char));
if (!u8str) {
return NULL;
}
const uint16_t* u16ptr = u16str;
char* u8ptr = u8str;
while (*u16ptr != 0x0000) {
int u8codelen = 0;
int u16codelen = 0;
utf16CodeToUTF8 (u16ptr, u8ptr, &u16codelen, &u8codelen);
if (u16codelen < 0 || u8codelen < 0) {
free (u8str);
return NULL;
}
u8ptr += u8codelen;
u16ptr += u16codelen;
}
*u8ptr = 0x00;
return u8str;
}
int
laaf_util_vsnprintf_realloc (char** str, size_t* size, size_t offset, const char* fmt, va_list args)
{
FILE* dummy = NULL;
va_list args1;
size_t tmpsize = 0;
if (size == NULL) {
size = &tmpsize;
}
va_copy (args1, args);
/* https://stackoverflow.com/a/4116308 */
#ifndef _WIN32
dummy = fopen ("/dev/null", "wb");
#else
dummy = fopen ("NUL", "wb");
#endif
if (!dummy) {
// fprintf( stderr, "Could not fopen() dummy null file\n" );
goto err;
}
int retval = vfprintf (dummy, fmt, args);
if (retval < 0) {
// fprintf( stderr, "vfprintf() error : %s\n", strerror(errno) );
goto err;
}
unsigned needed = (unsigned)retval + 1;
if (needed >= (*size) - offset) {
char* p = realloc (*str, (offset + needed) * sizeof (char));
if (p) {
*str = p;
*size = offset + needed;
} else {
goto err;
}
}
int written = vsnprintf ((*str) + offset, (*size) - offset, fmt, args1);
// assert( written >= 0 && (size_t)written < (*size)-offset );
if (written < 0 && (size_t)written >= (*size) - offset) {
fprintf (stderr, "vsnprintf() error : %s\n", strerror (errno));
goto err;
}
goto end;
err:
written = -1;
end:
if (dummy) {
fclose (dummy);
}
return written;
}
char*
laaf_util_c99strdup (const char* src)
{
if (!src) {
return NULL;
}
size_t len = 0;
while (src[len]) {
len++;
}
char* str = malloc (len + 1);
if (!str) {
return NULL;
}
char* p = str;
while (*src) {
*(p++) = *(src++);
}
*p = '\0';
return str;
}
int
laaf_util_dump_hex (const unsigned char* stream, size_t stream_sz, char** buf, size_t* bufsz, size_t offset, const char* padding)
{
if (stream == NULL) {
return -1;
}
size_t initialOffset = offset;
uint32_t i = 0;
char hex[49];
char ascii[19];
size_t count = 0;
int rc = laaf_util_snprintf_realloc (buf, bufsz, offset, "%s______________________________ Hex Dump ______________________________\n\n", padding);
if (rc < 0) {
goto end;
}
offset += (size_t)rc;
while (count < stream_sz) {
size_t lineLen = (stream_sz - count) / 16;
if (lineLen <= 0)
lineLen = (stream_sz) % 16;
else
lineLen = 16;
memset (&hex, 0x20, sizeof (hex));
memset (&ascii, 0x00, sizeof (ascii));
uint32_t linepos = 0;
for (i = 0; i < lineLen; i++) {
rc = snprintf (&hex[linepos], sizeof (hex) - (linepos), "%02x%s", *(const unsigned char*)(stream + count + i), (i == 7) ? " " : " ");
if (rc < 0) {
goto end;
}
linepos += (uint32_t)rc;
if (i < 8) {
if (isalnum (*(stream + count + i)))
ascii[i] = *(const char*)(stream + count + i);
else
ascii[i] = '.';
} else if (i > 8) {
if (isalnum (*(stream + count + i)))
ascii[i + 1] = *(const char*)(stream + count + i);
else
ascii[i + 1] = '.';
} else {
if (isalnum (*(stream + count + i))) {
ascii[i] = ' ';
ascii[i + 1] = *(const char*)(stream + count + i);
} else {
ascii[i] = ' ';
ascii[i + 1] = '.';
}
}
}
/* Fill with blank the rest of the line */
if (lineLen < 16) {
for (i = linepos; i < 48; i++) {
hex[linepos++] = 0x20;
}
}
/* terminate the line */
hex[48] = 0x00;
count += lineLen;
rc = laaf_util_snprintf_realloc (buf, bufsz, offset, "%s%s | %s\n", padding, hex, ascii);
if (rc < 0) {
goto end;
}
offset += (size_t)rc;
}
rc = laaf_util_snprintf_realloc (buf, bufsz, offset, "%s______________________________________________________________________\n\n", padding);
if (rc < 0) {
goto end;
}
end:
return (int)(offset - initialOffset); /* bytes written */
}