ASXLoader.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. #include "main.h"
  2. #include "api.h"
  3. #include "ASXLoader.h"
  4. #include <stdio.h>
  5. #include "../nu/AutoWide.h"
  6. #include "../xml/ifc_xmlreadercallback.h"
  7. #include "../xml/obj_xml.h"
  8. #include "api.h"
  9. #include <api/service/waservicefactory.h>
  10. #include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
  11. #include "../nu/AutoChar.h"
  12. #include "../Winamp/strutil.h"
  13. #include <strsafe.h>
  14. #include "XMLString.h"
  15. void SetUserAgent(api_httpreceiver *http)
  16. {
  17. char agent[256] = {0};
  18. StringCchPrintfA(agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString());
  19. http->addheader(agent);
  20. }
  21. class ASXInfo : public ifc_plentryinfo
  22. {
  23. public:
  24. ASXInfo()
  25. {
  26. memset( returnTemp, 0, sizeof( returnTemp ) );
  27. }
  28. const wchar_t *GetExtendedInfo( const wchar_t *parameter )
  29. {
  30. if ( !_wcsicmp( parameter, L"context" ) )
  31. {
  32. if ( isRadio )
  33. return L"radio";
  34. }
  35. else if ( !_wcsicmp( parameter, L"repeat" ) )
  36. {
  37. if ( repeat )
  38. {
  39. StringCchPrintfW( returnTemp, 20, L"%d", repeat );
  40. return returnTemp;
  41. }
  42. }
  43. return 0;
  44. }
  45. bool isRadio = false;
  46. int repeat = 0;
  47. protected:
  48. RECVS_DISPATCH;
  49. wchar_t returnTemp[ 20 ];
  50. };
  51. #define CBCLASS ASXInfo
  52. START_DISPATCH;
  53. CB( IFC_PLENTRYINFO_GETEXTENDEDINFO, GetExtendedInfo )
  54. END_DISPATCH;
  55. #undef CBCLASS
  56. class ASXXML : public ifc_xmlreadercallback
  57. {
  58. public:
  59. ASXXML(ifc_playlistloadercallback *_playlist, const wchar_t *_root, obj_xml *_parser) : playlist(_playlist), rootPath(_root), parser(_parser)
  60. {
  61. }
  62. void OnFileHelper(ifc_playlistloadercallback *playlist, const wchar_t *filename, const wchar_t *title, int length, ifc_plentryinfo *extraInfo)
  63. {
  64. if (wcsstr(filename, L"://") || PathIsRootW(filename))
  65. {
  66. playlist->OnFile(filename, title, length, extraInfo);
  67. }
  68. else
  69. {
  70. wchar_t fullPath[MAX_PATH] = {0}, canonicalizedPath[MAX_PATH] = {0};
  71. PathCombineW(fullPath, rootPath, filename);
  72. PathCanonicalizeW(canonicalizedPath, fullPath);
  73. playlist->OnFile(canonicalizedPath, title, length, extraInfo);
  74. }
  75. }
  76. void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params)
  77. {
  78. if (!_wcsicmp(xmltag, L"ENTRYREF"))
  79. {
  80. const wchar_t *url = params->getItemValue(L"HREF");
  81. const wchar_t *titleHack = params->getItemValue(L"CLIENTBIND");
  82. int lengthHack = -1;
  83. wchar_t titleBuf[256] = L"";
  84. if (titleHack)
  85. {
  86. // get the length out of the parantheses
  87. StringCchCopyW(titleBuf, 256, titleHack);
  88. wchar_t *end = titleBuf + lstrlenW(titleBuf);
  89. while (end && *end && *end != '(' && end != titleBuf)
  90. end = CharPrevW(titleBuf, end);
  91. *end = 0;
  92. end++;
  93. lengthHack = _wtoi(end);
  94. }
  95. wchar_t filename[FILENAME_SIZE] = {0};
  96. if (wcschr(url, L'?'))
  97. StringCchPrintfW(filename, FILENAME_SIZE, L"%s&=.asx", url);
  98. else
  99. StringCchPrintfW(filename, FILENAME_SIZE, L"%s?.asx", url);
  100. OnFileHelper(playlist, filename, titleBuf, lengthHack*1000, &info);
  101. }
  102. else if (!_wcsicmp(xmlpath, L"ASX\fENTRY\fREF") || !_wcsicmp(xmlpath, L"ASX\fREPEAT\fENTRY\fREF"))
  103. {
  104. const wchar_t *track = params->getItemValue(L"HREF");
  105. wchar_t fullTitle[128] = {0}, fullFilename[FILENAME_SIZE] = {0};
  106. // if there is no extension given, we need to add ?.wma or &=.wma to the end of the URL
  107. // this could be 2 lines of code if that wasn't the case :(
  108. if (track)
  109. {
  110. const wchar_t *trackTitle = 0;
  111. if (title.GetString()[0] && artist.GetString()[0])
  112. {
  113. StringCchPrintfW(fullTitle, 128, L"%s - %s", artist.GetString(), title.GetString());
  114. trackTitle = fullTitle;
  115. }
  116. if (!_wcsnicmp(track, L"http://", 7))
  117. {
  118. const wchar_t *end = scanstr_backcW(track, L"/.", 0);
  119. if (!end || *end == L'/')
  120. {
  121. if (wcschr(track, L'?'))
  122. StringCchPrintfW(fullFilename, FILENAME_SIZE, L"%s&=.wma", track);
  123. else
  124. StringCchPrintfW(fullFilename, FILENAME_SIZE, L"%s?.wma", track);
  125. track = fullFilename;
  126. }
  127. }
  128. OnFileHelper(playlist, track, trackTitle, -1, &info);
  129. }
  130. }
  131. else if (!_wcsicmp(xmltag, L"REPEAT"))
  132. {
  133. const wchar_t *param;
  134. if (param = params->getItemValue(L"count"))
  135. {
  136. info.repeat = _wtoi(param);
  137. }
  138. else
  139. info.repeat = -1;
  140. }
  141. else if (!_wcsicmp(xmltag, L"PARAM"))
  142. {
  143. const wchar_t *param;
  144. if (param = params->getItemValue(L"name"))
  145. {
  146. if (!_wcsicmp(param, L"context"))
  147. {
  148. const wchar_t * value = params->getItemValue(L"value");
  149. if (!_wcsicmp(value, L"station"))
  150. info.isRadio = true;
  151. }
  152. else if (!_wcsicmp(param, L"encoding"))
  153. {
  154. const wchar_t *value=params->getItemValue(L"value");
  155. if (value)
  156. parser->xmlreader_setEncoding(value); // I hope we can set it on the fly like this!
  157. }
  158. }
  159. }
  160. }
  161. void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag)
  162. {
  163. if (!_wcsicmp(xmltag, L"REPEAT"))
  164. {
  165. info.repeat = 0;
  166. }
  167. }
  168. ifc_playlistloadercallback *playlist;
  169. XMLString title, artist;
  170. ASXInfo info;
  171. const wchar_t *rootPath;
  172. obj_xml *parser;
  173. protected:
  174. RECVS_DISPATCH;
  175. };
  176. #define CBCLASS ASXXML
  177. START_DISPATCH;
  178. VCB(ONSTARTELEMENT, StartTag)
  179. VCB(ONENDELEMENT, EndTag)
  180. END_DISPATCH;
  181. #undef CBCLASS
  182. /*
  183. TODO:
  184. don't add tracks until all parts of the "ENTRY" tag are processed. There are some ASX playlists where the title comes AFTER the ref's
  185. maybe have separate XML callbacks for metadata (title, author, shit like that) so that logic can be separated from the main ASX logic
  186. */
  187. // ASX isn't really XML. That means we need to URL-encode the text so our XML parser doesn't choke
  188. // if microsoft followed standards, the world would be a better place.
  189. int ASXLoader::GayASX_to_XML_converter(obj_xml *parser, char *buffer, int len)
  190. {
  191. // benski> I have no idea if ASX is always ASCII, or if it's UTF-8 or what.
  192. // but really I can't be bothered with Microsoft's lameness right now, so we'll assume it's local code page for the time being
  193. char *start = buffer;
  194. int sofar = 0;
  195. for (int i = 0;i < len;i++)
  196. {
  197. if (buffer[i] == '&')
  198. {
  199. if (sofar)
  200. {
  201. if (parser->xmlreader_feed(start, sofar) != API_XML_SUCCESS)
  202. return API_XML_FAILURE;
  203. }
  204. if (parser->xmlreader_feed("&amp;", 5) != API_XML_SUCCESS) // no null terminator
  205. return API_XML_FAILURE;
  206. start = &buffer[i + 1];
  207. sofar = 0;
  208. }
  209. else
  210. {
  211. /**
  212. * ok, this might look really weird
  213. * but ASX doesn't have case sensitivity
  214. * so lots of playlists have things like
  215. * <title>This is the title</Title>
  216. * and so we have to accomodate
  217. * for this nonsense
  218. */
  219. if (inTag && !inQuotes)
  220. buffer[i] = toupper(buffer[i]);
  221. if (buffer[i] == '>')
  222. {
  223. inTag=false;
  224. }
  225. else if (buffer[i] == '<')
  226. {
  227. inTag=true;
  228. }
  229. // dro> only do uppercase handling on parts of the tag not inbetween quotes
  230. // (some servers just don't like having the urls case messed with, the swines)
  231. if (buffer[i] == '"')
  232. {
  233. if(!inQuotes)
  234. inQuotes=true;
  235. else
  236. inQuotes=false;
  237. }
  238. sofar++;
  239. }
  240. }
  241. if (sofar && parser->xmlreader_feed(start, sofar) != API_XML_SUCCESS)
  242. return API_XML_FAILURE;
  243. OutputDebugStringA(buffer);
  244. return API_XML_SUCCESS;
  245. }
  246. #define HTTP_BUFFER_SIZE 16384
  247. int ASXLoader::FeedXMLHTTP(api_httpreceiver *http, obj_xml *parser, bool *noData)
  248. {
  249. char downloadedData[HTTP_BUFFER_SIZE] = {0};
  250. int xmlResult = API_XML_SUCCESS;
  251. int downloadSize = http->get_bytes(downloadedData, HTTP_BUFFER_SIZE);
  252. if (downloadSize)
  253. {
  254. xmlResult = GayASX_to_XML_converter(parser, downloadedData, downloadSize);
  255. *noData=false;
  256. }
  257. else
  258. *noData = true;
  259. return xmlResult;
  260. }
  261. void ASXLoader::RunXMLDownload(api_httpreceiver *http, obj_xml *parser)
  262. {
  263. int ret;
  264. bool noData;
  265. do
  266. {
  267. Sleep(50);
  268. ret = http->run();
  269. if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS)
  270. return ;
  271. }
  272. while (ret == HTTPRECEIVER_RUN_OK);
  273. // finish off the data
  274. do
  275. {
  276. if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS)
  277. return ;
  278. } while (!noData);
  279. parser->xmlreader_feed(0, 0);
  280. }
  281. int ASXLoader::LoadFile(obj_xml *parser, const wchar_t *filename)
  282. {
  283. HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
  284. if (file == INVALID_HANDLE_VALUE)
  285. return IFC_PLAYLISTLOADER_FAILED;
  286. while (true)
  287. {
  288. char data[1024] = {0};
  289. DWORD bytesRead = 0;
  290. if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead)
  291. {
  292. if (GayASX_to_XML_converter(parser, data, bytesRead) != API_XML_SUCCESS)
  293. {
  294. CloseHandle(file);
  295. return IFC_PLAYLISTLOADER_FAILED;
  296. }
  297. }
  298. else
  299. break;
  300. }
  301. CloseHandle(file);
  302. if (parser->xmlreader_feed(0, 0) != API_XML_SUCCESS)
  303. return IFC_PLAYLISTLOADER_FAILED;
  304. return IFC_PLAYLISTLOADER_SUCCESS;
  305. }
  306. int ASXLoader::LoadURL(obj_xml *parser, const wchar_t *url)
  307. {
  308. api_httpreceiver *http = 0;
  309. waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID);
  310. if (sf) http = (api_httpreceiver *)sf->getInterface();
  311. if (!http)
  312. return IFC_PLAYLISTLOADER_FAILED;
  313. http->AllowCompression();
  314. http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, winamp.GetProxy());
  315. SetUserAgent(http);
  316. http->connect(AutoChar(url));
  317. int ret;
  318. do
  319. {
  320. Sleep(10);
  321. ret = http->run();
  322. if (ret == -1) // connection failed
  323. break;
  324. // ---- check our reply code ----
  325. int replycode = http->getreplycode();
  326. switch (replycode)
  327. {
  328. case 0:
  329. case 100:
  330. break;
  331. case 200:
  332. {
  333. RunXMLDownload(http, parser);
  334. sf->releaseInterface(http);
  335. return IFC_PLAYLISTLOADER_SUCCESS;
  336. }
  337. break;
  338. default:
  339. sf->releaseInterface(http);
  340. return IFC_PLAYLISTLOADER_FAILED;
  341. }
  342. }
  343. while (ret == HTTPRECEIVER_RUN_OK);
  344. //const char *er = http->geterrorstr();
  345. sf->releaseInterface(http);
  346. return IFC_PLAYLISTLOADER_FAILED;
  347. }
  348. static int loadasxv2fn(const wchar_t *filename, ifc_playlistloadercallback *playlist)
  349. {
  350. int i=1;
  351. wchar_t ref[FILENAME_SIZE] = {0};
  352. wchar_t key[100] = {0};
  353. while (1)
  354. {
  355. StringCchPrintfW(key, 100, L"Ref%d", i++);
  356. GetPrivateProfileStringW(L"Reference", key, L"?", ref, FILENAME_SIZE, filename);
  357. if (!lstrcmpiW(ref, L"?"))
  358. break;
  359. else
  360. {
  361. if (!_wcsnicmp(ref, L"http://", 7))
  362. {
  363. const wchar_t *end = scanstr_backcW(ref, L"/.", 0);
  364. if (!end || *end == L'/')
  365. {
  366. if (wcschr(ref, L'?'))
  367. StringCchCatW(ref, FILENAME_SIZE, L"&=.wma");
  368. else
  369. StringCchCatW(ref, FILENAME_SIZE, L"?.wma");
  370. }
  371. }
  372. playlist->OnFile(ref, 0, 0, 0);
  373. }
  374. }
  375. return IFC_PLAYLISTLOADER_SUCCESS;
  376. }
  377. int ASXLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist)
  378. {
  379. obj_xml *parser = 0;
  380. waServiceFactory *parserFactory = 0;
  381. HANDLE quickTest = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
  382. if (quickTest != INVALID_HANDLE_VALUE)
  383. {
  384. char reference[11] = {0};
  385. DWORD bytesRead=0;
  386. ReadFile(quickTest, reference, 11, &bytesRead, 0);
  387. CloseHandle(quickTest);
  388. if (bytesRead == 11 && !_strnicmp(reference, "[Reference]", 11))
  389. return loadasxv2fn(filename, playlist);
  390. }
  391. parserFactory = plugin.service->service_getServiceByGuid(obj_xmlGUID);
  392. if (parserFactory)
  393. parser = (obj_xml *)parserFactory->getInterface();
  394. if (parser)
  395. {
  396. wchar_t rootPath[MAX_PATH] = {0};
  397. const wchar_t *callbackPath = playlist->GetBasePath();
  398. if (callbackPath)
  399. lstrcpynW(rootPath, callbackPath, MAX_PATH);
  400. else
  401. {
  402. lstrcpynW(rootPath, filename, MAX_PATH);
  403. PathRemoveFileSpecW(rootPath);
  404. }
  405. ASXXML asxXml(playlist, rootPath, parser);
  406. parser->xmlreader_registerCallback(L"ASX\f*", &asxXml);
  407. parser->xmlreader_registerCallback(L"ASX\fENTRY\fTITLE", &asxXml.title);
  408. parser->xmlreader_registerCallback(L"ASX\fENTRY\fAUTHOR", &asxXml.artist);
  409. parser->xmlreader_open();
  410. parser->xmlreader_setEncoding(L"windows-1252");
  411. int ret;
  412. if (wcsstr(filename, L"://"))
  413. ret = LoadURL(parser, filename);
  414. else
  415. ret = LoadFile(parser, filename);
  416. parser->xmlreader_unregisterCallback(&asxXml);
  417. parser->xmlreader_unregisterCallback(&asxXml.title);
  418. parser->xmlreader_unregisterCallback(&asxXml.artist);
  419. parser->xmlreader_close();
  420. parserFactory->releaseInterface(parser);
  421. return ret;
  422. }
  423. return IFC_PLAYLISTLOADER_FAILED;
  424. }
  425. #define CBCLASS ASXLoader
  426. START_DISPATCH;
  427. CB(IFC_PLAYLISTLOADER_LOAD, Load)
  428. END_DISPATCH;
  429. #undef CBCLASS