winamp/Src/Plugins/Input/in_wmvdrm/ASXLoader.cpp

491 lines
12 KiB
C++
Raw Normal View History

2024-09-24 12:54:57 +00:00
#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("&amp;", 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