tag.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #include "tag.h"
  2. #include "header.h"
  3. #include "flags.h"
  4. #include "nu/ns_wc.h"
  5. #include <limits.h>
  6. #include <strsafe.h>
  7. #include <stdint.h>
  8. /*
  9. http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification
  10. */
  11. APEv2::Tag::Tag()
  12. {
  13. flags = 0; // default to writing just a footer
  14. }
  15. int APEv2::Tag::Parse(const void *_data, size_t len)
  16. {
  17. char *data = (char *)_data;
  18. if (len < Header::SIZE)
  19. return APEV2_TOO_SMALL;
  20. char *headerStart = data+(len-Header::SIZE);
  21. Header footer(headerStart);
  22. if (footer.Valid()
  23. && !(len == Header::SIZE && footer.IsHeader())) // if all we have is this footer, we should check that it's not really a header
  24. {
  25. len -= Header::SIZE;
  26. char *dataStart = data;
  27. if (footer.HasHeader())
  28. {
  29. // TODO: validate header
  30. dataStart+= Header::SIZE;
  31. len-=Header::SIZE;
  32. }
  33. flags = footer.GetFlags();
  34. flags &= ~FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
  35. return ParseData(dataStart, len);
  36. }
  37. // ok, we didn't have a footer, so let's see if we have a header
  38. headerStart = data;
  39. Header header(headerStart);
  40. if (header.Valid())
  41. {
  42. len -= Header::SIZE;
  43. char *dataStart = data + Header::SIZE;
  44. if (header.HasFooter())
  45. {
  46. // TODO: validate footer
  47. // benski> cut... we got here because we didn't have a footer.
  48. //len-=Header::SIZE;
  49. }
  50. flags = header.GetFlags();
  51. flags |= FLAG_HEADER_NO_FOOTER; // winamp 5.54 had this flag reversed, so let's correct it
  52. return ParseData(dataStart, len);
  53. }
  54. return APEV2_FAILURE;
  55. }
  56. int APEv2::Tag::ParseData(const void *data, size_t len)
  57. {
  58. char *dataStart = (char *)data;
  59. while (len)
  60. {
  61. Item *item = new Item;
  62. size_t new_len = 0;
  63. void *new_data = 0;
  64. int ret = item->Read(dataStart, len, &new_data, &new_len);
  65. if (ret == APEV2_SUCCESS)
  66. {
  67. len = new_len;
  68. dataStart = (char *)new_data;
  69. items.push_back(item);
  70. }
  71. else
  72. {
  73. item->Release();
  74. return ret;
  75. }
  76. }
  77. return APEV2_SUCCESS;
  78. }
  79. int APEv2::Tag::GetString(const char *tag, wchar_t *data, size_t dataLen)
  80. {
  81. /* our UTF-8 to wchar_t conversion function wants an int for size, so we should explicitly check the size */
  82. if (dataLen > INT_MAX)
  83. return APEV2_FAILURE;
  84. for ( APEv2::Item *l_item : items )
  85. {
  86. /* check if it's a string first, and then match the key next (will be faster) */
  87. if ( l_item->IsString() && l_item->KeyMatch(tag, ITEM_KEY_COMPARE_CASE_INSENSITIVE))
  88. {
  89. void *item_data = 0;
  90. size_t item_dataLen = 0;
  91. if ( l_item->Get(&item_data, &item_dataLen) == APEV2_SUCCESS && item_dataLen < INT_MAX)
  92. {
  93. int signed_len = static_cast<int>(item_dataLen); // we checked against INT_MAX above so this conversion is safe
  94. if (MultiByteToWideCharSZ(CP_UTF8, 0, (LPCSTR)item_data, signed_len, data, (int)dataLen) != 0)
  95. return APEV2_SUCCESS;
  96. }
  97. return APEV2_NO_DATA;
  98. }
  99. }
  100. return APEV2_KEY_NOT_FOUND;
  101. }
  102. int APEv2::Tag::SetItemData(APEv2::Item *item, const wchar_t *data)
  103. {
  104. int utf16_data_cch = (int)wcslen(data);
  105. int item_dataLen = WideCharToMultiByte(CP_UTF8, 0, data, utf16_data_cch, 0, 0, NULL, NULL);
  106. if (!item_dataLen)
  107. return APEV2_FAILURE;
  108. char *item_data = (char *)calloc(item_dataLen, sizeof(char));
  109. if (item_data)
  110. {
  111. if (!WideCharToMultiByte(CP_UTF8, 0, data, utf16_data_cch, item_data, item_dataLen, NULL, NULL))
  112. {
  113. free(item_data);
  114. return APEV2_FAILURE;
  115. }
  116. item->Set(item_data, item_dataLen, FLAG_ITEM_TEXT);
  117. free(item_data);
  118. return APEV2_SUCCESS;
  119. }
  120. return APEV2_FAILURE;
  121. }
  122. int APEv2::Tag::SetString(const char *tag, const wchar_t *data)
  123. {
  124. for (auto it = items.begin(); it != items.end(); it++)
  125. {
  126. APEv2::Item* item = *it;
  127. if (item->KeyMatch(tag, ITEM_KEY_COMPARE_CASE_INSENSITIVE))
  128. {
  129. if (data && *data)
  130. {
  131. return SetItemData(item, data);
  132. }
  133. else
  134. {
  135. item->Release();
  136. it = items.erase(it);
  137. return APEV2_SUCCESS;
  138. }
  139. }
  140. }
  141. if (data && *data)
  142. {
  143. /* Not already in the list, so we need to add it */
  144. Item *newItem = new Item;
  145. if (newItem->SetKey(tag) == APEV2_SUCCESS && SetItemData(newItem, data) == APEV2_SUCCESS)
  146. {
  147. items.push_back(newItem);
  148. }
  149. else
  150. {
  151. newItem->Release();
  152. return APEV2_FAILURE;
  153. }
  154. }
  155. return APEV2_SUCCESS;
  156. }
  157. int APEv2::Tag::EnumValue(size_t i, const char **tag, wchar_t *data, size_t dataLen)
  158. {
  159. /* our UTF-8 to wchar_t conversion function wants an int for size, so we should explicitly check the size */
  160. if (dataLen > INT_MAX)
  161. return APEV2_FAILURE;
  162. if (i >= items.size())
  163. return APEV2_END_OF_ITEMS;
  164. APEv2::Item *item = items[i];
  165. item->GetKey(tag);
  166. if (!data || !dataLen) // if they don't want the data, go ahead and return now
  167. return APEV2_SUCCESS;
  168. data[0]=0;
  169. void *item_data = 0;
  170. size_t item_dataLen = 0;
  171. if (item->IsString())
  172. {
  173. if (item->Get(&item_data, &item_dataLen) == APEV2_SUCCESS && item_dataLen < INT_MAX)
  174. {
  175. int signed_len = static_cast<int>(item_dataLen); // we checked against INT_MAX above so this conversion is safe
  176. if (MultiByteToWideCharSZ(CP_UTF8, 0, (LPCSTR)item_data, signed_len, data, (int)dataLen) == 0)
  177. *data=0;
  178. }
  179. }
  180. else
  181. {
  182. // TODO: benski> hmmm
  183. StringCchCopyW(data, dataLen, L"[TODO: deal with binary]");
  184. }
  185. return APEV2_SUCCESS;
  186. }
  187. void APEv2::Tag::Clear()
  188. {
  189. for ( APEv2::Item *l_item : items )
  190. {
  191. l_item->Release();
  192. }
  193. items.clear();
  194. }
  195. int APEv2::Tag::RemoveItem(size_t index)
  196. {
  197. if (index >= items.size())
  198. return APEV2_END_OF_ITEMS;
  199. APEv2::Item *item = items[index];
  200. items.erase(items.begin() + index);
  201. item->Release();
  202. return APEV2_SUCCESS;
  203. }
  204. size_t APEv2::Tag::EncodeSize()
  205. {
  206. size_t totalSize=0;
  207. if (flags & FLAG_HEADER_HAS_HEADER)
  208. totalSize+=Header::SIZE;
  209. for ( APEv2::Item *l_item : items )
  210. {
  211. totalSize += l_item->EncodeSize();
  212. }
  213. if (!(flags & FLAG_HEADER_NO_FOOTER))
  214. totalSize+=Header::SIZE;
  215. return totalSize;
  216. }
  217. int APEv2::Tag::Encode(void *data, size_t len)
  218. {
  219. size_t totalSize=0;
  220. int8_t *ptr = (int8_t *)data;
  221. if (flags & FLAG_HEADER_HAS_HEADER)
  222. {
  223. HeaderData headerData = {0};
  224. headerData.size= (uint32_t)totalSize;
  225. if (!(flags & FLAG_HEADER_NO_FOOTER))
  226. headerData.size += Header::SIZE;
  227. headerData.items = (uint32_t)items.size();
  228. headerData.flags = (flags & FLAG_HEADER_ENCODE_MASK)|FLAG_HEADER_IS_HEADER;
  229. Header header(&headerData);
  230. int ret = header.Encode(ptr, len);
  231. if (ret != APEV2_SUCCESS)
  232. return ret;
  233. ptr += Header::SIZE;
  234. len -= Header::SIZE;
  235. }
  236. for ( APEv2::Item *l_item : items )
  237. {
  238. int ret = l_item->Encode(ptr, len);
  239. if (ret!= APEV2_SUCCESS)
  240. return ret;
  241. size_t itemSize = l_item->EncodeSize();
  242. len-=itemSize;
  243. ptr+=itemSize;
  244. totalSize+=itemSize;
  245. }
  246. if (!(flags & FLAG_HEADER_NO_FOOTER))
  247. {
  248. HeaderData footerData = {0};
  249. footerData.size= (uint32_t)totalSize + Header::SIZE;
  250. footerData.items = (uint32_t)items.size();
  251. footerData.flags = (flags & FLAG_HEADER_ENCODE_MASK);
  252. Header footer(&footerData);
  253. int ret = footer.Encode(ptr, len);
  254. if (ret != APEV2_SUCCESS)
  255. return ret;
  256. }
  257. return APEV2_SUCCESS;
  258. }
  259. int APEv2::Tag::SetKeyValueByIndex(size_t index, const char *key, const wchar_t *data)
  260. {
  261. if (index >= items.size())
  262. return APEV2_END_OF_ITEMS;
  263. // TODO: check for duplicate key
  264. items[index]->SetKey(key);
  265. return SetItemData(items[index], data);
  266. }
  267. APEv2::Item *APEv2::Tag::AddItem()
  268. {
  269. Item *newItem = new Item;
  270. items.push_back(newItem);
  271. return newItem;
  272. }
  273. int APEv2::Tag::SetFlags(uint32_t newflags, uint32_t mask)
  274. {
  275. flags = (flags & ~mask) | newflags;
  276. return APEV2_SUCCESS;
  277. }
  278. int APEv2::Tag::FindItemByKey(const char *key, size_t *index, int compare)
  279. {
  280. for (size_t i=0;i!=items.size();i++)
  281. {
  282. if (items[i]->KeyMatch(key, compare))
  283. {
  284. *index = i;
  285. return APEV2_SUCCESS;
  286. }
  287. }
  288. return APEV2_KEY_NOT_FOUND;
  289. }
  290. size_t APEv2::Tag::GetNumItems()
  291. {
  292. return items.size();
  293. }
  294. bool APEv2::Tag::IsReadOnly()
  295. {
  296. return flags & FLAG_READONLY;
  297. }
  298. bool APEv2::Tag::IsItemReadOnly(size_t index)
  299. {
  300. if (index >= items.size())
  301. return true;
  302. return items[index]->IsReadOnly();
  303. }