diff options
Diffstat (limited to 'Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp')
-rw-r--r-- | Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp b/Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp new file mode 100644 index 00000000..485e0f77 --- /dev/null +++ b/Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp @@ -0,0 +1,490 @@ +#include "main.h" +#include "api.h" +#include "ASXLoader.h" +#include <stdio.h> +#include "../nu/AutoWide.h" +#include "../xml/ifc_xmlreadercallback.h" +#include "../xml/obj_xml.h" +#include "api.h" +#include <api/service/waservicefactory.h> +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" +#include "../nu/AutoChar.h" +#include "../Winamp/strutil.h" +#include <strsafe.h> +#include "XMLString.h" + +void SetUserAgent(api_httpreceiver *http) +{ + char agent[256] = {0}; + StringCchPrintfA(agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString()); + http->addheader(agent); +} + +class ASXInfo : public ifc_plentryinfo +{ +public: + ASXInfo() + { + memset( returnTemp, 0, sizeof( returnTemp ) ); + } + + const wchar_t *GetExtendedInfo( const wchar_t *parameter ) + { + if ( !_wcsicmp( parameter, L"context" ) ) + { + if ( isRadio ) + return L"radio"; + } + else if ( !_wcsicmp( parameter, L"repeat" ) ) + { + if ( repeat ) + { + StringCchPrintfW( returnTemp, 20, L"%d", repeat ); + + return returnTemp; + } + } + + return 0; + } + + bool isRadio = false; + int repeat = 0; + +protected: + RECVS_DISPATCH; + + wchar_t returnTemp[ 20 ]; +}; + +#define CBCLASS ASXInfo +START_DISPATCH; +CB( IFC_PLENTRYINFO_GETEXTENDEDINFO, GetExtendedInfo ) +END_DISPATCH; +#undef CBCLASS + +class ASXXML : public ifc_xmlreadercallback +{ +public: + ASXXML(ifc_playlistloadercallback *_playlist, const wchar_t *_root, obj_xml *_parser) : playlist(_playlist), rootPath(_root), parser(_parser) + { + } + + void OnFileHelper(ifc_playlistloadercallback *playlist, const wchar_t *filename, const wchar_t *title, int length, ifc_plentryinfo *extraInfo) + { + if (wcsstr(filename, L"://") || PathIsRootW(filename)) + { + playlist->OnFile(filename, title, length, extraInfo); + } + else + { + wchar_t fullPath[MAX_PATH] = {0}, canonicalizedPath[MAX_PATH] = {0}; + PathCombineW(fullPath, rootPath, filename); + PathCanonicalizeW(canonicalizedPath, fullPath); + playlist->OnFile(canonicalizedPath, title, length, extraInfo); + } + } + + void StartTag(const wchar_t *xmlpath, const wchar_t *xmltag, ifc_xmlreaderparams *params) + { + if (!_wcsicmp(xmltag, L"ENTRYREF")) + { + const wchar_t *url = params->getItemValue(L"HREF"); + + const wchar_t *titleHack = params->getItemValue(L"CLIENTBIND"); + int lengthHack = -1; + wchar_t titleBuf[256] = L""; + + if (titleHack) + { + // get the length out of the parantheses + StringCchCopyW(titleBuf, 256, titleHack); + wchar_t *end = titleBuf + lstrlenW(titleBuf); + while (end && *end && *end != '(' && end != titleBuf) + end = CharPrevW(titleBuf, end); + + *end = 0; + end++; + lengthHack = _wtoi(end); + } + + wchar_t filename[FILENAME_SIZE] = {0}; + if (wcschr(url, L'?')) + StringCchPrintfW(filename, FILENAME_SIZE, L"%s&=.asx", url); + else + StringCchPrintfW(filename, FILENAME_SIZE, L"%s?.asx", url); + + OnFileHelper(playlist, filename, titleBuf, lengthHack*1000, &info); + + } + else if (!_wcsicmp(xmlpath, L"ASX\fENTRY\fREF") || !_wcsicmp(xmlpath, L"ASX\fREPEAT\fENTRY\fREF")) + { + const wchar_t *track = params->getItemValue(L"HREF"); + wchar_t fullTitle[128] = {0}, fullFilename[FILENAME_SIZE] = {0}; + // if there is no extension given, we need to add ?.wma or &=.wma to the end of the URL + // this could be 2 lines of code if that wasn't the case :( + if (track) + { + const wchar_t *trackTitle = 0; + if (title.GetString()[0] && artist.GetString()[0]) + { + StringCchPrintfW(fullTitle, 128, L"%s - %s", artist.GetString(), title.GetString()); + trackTitle = fullTitle; + } + if (!_wcsnicmp(track, L"http://", 7)) + { + const wchar_t *end = scanstr_backcW(track, L"/.", 0); + if (!end || *end == L'/') + { + if (wcschr(track, L'?')) + StringCchPrintfW(fullFilename, FILENAME_SIZE, L"%s&=.wma", track); + else + StringCchPrintfW(fullFilename, FILENAME_SIZE, L"%s?.wma", track); + track = fullFilename; + } + } + + OnFileHelper(playlist, track, trackTitle, -1, &info); + } + } + else if (!_wcsicmp(xmltag, L"REPEAT")) + { + const wchar_t *param; + if (param = params->getItemValue(L"count")) + { + info.repeat = _wtoi(param); + } + else + info.repeat = -1; + } + else if (!_wcsicmp(xmltag, L"PARAM")) + { + const wchar_t *param; + if (param = params->getItemValue(L"name")) + { + if (!_wcsicmp(param, L"context")) + { + const wchar_t * value = params->getItemValue(L"value"); + if (!_wcsicmp(value, L"station")) + info.isRadio = true; + } + else if (!_wcsicmp(param, L"encoding")) + { + const wchar_t *value=params->getItemValue(L"value"); + if (value) + parser->xmlreader_setEncoding(value); // I hope we can set it on the fly like this! + } + } + } + } + + void EndTag(const wchar_t *xmlpath, const wchar_t *xmltag) + { + if (!_wcsicmp(xmltag, L"REPEAT")) + { + info.repeat = 0; + } + } + ifc_playlistloadercallback *playlist; + XMLString title, artist; + ASXInfo info; + const wchar_t *rootPath; + obj_xml *parser; +protected: + RECVS_DISPATCH; + +}; + +#define CBCLASS ASXXML +START_DISPATCH; +VCB(ONSTARTELEMENT, StartTag) +VCB(ONENDELEMENT, EndTag) +END_DISPATCH; +#undef CBCLASS + + +/* +TODO: + +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 + +maybe have separate XML callbacks for metadata (title, author, shit like that) so that logic can be separated from the main ASX logic +*/ + +// ASX isn't really XML. That means we need to URL-encode the text so our XML parser doesn't choke +// if microsoft followed standards, the world would be a better place. +int ASXLoader::GayASX_to_XML_converter(obj_xml *parser, char *buffer, int len) +{ + // benski> I have no idea if ASX is always ASCII, or if it's UTF-8 or what. + // 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 + char *start = buffer; + int sofar = 0; + for (int i = 0;i < len;i++) + { + if (buffer[i] == '&') + { + if (sofar) + { + if (parser->xmlreader_feed(start, sofar) != API_XML_SUCCESS) + return API_XML_FAILURE; + } + + if (parser->xmlreader_feed("&", 5) != API_XML_SUCCESS) // no null terminator + return API_XML_FAILURE; + start = &buffer[i + 1]; + sofar = 0; + } + else + { + /** + * ok, this might look really weird + * but ASX doesn't have case sensitivity + * so lots of playlists have things like + * <title>This is the title</Title> + * and so we have to accomodate + * for this nonsense + */ + + if (inTag && !inQuotes) + buffer[i] = toupper(buffer[i]); + + if (buffer[i] == '>') + { + inTag=false; + } + else if (buffer[i] == '<') + { + inTag=true; + } + + // dro> only do uppercase handling on parts of the tag not inbetween quotes + // (some servers just don't like having the urls case messed with, the swines) + if (buffer[i] == '"') + { + if(!inQuotes) + inQuotes=true; + else + inQuotes=false; + } + + sofar++; + } + } + if (sofar && parser->xmlreader_feed(start, sofar) != API_XML_SUCCESS) + return API_XML_FAILURE; + OutputDebugStringA(buffer); + return API_XML_SUCCESS; +} + + +#define HTTP_BUFFER_SIZE 16384 +int ASXLoader::FeedXMLHTTP(api_httpreceiver *http, obj_xml *parser, bool *noData) +{ + char downloadedData[HTTP_BUFFER_SIZE] = {0}; + int xmlResult = API_XML_SUCCESS; + int downloadSize = http->get_bytes(downloadedData, HTTP_BUFFER_SIZE); + if (downloadSize) + { + xmlResult = GayASX_to_XML_converter(parser, downloadedData, downloadSize); + *noData=false; + } + else + *noData = true; + + return xmlResult; +} + +void ASXLoader::RunXMLDownload(api_httpreceiver *http, obj_xml *parser) +{ +int ret; + bool noData; + do + { + Sleep(50); + ret = http->run(); + if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS) + return ; + } + while (ret == HTTPRECEIVER_RUN_OK); + + // finish off the data + do + { + if (FeedXMLHTTP(http, parser, &noData) != API_XML_SUCCESS) + return ; + } while (!noData); + + parser->xmlreader_feed(0, 0); +} + +int ASXLoader::LoadFile(obj_xml *parser, const wchar_t *filename) +{ + HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); + + if (file == INVALID_HANDLE_VALUE) + return IFC_PLAYLISTLOADER_FAILED; + + while (true) + { + char data[1024] = {0}; + DWORD bytesRead = 0; + if (ReadFile(file, data, 1024, &bytesRead, NULL) && bytesRead) + { + if (GayASX_to_XML_converter(parser, data, bytesRead) != API_XML_SUCCESS) + { + CloseHandle(file); + return IFC_PLAYLISTLOADER_FAILED; + } + } + else + break; + } + + CloseHandle(file); + if (parser->xmlreader_feed(0, 0) != API_XML_SUCCESS) + return IFC_PLAYLISTLOADER_FAILED; + + return IFC_PLAYLISTLOADER_SUCCESS; +} + + +int ASXLoader::LoadURL(obj_xml *parser, const wchar_t *url) +{ + api_httpreceiver *http = 0; + waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + if (!http) + return IFC_PLAYLISTLOADER_FAILED; + http->AllowCompression(); + http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, winamp.GetProxy()); + SetUserAgent(http); + http->connect(AutoChar(url)); + int ret; + + do + { + Sleep(10); + ret = http->run(); + if (ret == -1) // connection failed + break; + + // ---- check our reply code ---- + int replycode = http->getreplycode(); + switch (replycode) + { + case 0: + case 100: + break; + case 200: + { + RunXMLDownload(http, parser); + sf->releaseInterface(http); + return IFC_PLAYLISTLOADER_SUCCESS; + } + break; + default: + sf->releaseInterface(http); + return IFC_PLAYLISTLOADER_FAILED; + } + } + while (ret == HTTPRECEIVER_RUN_OK); + //const char *er = http->geterrorstr(); + sf->releaseInterface(http); + return IFC_PLAYLISTLOADER_FAILED; +} + +static int loadasxv2fn(const wchar_t *filename, ifc_playlistloadercallback *playlist) +{ + int i=1; + wchar_t ref[FILENAME_SIZE] = {0}; + wchar_t key[100] = {0}; + while (1) + { + StringCchPrintfW(key, 100, L"Ref%d", i++); + GetPrivateProfileStringW(L"Reference", key, L"?", ref, FILENAME_SIZE, filename); + if (!lstrcmpiW(ref, L"?")) + break; + else + { + if (!_wcsnicmp(ref, L"http://", 7)) + { + const wchar_t *end = scanstr_backcW(ref, L"/.", 0); + if (!end || *end == L'/') + { + if (wcschr(ref, L'?')) + StringCchCatW(ref, FILENAME_SIZE, L"&=.wma"); + else + StringCchCatW(ref, FILENAME_SIZE, L"?.wma"); + } + } + + playlist->OnFile(ref, 0, 0, 0); + } + + } + return IFC_PLAYLISTLOADER_SUCCESS; +} + +int ASXLoader::Load(const wchar_t *filename, ifc_playlistloadercallback *playlist) +{ + obj_xml *parser = 0; + waServiceFactory *parserFactory = 0; + + HANDLE quickTest = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + if (quickTest != INVALID_HANDLE_VALUE) + { + char reference[11] = {0}; + DWORD bytesRead=0; + ReadFile(quickTest, reference, 11, &bytesRead, 0); + CloseHandle(quickTest); + if (bytesRead == 11 && !_strnicmp(reference, "[Reference]", 11)) + return loadasxv2fn(filename, playlist); + } + + parserFactory = plugin.service->service_getServiceByGuid(obj_xmlGUID); + if (parserFactory) + parser = (obj_xml *)parserFactory->getInterface(); + + if (parser) + { + wchar_t rootPath[MAX_PATH] = {0}; + const wchar_t *callbackPath = playlist->GetBasePath(); + if (callbackPath) + lstrcpynW(rootPath, callbackPath, MAX_PATH); + else + { + lstrcpynW(rootPath, filename, MAX_PATH); + PathRemoveFileSpecW(rootPath); + } + + ASXXML asxXml(playlist, rootPath, parser); + parser->xmlreader_registerCallback(L"ASX\f*", &asxXml); + parser->xmlreader_registerCallback(L"ASX\fENTRY\fTITLE", &asxXml.title); + parser->xmlreader_registerCallback(L"ASX\fENTRY\fAUTHOR", &asxXml.artist); + parser->xmlreader_open(); + parser->xmlreader_setEncoding(L"windows-1252"); + + int ret; + if (wcsstr(filename, L"://")) + ret = LoadURL(parser, filename); + else + ret = LoadFile(parser, filename); + + parser->xmlreader_unregisterCallback(&asxXml); + parser->xmlreader_unregisterCallback(&asxXml.title); + parser->xmlreader_unregisterCallback(&asxXml.artist); + parser->xmlreader_close(); + parserFactory->releaseInterface(parser); + return ret; + } + + + return IFC_PLAYLISTLOADER_FAILED; +} + +#define CBCLASS ASXLoader +START_DISPATCH; +CB(IFC_PLAYLISTLOADER_LOAD, Load) +END_DISPATCH; +#undef CBCLASS |