winamp/Src/apev2/tag.cpp
2024-09-24 14:54:57 +02:00

353 lines
7.8 KiB
C++

#include "tag.h"
#include "header.h"
#include "flags.h"
#include "nu/ns_wc.h"
#include <limits.h>
#include <strsafe.h>
#include <stdint.h>
/*
http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification
*/
APEv2::Tag::Tag()
{
flags = 0; // default to writing just a footer
}
int APEv2::Tag::Parse(const void *_data, size_t len)
{
char *data = (char *)_data;
if (len < Header::SIZE)
return APEV2_TOO_SMALL;
char *headerStart = data+(len-Header::SIZE);
Header footer(headerStart);
if (footer.Valid()
&& !(len == Header::SIZE && footer.IsHeader())) // if all we have is this footer, we should check that it's not really a header
{
len -= Header::SIZE;
char *dataStart = data;
if (footer.HasHeader())
{
// TODO: validate header
dataStart+= Header::SIZE;
len-=Header::SIZE;
}
flags = footer.GetFlags();
flags &= ~FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
return ParseData(dataStart, len);
}
// ok, we didn't have a footer, so let's see if we have a header
headerStart = data;
Header header(headerStart);
if (header.Valid())
{
len -= Header::SIZE;
char *dataStart = data + Header::SIZE;
if (header.HasFooter())
{
// TODO: validate footer
// benski> cut... we got here because we didn't have a footer.
//len-=Header::SIZE;
}
flags = header.GetFlags();
flags |= FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
return ParseData(dataStart, len);
}
return APEV2_FAILURE;
}
int APEv2::Tag::ParseData(const void *data, size_t len)
{
char *dataStart = (char *)data;
while (len)
{
Item *item = new Item;
size_t new_len = 0;
void *new_data = 0;
int ret = item->Read(dataStart, len, &new_data, &new_len);
if (ret == APEV2_SUCCESS)
{
len = new_len;
dataStart = (char *)new_data;
items.push_back(item);
}
else
{
item->Release();
return ret;
}
}
return APEV2_SUCCESS;
}
int APEv2::Tag::GetString(const char *tag, wchar_t *data, size_t dataLen)
{
/* our UTF-8 to wchar_t conversion function wants an int for size, so we should explicitly check the size */
if (dataLen > INT_MAX)
return APEV2_FAILURE;
for ( APEv2::Item *l_item : items )
{
/* check if it's a string first, and then match the key next (will be faster) */
if ( l_item->IsString() && l_item->KeyMatch(tag, ITEM_KEY_COMPARE_CASE_INSENSITIVE))
{
void *item_data = 0;
size_t item_dataLen = 0;
if ( l_item->Get(&item_data, &item_dataLen) == APEV2_SUCCESS && item_dataLen < INT_MAX)
{
int signed_len = static_cast<int>(item_dataLen); // we checked against INT_MAX above so this conversion is safe
if (MultiByteToWideCharSZ(CP_UTF8, 0, (LPCSTR)item_data, signed_len, data, (int)dataLen) != 0)
return APEV2_SUCCESS;
}
return APEV2_NO_DATA;
}
}
return APEV2_KEY_NOT_FOUND;
}
int APEv2::Tag::SetItemData(APEv2::Item *item, const wchar_t *data)
{
int utf16_data_cch = (int)wcslen(data);
int item_dataLen = WideCharToMultiByte(CP_UTF8, 0, data, utf16_data_cch, 0, 0, NULL, NULL);
if (!item_dataLen)
return APEV2_FAILURE;
char *item_data = (char *)calloc(item_dataLen, sizeof(char));
if (item_data)
{
if (!WideCharToMultiByte(CP_UTF8, 0, data, utf16_data_cch, item_data, item_dataLen, NULL, NULL))
{
free(item_data);
return APEV2_FAILURE;
}
item->Set(item_data, item_dataLen, FLAG_ITEM_TEXT);
free(item_data);
return APEV2_SUCCESS;
}
return APEV2_FAILURE;
}
int APEv2::Tag::SetString(const char *tag, const wchar_t *data)
{
for (auto it = items.begin(); it != items.end(); it++)
{
APEv2::Item* item = *it;
if (item->KeyMatch(tag, ITEM_KEY_COMPARE_CASE_INSENSITIVE))
{
if (data && *data)
{
return SetItemData(item, data);
}
else
{
item->Release();
it = items.erase(it);
return APEV2_SUCCESS;
}
}
}
if (data && *data)
{
/* Not already in the list, so we need to add it */
Item *newItem = new Item;
if (newItem->SetKey(tag) == APEV2_SUCCESS && SetItemData(newItem, data) == APEV2_SUCCESS)
{
items.push_back(newItem);
}
else
{
newItem->Release();
return APEV2_FAILURE;
}
}
return APEV2_SUCCESS;
}
int APEv2::Tag::EnumValue(size_t i, const char **tag, wchar_t *data, size_t dataLen)
{
/* our UTF-8 to wchar_t conversion function wants an int for size, so we should explicitly check the size */
if (dataLen > INT_MAX)
return APEV2_FAILURE;
if (i >= items.size())
return APEV2_END_OF_ITEMS;
APEv2::Item *item = items[i];
item->GetKey(tag);
if (!data || !dataLen) // if they don't want the data, go ahead and return now
return APEV2_SUCCESS;
data[0]=0;
void *item_data = 0;
size_t item_dataLen = 0;
if (item->IsString())
{
if (item->Get(&item_data, &item_dataLen) == APEV2_SUCCESS && item_dataLen < INT_MAX)
{
int signed_len = static_cast<int>(item_dataLen); // we checked against INT_MAX above so this conversion is safe
if (MultiByteToWideCharSZ(CP_UTF8, 0, (LPCSTR)item_data, signed_len, data, (int)dataLen) == 0)
*data=0;
}
}
else
{
// TODO: benski> hmmm
StringCchCopyW(data, dataLen, L"[TODO: deal with binary]");
}
return APEV2_SUCCESS;
}
void APEv2::Tag::Clear()
{
for ( APEv2::Item *l_item : items )
{
l_item->Release();
}
items.clear();
}
int APEv2::Tag::RemoveItem(size_t index)
{
if (index >= items.size())
return APEV2_END_OF_ITEMS;
APEv2::Item *item = items[index];
items.erase(items.begin() + index);
item->Release();
return APEV2_SUCCESS;
}
size_t APEv2::Tag::EncodeSize()
{
size_t totalSize=0;
if (flags & FLAG_HEADER_HAS_HEADER)
totalSize+=Header::SIZE;
for ( APEv2::Item *l_item : items )
{
totalSize += l_item->EncodeSize();
}
if (!(flags & FLAG_HEADER_NO_FOOTER))
totalSize+=Header::SIZE;
return totalSize;
}
int APEv2::Tag::Encode(void *data, size_t len)
{
size_t totalSize=0;
int8_t *ptr = (int8_t *)data;
if (flags & FLAG_HEADER_HAS_HEADER)
{
HeaderData headerData = {0};
headerData.size= (uint32_t)totalSize;
if (!(flags & FLAG_HEADER_NO_FOOTER))
headerData.size += Header::SIZE;
headerData.items = (uint32_t)items.size();
headerData.flags = (flags & FLAG_HEADER_ENCODE_MASK)|FLAG_HEADER_IS_HEADER;
Header header(&headerData);
int ret = header.Encode(ptr, len);
if (ret != APEV2_SUCCESS)
return ret;
ptr += Header::SIZE;
len -= Header::SIZE;
}
for ( APEv2::Item *l_item : items )
{
int ret = l_item->Encode(ptr, len);
if (ret!= APEV2_SUCCESS)
return ret;
size_t itemSize = l_item->EncodeSize();
len-=itemSize;
ptr+=itemSize;
totalSize+=itemSize;
}
if (!(flags & FLAG_HEADER_NO_FOOTER))
{
HeaderData footerData = {0};
footerData.size= (uint32_t)totalSize + Header::SIZE;
footerData.items = (uint32_t)items.size();
footerData.flags = (flags & FLAG_HEADER_ENCODE_MASK);
Header footer(&footerData);
int ret = footer.Encode(ptr, len);
if (ret != APEV2_SUCCESS)
return ret;
}
return APEV2_SUCCESS;
}
int APEv2::Tag::SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data)
{
if (index >= items.size())
return APEV2_END_OF_ITEMS;
// TODO: check for duplicate key
items[index]->SetKey(key);
return SetItemData(items[index], data);
}
APEv2::Item *APEv2::Tag::AddItem()
{
Item *newItem = new Item;
items.push_back(newItem);
return newItem;
}
int APEv2::Tag::SetFlags(uint32_t newflags, uint32_t mask)
{
flags = (flags & ~mask) | newflags;
return APEV2_SUCCESS;
}
int APEv2::Tag::FindItemByKey(const char *key, size_t *index, int compare)
{
for (size_t i=0;i!=items.size();i++)
{
if (items[i]->KeyMatch(key, compare))
{
*index = i;
return APEV2_SUCCESS;
}
}
return APEV2_KEY_NOT_FOUND;
}
size_t APEv2::Tag::GetNumItems()
{
return items.size();
}
bool APEv2::Tag::IsReadOnly()
{
return flags & FLAG_READONLY;
}
bool APEv2::Tag::IsItemReadOnly(size_t index)
{
if (index >= items.size())
return true;
return items[index]->IsReadOnly();
}