Hi, You guys may be interested in a patch that I have developed to improve HTTP streaming support. A little background, I run an Apache web server at home with all my mp3s served using the Apache::MP3 module so I can listen to my collection while I'm at work (see http://modperl.com:9000/Songs/ for an Apache::MP3 demo). In order to protect my files from unauthorised use all accesses need to be authenticated with a user and password. Unfortunately I could not find an mp3 player that could handle this, so I bit the bullet and added the required support myself.
The patch itself consists of two parts: The first part fixes a bug in EncodeURI() and adds user/password authentication support when opening the stream, using the standard http://user:password@host:port/file syntax. The second part attempts to extract the track name and number from the URL to display in the playlist before the stream is opened. diff -NurdbX excl.txt freeamp.orig/io/http/httpinput.cpp freeamp/io/http/httpinput.cpp --- freeamp.orig/io/http/httpinput.cpp Tue Oct 16 06:58:12 2001 +++ freeamp/io/http/httpinput.cpp Sat Apr 27 00:07:22 2002 @@ -467,8 +467,8 @@ // Do not replace %## sequences -- they are already encoded and // ready to roll if (URI[convert] == '%' && URI.length() - convert > 2 && - isdigit(URI[convert + 1]) && - isdigit(URI[convert + 2])) + isxdigit(URI[convert + 1]) && + isxdigit(URI[convert + 2])) { convert++; continue; @@ -483,6 +483,131 @@ } } + +static +char * +Base64Encode +( char *strDest, + char *strSrce +) +{ static const char alphabet[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char *strSave = strDest; + unsigned long dwBits; + int nCount; + + nCount = 0; + dwBits = 0; + + while (*strSrce) + { + dwBits <<= 8; + dwBits |= (unsigned char) *strSrce++; + + if (++nCount == 3) + { + *strDest++ = alphabet[(dwBits >> 18) ]; + *strDest++ = alphabet[(dwBits >> 12) & 0x3f]; + *strDest++ = alphabet[(dwBits >> 6) & 0x3f]; + *strDest++ = alphabet[(dwBits ) & 0x3f]; + dwBits = 0; + nCount = 0; + } + } + + if (nCount) + { + dwBits <<= (nCount == 1) ? 16 : 8; + + *strDest++ = alphabet[(dwBits >> 18) ]; + *strDest++ = alphabet[(dwBits >> 12) & 0x3f]; + + if (nCount == 1) + { + *strDest++ = '='; + } + else + { + *strDest++ = alphabet[(dwBits >> 6) & 0x3f]; + } + + *strDest++ = '='; + } + + *strDest = '\0'; + + return strSave; +} + + +static +char * +ParseURI +( char *szURI, + char *szHostName, + char *szAuth, + unsigned *puiPort +) +{ + // Ignore leading white space + while (*szURI && *szURI <= ' ') + szURI++; + + if (strncasecmp(szURI, "http://", 7)) + return NULL; + + szURI += 7; + + // No user name and password by default + szAuth[0] = '\0'; + + // Find start of file name + char *szFile = strchr(szURI, '/'); + + // If no file name + if (szFile == NULL) + { + szFile = strchr(szURI, '?'); + if (szFile == NULL) + { + // Point to end of URI where file name should go + szFile = szURI + strlen(szURI); + } + } + + // Find end of user name and password + char *at = strchr(szURI, '@'); + + if (at) + { + // If '@' not part of file name + if (szFile > at) + { + // Temporarily truncate at '@' + *at = '\0'; + + // Return username and password base64 encoded + Base64Encode(szAuth, szURI); + + // Restore + *at = '@'; + + // Skip username and password + szURI = at + 1; + } + } + + int nArgs = sscanf(szURI, "%[^:/?]:%u", szHostName, puiPort); + + if (nArgs < 1) + return NULL; + + if (nArgs == 1) + *puiPort = iHttpPort; + + return szFile; +} + + Error HttpInput::Open(void) { @@ -501,30 +626,28 @@ bool bUseTitleStreaming = true, bUseAltNIC = false; int iHeaderBytes = 0, iCurHeaderSize = 1024; string file; + char szAuth[128]; szStreamName = NULL; szStreamUrl = NULL; if (!m_bUseProxy) { - const char* ptr; - - iRet = sscanf(m_path, " http://%[^:/]:%d", szHostName, &iPort); - if (iRet < 1) + pPtr = ParseURI(m_path, szHostName, szAuth, &iPort); + if (pPtr == NULL) { - ReportError("Bad URL format. URL format: http://<host name>" + ReportError("Bad URL format. URL format: http://[user:pass@;]<host name>" ":[port][/path]. Please check the URL and try again."); return (Error) httpError_BadUrl; } - ptr = strchr(m_path + 7, '/'); - file = (ptr ? ptr : ""); + file = pPtr; } else { - iRet = sscanf(m_szProxyHost, " http://%[^:/]:%d", szHostName, &iPort); - if (iRet < 1) + pPtr = ParseURI(m_szProxyHost, szHostName, szAuth, &iPort); + if ((pPtr == NULL) || (pPtr[0] == '/' && pPtr[1] != '\0')) { ReportError("Bad Proxy URL format. URL format: http:" - "//<host name>:[port]. Please check your proxy settings " + "//[user:pass@]<host name>:[port]. Please check your proxy +settings " "in the Options."); m_pContext->log->Error("Debug: m_szProxyHost: '%s'\n", m_szProxyHost); @@ -534,9 +657,6 @@ } EncodeURI(file); - if (iRet < 2) - iPort = iHttpPort; - memset(&sAddr, 0, sizeof(struct sockaddr_in)); ReportStatus("Looking up host %s...", szHostName); @@ -675,6 +795,11 @@ "Icy-MetaData:1\r\n" "User-Agent: FreeAmp/%s\r\n", szHostName, FREEAMP_VERSION); } + if (szAuth[0]) + { + sprintf(szQuery + strlen(szQuery), + "Authorization: basic %s\r\n", szAuth); + } m_pContext->prefs->GetPrefBoolean(kUseTitleStreamingPref, &bUseTitleStreaming); diff -NurdbX excl.txt freeamp.orig/plm/metadata/misc/misc.cpp freeamp/plm/metadata/misc/misc.cpp --- freeamp.orig/plm/metadata/misc/misc.cpp Wed Feb 21 03:19:08 2001 +++ freeamp/plm/metadata/misc/misc.cpp Sat Apr 27 01:05:21 2002 @@ -173,7 +173,103 @@ } else if(!strncasecmp(url, "http://", 7) && !metadata->Title().size()) { - metadata->SetTitle("HTTP Stream"); +// metadata->SetTitle("HTTP Stream"); + + // Get working copy of URL + char *urlCopy = new char[strlen(url) + 1]; + strcpy(urlCopy, url); + + // Find start of file name + char *fileName = strrchr(urlCopy, '/'); + if (fileName == NULL) + fileName = urlCopy; + else + fileName++; + + // Remove query + char *ext = strrchr(fileName, '?'); + if (ext) { + *ext = '\0'; + } + + // Undo URL escape sequences + char *cleanName = new char[strlen(fileName) + 1]; + char *p = cleanName; + char *f = fileName; + while (*f) + { + if (f[0] == '%' && isxdigit(f[1]) && isxdigit(f[2])) + { + unsigned char n; + n = ((isdigit(f[1]) ? (f[1] - '0') : (toupper(f[1]) -'A' + 10)) * +16) + | (isdigit(f[2]) ? (f[2] - '0') : (toupper(f[2]) -'A' + 10)); + if (n) *p = n; + f += 3; + } + else + { + *p = *f++; + } + p++; + } + *p = '\0'; + + // Remove extension + ext = strrchr(cleanName, '.'); + if (ext) { + *ext = '\0'; + } + + // Bingo! + metadata->SetTitle(*cleanName ? cleanName : url); + + // Try to find a track number i.e. a decimal number between + // 1 and 99 surrounded by non alpha characters + if (*cleanName) + { + p = cleanName; + while (*p) + { + // Skip non numerics + while (!isdigit(*p)) + p++; + + // Ensure character before digit is not alphabetic + if (p != fileName && isalpha(p[-1])) + { + // Skip digits + while (isdigit(*p)) + p++; + + // Try again + continue; + } + + // Evaluate digits + int n = 0; + while (isdigit(*p)) + { + n *= 10; + n += *p++ - '0'; + } + + // Ensure following character is not alphabetic + if (isalpha(*p)) + continue; + + // Must be in range of 1 to 99 + if (n > 0 && n < 100) + { + // Got it + metadata->SetTrack(n); + break; + } + } + } + + // Clean up + delete [] cleanName; + delete [] urlCopy; } else if(!strncasecmp(url, "rtp://", 6) && !metadata->Title().size()) { Nick ---------------------------------------------------------------- Nick Holgate <[EMAIL PROTECTED]> GPG key available from public key servers : Key ID FD9C18AF Fingerprint = 9DCA EDEA D5C5 57DA 23F3 1A2B 2273 5645 FD9C 18AF _______________________________________________ [EMAIL PROTECTED] http://www.freeamp.org/mailman/listinfo/freeamp-dev