123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- #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();
- }
|