diff options
Diffstat (limited to 'src/libpiano')
| -rw-r--r-- | src/libpiano/crypt.c | 205 | ||||
| -rw-r--r-- | src/libpiano/piano.c | 1146 | ||||
| -rw-r--r-- | src/libpiano/piano.h | 79 | ||||
| -rw-r--r-- | src/libpiano/xml.c | 980 | ||||
| -rw-r--r-- | src/libpiano/xml.h | 49 | 
5 files changed, 749 insertions, 1710 deletions
diff --git a/src/libpiano/crypt.c b/src/libpiano/crypt.c index 3f98f42..6dafcca 100644 --- a/src/libpiano/crypt.c +++ b/src/libpiano/crypt.c @@ -1,5 +1,5 @@  /* -Copyright (c) 2008-2011 +Copyright (c) 2008-2012  	Lars-Dominik Braun <lars@6xq.net>  Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,23 +22,13 @@ THE SOFTWARE.  */  #include <string.h> +#include <assert.h> +#include <gcrypt.h>  #include <stdio.h>  #include <stdlib.h>  #include <stdint.h> -#include <arpa/inet.h>  #include "crypt.h" -#include "crypt_key_output.h" -#include "crypt_key_input.h" -#include "piano_private.h" - -#define byteswap32(x) ((((x) >> 24) & 0x000000ff) | \ -		(((x) >> 8) & 0x0000ff00) | \ -		(((x) << 8) & 0x00ff0000) | \ -		(((x) << 24) & 0xff000000)) - -#define hostToBigEndian32(x) htonl(x) -#define bigToHostEndian32(x) ntohl(x)  /*	decrypt hex-encoded, blowfish-crypted string: decode 2 hex-encoded blocks,   *	decrypt, byteswap @@ -46,162 +36,69 @@ THE SOFTWARE.   *	@param decrypted string length (without trailing NUL)   *	@return decrypted string or NULL   */ -#define INITIAL_SHIFT 28 -#define SHIFT_DEC 4 -char *PianoDecryptString (const char * const s, size_t * const retSize) { -	const unsigned char *strInput = (const unsigned char *) s; -	/* hex-decode => strlen/2 + null-byte */ -	uint32_t *iDecrypt; -	size_t decryptedSize; -	char *strDecrypted; -	unsigned char shift = INITIAL_SHIFT, intsDecoded = 0, j; -	/* blowfish blocks, 32-bit */ -	uint32_t f, l, r, lrExchange; - -	decryptedSize = strlen ((const char *) strInput)/2; -	if ((iDecrypt = calloc (decryptedSize/sizeof (*iDecrypt)+1, -			sizeof (*iDecrypt))) == NULL) { -		return NULL; -	} -	strDecrypted = (char *) iDecrypt; - -	while (*strInput != '\0') { -		/* hex-decode string */ -		if (*strInput >= '0' && *strInput <= '9') { -			*iDecrypt |= (*strInput & 0x0f) << shift; -		} else if (*strInput >= 'a' && *strInput <= 'f') { -			/* 0xa (hex) = 10 (decimal), 'a' & 0x0f == 1 => +9 */ -			*iDecrypt |= ((*strInput+9) & 0x0f) << shift; -		} -		if (shift > 0) { -			shift -= SHIFT_DEC; -		} else { -			shift = INITIAL_SHIFT; -			/* initialize next dword */ -			*(++iDecrypt) = 0; -			++intsDecoded; -		} - -		/* two 32-bit hex-decoded boxes available => blowfish decrypt */ -		if (intsDecoded == 2) { -			l = *(iDecrypt-2); -			r = *(iDecrypt-1); - -			for (j = in_key_n + 1; j > 1; --j) { -				l ^= in_key_p [j]; -				 -				f = in_key_s [0][(l >> 24) & 0xff] + -						in_key_s [1][(l >> 16) & 0xff]; -				f ^= in_key_s [2][(l >> 8) & 0xff]; -				f += in_key_s [3][l & 0xff]; -				r ^= f; -				/* exchange l & r */ -				lrExchange = l; -				l = r; -				r = lrExchange; -			} -			/* exchange l & r */ -			lrExchange = l; -			l = r; -			r = lrExchange; -			r ^= in_key_p [1]; -			l ^= in_key_p [0]; - -			*(iDecrypt-2) = bigToHostEndian32 (l); -			*(iDecrypt-1) = bigToHostEndian32 (r); - -			intsDecoded = 0; -		} -		++strInput; +char *PianoDecryptString (const char * const input, size_t * const retSize) { +	size_t inputLen = strlen (input); +	gcry_error_t gret; +	unsigned char *output; +	size_t outputLen = inputLen/2; + +	assert (inputLen%2 == 0); + +	output = calloc (outputLen+1, sizeof (*output)); +	/* hex decode */ +	for (size_t i = 0; i < outputLen; i++) { +		char hex[3]; +		memcpy (hex, &input[i*2], 2); +		hex[2] = '\0'; +		output[i] = strtol (hex, NULL, 16);  	} -	if (retSize != NULL) { -		*retSize = decryptedSize; +	gcry_cipher_hd_t h; +	gcry_cipher_open (&h, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_ECB, 0); +	gcry_cipher_setkey (h, (unsigned char *) "R=U!LH$O2B#", 11); +	gret = gcry_cipher_decrypt (h, output, outputLen, NULL, 0); +	if (gret) { +		fprintf (stderr, "Failure: %s/%s\n", gcry_strsource (gret), gcry_strerror (gret)); +		return NULL;  	} -	return strDecrypted; +	gcry_cipher_close (h); +	*retSize = outputLen; + +	return (char *) output;  } -#undef INITIAL_SHIFT -#undef SHIFT_DEC  /*	blowfish-encrypt/hex-encode string   *	@param encrypt this   *	@return encrypted, hex-encoded string   */  char *PianoEncryptString (const char *s) { -	const unsigned char *strInput = (const unsigned char *) s; -	const size_t strInputN = strlen ((const char *) strInput); -	/* num of 64-bit blocks, rounded to next block */ -	size_t blockN = strInputN / 8 + 1; -	uint32_t *blockInput, *blockPtr; -	/* output string */ -	unsigned char *strHex, *hexPtr; -	const char *hexmap = "0123456789abcdef"; - -	if ((blockInput = calloc (blockN*2, sizeof (*blockInput))) == NULL) { +	unsigned char *paddedInput, *hexOutput; +	size_t inputLen = strlen (s); +	/* blowfish expects two 32 bit blocks */ +	size_t paddedInputLen = (inputLen % 8 == 0) ? inputLen : inputLen + (8-inputLen%8); +	gcry_error_t gret; + +	paddedInput = calloc (paddedInputLen+1, sizeof (*paddedInput)); +	memcpy (paddedInput, s, inputLen); + +	gcry_cipher_hd_t h; +	gcry_cipher_open (&h, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_ECB, 0); +	gcry_cipher_setkey (h, (unsigned char *) "6#26FRL$ZWD", 11); +	gret = gcry_cipher_encrypt (h, paddedInput, paddedInputLen, NULL, 0); +	if (gret) { +		fprintf (stderr, "Failure: %s/%s\n", gcry_strsource (gret), gcry_strerror (gret));  		return NULL;  	} -	blockPtr = blockInput; -	if ((strHex = calloc (blockN*8*2 + 1, sizeof (*strHex))) == NULL) { -		return NULL; -	} -	hexPtr = strHex; - -	memcpy (blockInput, strInput, strInputN); - -	while (blockN > 0) { -		/* encryption blocks */ -		uint32_t f, lrExchange; -		register uint32_t l, r; -		int i; - -		l = hostToBigEndian32 (*blockPtr); -		r = hostToBigEndian32 (*(blockPtr+1)); -		 -		/* encrypt blocks */ -		for (i = 0; i < out_key_n; i++) { -			l ^= out_key_p[i]; - -			f = out_key_s[0][(l >> 24) & 0xff] + -					out_key_s[1][(l >> 16) & 0xff]; -			f ^= out_key_s[2][(l >> 8) & 0xff]; -			f += out_key_s[3][l & 0xff]; -			r ^= f; -			/* exchange l & r */ -			lrExchange = l; -			l = r; -			r = lrExchange; -		} -		/* exchange l & r again */ -		lrExchange = l; -		l = r; -		r = lrExchange; -		r ^= out_key_p [out_key_n]; -		l ^= out_key_p [out_key_n+1]; - -		/* swap bytes again... */ -		l = byteswap32 (l); -		r = byteswap32 (r); - -		/* hex-encode encrypted blocks */ -		for (i = 0; i < 4; i++) { -			*hexPtr++ = hexmap[(l & 0xf0) >> 4]; -			*hexPtr++ = hexmap[l & 0x0f]; -			l >>= 8; -		} -		for (i = 0; i < 4; i++) { -			*hexPtr++ = hexmap[(r & 0xf0) >> 4]; -			*hexPtr++ = hexmap[r & 0x0f]; -			r >>= 8; -		} - -		/* two! 32-bit blocks encrypted (l & r) */ -		blockPtr += 2; -		--blockN; +	hexOutput = calloc (paddedInputLen*2+1, sizeof (*hexOutput)); +	for (size_t i = 0; i < paddedInputLen; i++) { +		snprintf ((char * restrict) &hexOutput[i*2], 3, "%02x", paddedInput[i]);  	} -	free (blockInput); +	gcry_cipher_close (h); +	free (paddedInput); -	return (char *) strHex; +	return (char *) hexOutput;  } + diff --git a/src/libpiano/piano.c b/src/libpiano/piano.c index 342e4ec..0ac96b6 100644 --- a/src/libpiano/piano.c +++ b/src/libpiano/piano.c @@ -1,5 +1,5 @@  /* -Copyright (c) 2008-2011 +Copyright (c) 2008-2012  	Lars-Dominik Braun <lars@6xq.net>  Permission is hereby granted, free of charge, to any person obtaining a copy @@ -32,20 +32,17 @@ THE SOFTWARE.  #include <time.h>  #include <assert.h>  #include <stdint.h> +#include <json.h>  /* needed for urlencode */  #include <waitress.h>  #include "piano_private.h"  #include "piano.h" -#include "xml.h"  #include "crypt.h"  #include "config.h" -#define PIANO_PROTOCOL_VERSION "33" -#define PIANO_RPC_HOST "www.pandora.com" -#define PIANO_RPC_PORT "80" -#define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?" +#define PIANO_RPC_PATH "/services/json/?"  #define PIANO_SEND_BUFFER_SIZE 10000  /*	initialize piano handle @@ -54,10 +51,6 @@ THE SOFTWARE.   */  void PianoInit (PianoHandle_t *ph) {  	memset (ph, 0, sizeof (*ph)); - -	/* route-id seems to be random. we're using time anyway... */ -	snprintf (ph->routeId, sizeof (ph->routeId), "%07luP", -			(unsigned long) time (NULL) % 10000000);  }  /*	destroy artist linked list @@ -125,10 +118,8 @@ void PianoDestroyPlaylist (PianoSong_t *playlist) {  		free (curSong->artist);  		free (curSong->musicId);  		free (curSong->title); -		free (curSong->userSeed);  		free (curSong->stationId);  		free (curSong->album); -		free (curSong->artistMusicId);  		free (curSong->feedbackId);  		free (curSong->seedId);  		free (curSong->detailUrl); @@ -164,7 +155,6 @@ static void PianoDestroyGenres (PianoGenre_t *genres) {  /*	destroy user information   */  static void PianoDestroyUserInfo (PianoUserInfo_t *user) { -	free (user->webAuthToken);  	free (user->authToken);  	free (user->listenerId);  } @@ -185,6 +175,7 @@ void PianoDestroy (PianoHandle_t *ph) {  		curGenreCat = curGenreCat->next;  		free (lastGenreCat);  	} +	free (ph->partnerAuthToken);  	memset (ph, 0, sizeof (*ph));  } @@ -228,9 +219,12 @@ static const char *PianoAudioFormatToString (PianoAudioFormat_t format) {   */  PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  		PianoRequestType_t type) { -	char xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; +	const char *jsonSendBuf; +	const char *method = NULL; +	json_object *j = json_object_new_object ();  	/* corrected timestamp */  	time_t timestamp = time (NULL) - ph->timeOffset; +	bool encrypted = true;  	assert (ph != NULL);  	assert (req != NULL); @@ -248,66 +242,59 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			switch (logindata->step) {  				case 0: -					snprintf (xmlSendBuf, sizeof (xmlSendBuf),  -							"<?xml version=\"1.0\"?><methodCall>" -							"<methodName>misc.sync</methodName>" -							"<params></params></methodCall>"); -					snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -							"rid=%s&method=sync", ph->routeId); +					encrypted = false; +					req->secure = true; + +					json_object_object_add (j, "username", +							json_object_new_string ("android")); +					json_object_object_add (j, "password", +							json_object_new_string ("AC7IBG09A3DTSYM4R41UJWL07VLN8JI7")); +					json_object_object_add (j, "deviceModel", +							json_object_new_string ("android-generic")); +					json_object_object_add (j, "version", +							json_object_new_string ("5")); +					json_object_object_add (j, "includeUrls", +							json_object_new_boolean (true)); +					snprintf (req->urlPath, sizeof (req->urlPath), +							PIANO_RPC_PATH "method=auth.partnerLogin");  					break;  				case 1: { -					char *xmlencodedPassword = NULL; +					char *urlencAuthToken;  					req->secure = true; -					/* username == email address does not contain &,<,>," */ -					if ((xmlencodedPassword = -							PianoXmlEncodeString (logindata->password)) == -							NULL) { -						return PIANO_RET_OUT_OF_MEMORY; -					} +					json_object_object_add (j, "loginType", +							json_object_new_string ("user")); +					json_object_object_add (j, "username", +							json_object_new_string (logindata->user)); +					json_object_object_add (j, "password", +							json_object_new_string (logindata->password)); +					json_object_object_add (j, "partnerAuthToken", +							json_object_new_string (ph->partnerAuthToken)); +					json_object_object_add (j, "syncTime", +							json_object_new_int (timestamp)); + +					urlencAuthToken = WaitressUrlEncode (ph->partnerAuthToken); +					assert (urlencAuthToken != NULL); +					snprintf (req->urlPath, sizeof (req->urlPath), +							PIANO_RPC_PATH "method=auth.userLogin&" +							"auth_token=%s&partner_id=%i", urlencAuthToken, +							ph->partnerId); +					free (urlencAuthToken); -					snprintf (xmlSendBuf, sizeof (xmlSendBuf),  -							"<?xml version=\"1.0\"?><methodCall>" -							"<methodName>listener.authenticateListener</methodName>" -							"<params><param><value><int>%lu</int></value></param>" -							/* user */ -							"<param><value><string>%s</string></value></param>" -							/* password */ -							"<param><value><string>%s</string></value></param>" -							/* vendor */ -							"<param><value><string>html5tuner</string></value></param>" -							"<param><value><string/></value></param>" -							"<param><value><string/></value></param>" -							"<param><value><string>HTML5</string></value></param>" -							"<param><value><boolean>1</boolean></value></param>" -							"</params></methodCall>", (unsigned long) timestamp, -							logindata->user, xmlencodedPassword); -					snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -							"rid=%s&method=authenticateListener", ph->routeId); - -					free (xmlencodedPassword);  					break;  				}  			}  			break;  		} -		case PIANO_REQUEST_GET_STATIONS: +		case PIANO_REQUEST_GET_STATIONS: {  			/* get stations, user must be authenticated */  			assert (ph->user.listenerId != NULL); - -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.getStations</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=getStations", ph->routeId, -					ph->user.listenerId); +			method = "user.getStationList";  			break; +		}  		case PIANO_REQUEST_GET_PLAYLIST: {  			/* get playlist for specified station */ @@ -318,33 +305,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (reqData->station->id != NULL);  			assert (reqData->format != PIANO_AF_UNKNOWN); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>playlist.getFragment</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					/* total listening time */ -					"<param><value><string>0</string></value></param>" -					/* time since last session */ -					"<param><value><string></string></value></param>" -					/* tracking code */ -					"<param><value><string></string></value></param>" -					/* audio format */ -					"<param><value><string>%s</string></value></param>" -					/* delta listening time */ -					"<param><value><string>0</string></value></param>" -					/* listening timestamp */ -					"<param><value><string>0</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->station->id, -					PianoAudioFormatToString (reqData->format)); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=getFragment&arg1=%s&arg2=0" -					"&arg3=&arg4=&arg5=%s&arg6=0&arg7=0", ph->routeId, -					ph->user.listenerId, reqData->station->id, -					PianoAudioFormatToString (reqData->format)); +			req->secure = true; + +			json_object_object_add (j, "stationToken", +					json_object_new_string (reqData->station->id)); + +			method = "station.getPlaylist";  			break;  		} @@ -353,65 +319,31 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			PianoRequestDataAddFeedback_t *reqData = req->data;  			assert (reqData != NULL); -			assert (reqData->stationId != NULL); +			assert (reqData->trackToken != NULL);  			assert (reqData->rating != PIANO_RATE_NONE); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.addFeedback</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					/* track token */ -					"<param><value><string>%s</string></value></param>" -					/* positive */ -					"<param><value><boolean>%i</boolean></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->stationId, reqData->trackToken, -					(reqData->rating == PIANO_RATE_LOVE) ? 1 : 0); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" -					"&arg3=%s", -					ph->routeId, ph->user.listenerId, reqData->stationId, -					reqData->trackToken, -					(reqData->rating == PIANO_RATE_LOVE) ? "true" : "false"); +			json_object_object_add (j, "trackToken", +					json_object_new_string (reqData->trackToken)); +			json_object_object_add (j, "isPositive", +					json_object_new_boolean (reqData->rating == PIANO_RATE_LOVE)); + +			method = "station.addFeedback";  			break;  		}  		case PIANO_REQUEST_RENAME_STATION: { -			/* rename stations */  			PianoRequestDataRenameStation_t *reqData = req->data; -			char *urlencodedNewName, *xmlencodedNewName;  			assert (reqData != NULL);  			assert (reqData->station != NULL);  			assert (reqData->newName != NULL); -			if ((xmlencodedNewName = PianoXmlEncodeString (reqData->newName)) == NULL) { -				return PIANO_RET_OUT_OF_MEMORY; -			} -			urlencodedNewName = WaitressUrlEncode (reqData->newName); - -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.setStationName</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					/* new name */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->station->id, -					xmlencodedNewName); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", -					ph->routeId, ph->user.listenerId, reqData->station->id, -					urlencodedNewName); +			json_object_object_add (j, "stationToken", +					json_object_new_string (reqData->station->id)); +			json_object_object_add (j, "stationName", +					json_object_new_string (reqData->newName)); -			free (urlencodedNewName); -			free (xmlencodedNewName); +			method = "station.renameStation";  			break;  		} @@ -420,50 +352,26 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			PianoStation_t *station = req->data;  			assert (station != NULL); +			assert (station->id != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.removeStation</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, station->id); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, -					ph->user.listenerId, station->id); +			json_object_object_add (j, "stationToken", +					json_object_new_string (station->id)); + +			method = "station.deleteStation";  			break;  		}  		case PIANO_REQUEST_SEARCH: {  			/* search for artist/song title */  			PianoRequestDataSearch_t *reqData = req->data; -			char *xmlencodedSearchStr, *urlencodedSearchStr;  			assert (reqData != NULL);  			assert (reqData->searchStr != NULL); -			if ((xmlencodedSearchStr = PianoXmlEncodeString (reqData->searchStr)) == NULL) { -				return PIANO_RET_OUT_OF_MEMORY; -			} -			urlencodedSearchStr = WaitressUrlEncode (reqData->searchStr); - -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>music.search</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* search string */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, xmlencodedSearchStr); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, -					ph->user.listenerId, urlencodedSearchStr); +			json_object_object_add (j, "searchText", +					json_object_new_string (reqData->searchStr)); -			free (urlencodedSearchStr); -			free (xmlencodedSearchStr); +			method = "music.search";  			break;  		} @@ -474,23 +382,11 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (reqData != NULL);  			assert (reqData->id != NULL); -			assert (reqData->type != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.createStation</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* music id */ -					"<param><value><string>%s%s</string></value></param>" -					/* empty */ -					"<param><value><string></string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->type, reqData->id); +			json_object_object_add (j, "musicToken", +					json_object_new_string (reqData->id)); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=createStation&arg1=%s%s&arg2=", ph->routeId, -					ph->user.listenerId, reqData->type, reqData->id); +			method = "station.createStation";  			break;  		} @@ -502,20 +398,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (reqData->station != NULL);  			assert (reqData->musicId != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.addSeed</methodName><params>" -					"<param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					/* music id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->station->id, reqData->musicId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, -					ph->user.listenerId, reqData->station->id, reqData->musicId); +			json_object_object_add (j, "musicToken", +					json_object_new_string (reqData->musicId)); +			json_object_object_add (j, "stationToken", +					json_object_new_string (reqData->station->id)); + +			method = "station.addMusic";  			break;  		} @@ -525,87 +413,40 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (song != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>listener.addTiredSong</methodName><params>" -					"<param><value><int>%lu</int></value></param>" -					"<param><value><string>%s</string></value></param>" -					/* key */ -					"<param><value><string>%s</string></value></param>" -					/* user seed */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, -					(song->musicId == NULL) ? "" : song->musicId, -					(song->userSeed == NULL) ? "" : song->userSeed, -					song->stationId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=addTiredSong&arg1=%s&arg2=%s&arg3=%s", -					ph->routeId, ph->user.listenerId, -					(song->musicId == NULL) ? "" : song->musicId, -					(song->userSeed == NULL) ? "" : song->userSeed, -					song->stationId); +			json_object_object_add (j, "trackToken", +					json_object_new_string (song->trackToken)); + +			method = "user.sleepSong";  			break;  		}  		case PIANO_REQUEST_SET_QUICKMIX: {  			/* select stations included in quickmix (see useQuickMix flag of  			 * PianoStation_t) */ -			char valueBuf[1000], urlArgBuf[1000];  			PianoStation_t *curStation = ph->stations; +			json_object *a = json_object_new_array (); -			memset (urlArgBuf, 0, sizeof (urlArgBuf)); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.setQuickMix</methodName><params>" -					"<param><value><int>%lu</int></value></param>" -					"<param><value><string>%s</string></value></param>" -					/* quick mix type */ -					"<param><value><string>RANDOM</string></value></param>" -					"<param><value><array><data>", (unsigned long) timestamp, -					ph->user.authToken);  			while (curStation != NULL) {  				/* quick mix can't contain itself */ -				if (!curStation->useQuickMix || curStation->isQuickMix) { -					curStation = curStation->next; -					continue; +				if (curStation->useQuickMix && !curStation->isQuickMix) { +					json_object_array_add (a, +							json_object_new_string (curStation->id));  				} -				/* append to xml doc */ -				snprintf (valueBuf, sizeof (valueBuf), -						"<value><string>%s</string></value>", curStation->id); -				strncat (xmlSendBuf, valueBuf, sizeof (xmlSendBuf) - -						strlen (xmlSendBuf) - 1); -				/* append to url arg */ -				strncat (urlArgBuf, curStation->id, sizeof (urlArgBuf) - -						strlen (urlArgBuf) - 1); +  				curStation = curStation->next; -				/* if not last item: append "," */ -				if (curStation != NULL) { -					strncat (urlArgBuf, "%2C", sizeof (urlArgBuf) - -							strlen (urlArgBuf) - 1); -				}  			} -			strncat (xmlSendBuf, -					"</data></array></value></param>" -					/* empty */ -					"<param><value><string></string></value></param>" -					/* empty */ -					"<param><value><string></string></value></param>" -					"</params></methodCall>", -					sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s&arg3=&arg4=", -					ph->routeId, ph->user.listenerId, urlArgBuf); +			json_object_object_add (j, "quickMixStationIds", a); + +			method = "user.setQuickMix";  			break;  		} -		case PIANO_REQUEST_GET_GENRE_STATIONS: +		case PIANO_REQUEST_GET_GENRE_STATIONS: {  			/* receive list of pandora's genre stations */ -			xmlSendBuf[0] = '\0'; -			snprintf (req->urlPath, sizeof (req->urlPath), "/xml/genre?r=%lu", -					(unsigned long) timestamp); +			method = "station.getGenreStations";  			break; +		}  		case PIANO_REQUEST_TRANSFORM_STATION: {  			/* transform shared station into private */ @@ -613,18 +454,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (station != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.transformShared</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, station->id); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, -					ph->user.listenerId, station->id); +			json_object_object_add (j, "stationToken", +					json_object_new_string (station->id)); + +			method = "station.transformSharedStation";  			break;  		} @@ -635,26 +468,15 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (reqData != NULL);  			assert (reqData->song != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>playlist.narrative</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					/* music id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->song->stationId, -					reqData->song->musicId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=narrative&arg1=%s&arg2=%s", -					ph->routeId, ph->user.listenerId, reqData->song->stationId, -					reqData->song->musicId); +			json_object_object_add (j, "trackToken", +					json_object_new_string (reqData->song->trackToken)); + +			method = "track.explainTrack";  			break;  		}  		case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +#if 0  			/* find similar artists */  			PianoRequestDataGetSeedSuggestions_t *reqData = req->data; @@ -679,6 +501,7 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH  					"rid=%s&lid=%s&method=getSeedSuggestions&arg1=%s&arg2=%u",  					ph->routeId, ph->user.listenerId, reqData->musicId, reqData->max); +#endif  			break;  		} @@ -688,21 +511,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (song != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.createBookmark</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					/* music id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, song->stationId, song->musicId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=createBookmark&arg1=%s&arg2=%s", -					ph->routeId, ph->user.listenerId, song->stationId, -					song->musicId); +			json_object_object_add (j, "trackToken", +					json_object_new_string (song->trackToken)); + +			method = "bookmark.addSongBookmark";  			break;  		} @@ -712,18 +524,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (song != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.createArtistBookmark</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* music id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, song->artistMusicId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=createArtistBookmark&arg1=%s", -					ph->routeId, ph->user.listenerId, song->artistMusicId); +			json_object_object_add (j, "trackToken", +					json_object_new_string (song->trackToken)); + +			method = "bookmark.addArtistBookmark";  			break;  		} @@ -734,18 +538,12 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (reqData != NULL);  			assert (reqData->station != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.getStation</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* station id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, reqData->station->id); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=getStation&arg1=%s", -					ph->routeId, ph->user.listenerId, reqData->station->id); +			json_object_object_add (j, "stationToken", +					json_object_new_string (reqData->station->id)); +			json_object_object_add (j, "includeExtendedAttributes", +					json_object_new_boolean (true)); + +			method = "station.getStation";  			break;  		} @@ -754,18 +552,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (song != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.deleteFeedback</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* feedback id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, song->feedbackId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=deleteFeedback&arg1=%s", -					ph->routeId, ph->user.listenerId, song->feedbackId); +			json_object_object_add (j, "feedbackId", +					json_object_new_string (song->feedbackId)); + +			method = "station.deleteFeedback";  			break;  		} @@ -787,18 +577,10 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  			assert (seedId != NULL); -			snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>" -					"<methodCall><methodName>station.deleteSeed</methodName>" -					"<params><param><value><int>%lu</int></value></param>" -					/* auth token */ -					"<param><value><string>%s</string></value></param>" -					/* seed id */ -					"<param><value><string>%s</string></value></param>" -					"</params></methodCall>", (unsigned long) timestamp, -					ph->user.authToken, seedId); -			snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH -					"rid=%s&lid=%s&method=deleteSeed&arg1=%s", -					ph->routeId, ph->user.listenerId, seedId); +			json_object_object_add (j, "seedId", +					json_object_new_string (seedId)); + +			method = "station.deleteMusic";  			break;  		} @@ -866,24 +648,89 @@ PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,  		}  	} -	if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) { -		return PIANO_RET_OUT_OF_MEMORY; +	/* standard parameter */ +	if (method != NULL) { +		char *urlencAuthToken; + +		assert (ph->user.authToken != NULL); + +		urlencAuthToken = WaitressUrlEncode (ph->user.authToken); +		assert (urlencAuthToken != NULL); + +		snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH +				"method=%s&auth_token=%s&partner_id=%i&user_id=%s", method, +				urlencAuthToken, ph->partnerId, ph->user.listenerId); + +		free (urlencAuthToken); + +		json_object_object_add (j, "userAuthToken", +				json_object_new_string (ph->user.authToken)); +		json_object_object_add (j, "syncTime", +				json_object_new_int (timestamp)); +	} + +	/* json to string */ +	jsonSendBuf = json_object_to_json_string (j); +	if (encrypted) { +		if ((req->postData = PianoEncryptString (jsonSendBuf)) == NULL) { +			return PIANO_RET_OUT_OF_MEMORY; +		} +	} else { +		req->postData = strdup (jsonSendBuf);  	} +	json_object_put (j);  	return PIANO_RET_OK;  } +static char *PianoJsonStrdup (json_object *j, const char *key) { +	return strdup (json_object_get_string (json_object_object_get (j, key))); +} + +static void PianoJsonParseStation (json_object *j, PianoStation_t *s) { +	s->name = PianoJsonStrdup (j, "stationName"); +	s->id = PianoJsonStrdup (j, "stationToken"); +	s->isCreator = !json_object_get_boolean (json_object_object_get (j, +			"isShared")); +	s->isQuickMix = json_object_get_boolean (json_object_object_get (j, +			"isQuickMix")); +} +  /*	parse xml response and update data structures/return new data structure   *	@param piano handle   *	@param initialized request (expects responseData to be a NUL-terminated   *			string)   */  PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) { -	PianoReturn_t ret = PIANO_RET_ERR; +	PianoReturn_t ret = PIANO_RET_OK; +	json_object *j, *result, *status;  	assert (ph != NULL);  	assert (req != NULL); +	j = json_tokener_parse (req->responseData); + +	status = json_object_object_get (j, "stat"); +	if (status == NULL) { +		json_object_put (j); +		return PIANO_RET_INVALID_RESPONSE; +	} + +	/* error handling */ +	if (strcmp (json_object_get_string (status), "ok") != 0) { +		json_object *code = json_object_object_get (j, "code"); +		if (code == NULL) { +			ret = PIANO_RET_INVALID_RESPONSE; +		} else { +			ret = json_object_get_int (code)+PIANO_RET_OFFSET; +		} + +		json_object_put (j); +		return ret; +	} + +	result = json_object_object_get (j, "result"); +  	switch (req->type) {  		case PIANO_REQUEST_LOGIN: {  			/* authenticate user */ @@ -894,29 +741,28 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {  			switch (reqData->step) {  				case 0: { -					char *cryptedTimestamp = NULL; - -					assert (req->responseData != NULL); - -					/* abusing parseNarrative; has same xml structure */ -					ret = PianoXmlParseNarrative (req->responseData, &cryptedTimestamp); -					if (ret == PIANO_RET_OK && cryptedTimestamp != NULL) { -						unsigned long timestamp = 0; -						const time_t realTimestamp = time (NULL); -						char *decryptedTimestamp = NULL; -						size_t decryptedSize; - -						ret = PIANO_RET_ERR; -						if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp, -								&decryptedSize)) != NULL && decryptedSize > 4) { -							/* skip four bytes garbage(?) at beginning */ -							timestamp = strtoul (decryptedTimestamp+4, NULL, 0); -							ph->timeOffset = realTimestamp - timestamp; -							ret = PIANO_RET_CONTINUE_REQUEST; -						} -						free (decryptedTimestamp); +					/* decrypt timestamp */ +					const char *cryptedTimestamp = json_object_get_string ( +							json_object_object_get (result, "syncTime")); +					unsigned long timestamp = 0; +					const time_t realTimestamp = time (NULL); +					char *decryptedTimestamp = NULL; +					size_t decryptedSize; + +					ret = PIANO_RET_ERR; +					if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp, +							&decryptedSize)) != NULL && decryptedSize > 4) { +						/* skip four bytes garbage(?) at beginning */ +						timestamp = strtoul (decryptedTimestamp+4, NULL, 0); +						ph->timeOffset = realTimestamp - timestamp; +						ret = PIANO_RET_CONTINUE_REQUEST;  					} -					free (cryptedTimestamp); +					free (decryptedTimestamp); +					/* get auth token */ +					ph->partnerAuthToken = PianoJsonStrdup (result, +							"partnerAuthToken"); +					ph->partnerId = json_object_get_int ( +							json_object_object_get (result, "partnerId"));  					++reqData->step;  					break;  				} @@ -927,48 +773,145 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {  					if (ph->user.listenerId != NULL) {  						PianoDestroyUserInfo (&ph->user);  					} -					ret = PianoXmlParseUserinfo (ph, req->responseData); +					ph->user.listenerId = PianoJsonStrdup (result, "userId"); +					ph->user.authToken = PianoJsonStrdup (result, +							"userAuthToken");  					break;  			}  			break;  		} -		case PIANO_REQUEST_GET_STATIONS: +		case PIANO_REQUEST_GET_STATIONS: {  			/* get stations */  			assert (req->responseData != NULL); -			 -			ret = PianoXmlParseStations (ph, req->responseData); + +			json_object *stations = json_object_object_get (result, +					"stations"), *mix = NULL; + +			for (size_t i=0; i < json_object_array_length (stations); i++) { +				PianoStation_t *tmpStation; +				json_object *s = json_object_array_get_idx (stations, i); + +				if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { +					return PIANO_RET_OUT_OF_MEMORY; +				} + +				PianoJsonParseStation (s, tmpStation); + +				if (tmpStation->isQuickMix) { +					/* fix flags on other stations later */ +					mix = json_object_object_get (s, "quickMixStationIds"); +				} + +				/* start new linked list or append */ +				if (ph->stations == NULL) { +					ph->stations = tmpStation; +				} else { +					PianoStation_t *curStation = ph->stations; +					while (curStation->next != NULL) { +						curStation = curStation->next; +					} +					curStation->next = tmpStation; +				} +			} + +			/* fix quickmix flags */ +			if (mix != NULL) { +				PianoStation_t *curStation = ph->stations; +				while (curStation != NULL) { +					for (size_t i = 0; i < json_object_array_length (mix); i++) { +						json_object *id = json_object_array_get_idx (mix, i); +						if (strcmp (json_object_get_string (id), +								curStation->id) == 0) { +							curStation->useQuickMix = true; +						} +					} +					curStation = curStation->next; +				} +			}  			break; +		}  		case PIANO_REQUEST_GET_PLAYLIST: {  			/* get playlist, usually four songs */  			PianoRequestDataGetPlaylist_t *reqData = req->data; +			PianoSong_t *playlist = NULL;  			assert (req->responseData != NULL);  			assert (reqData != NULL); -			reqData->retPlaylist = NULL; -			ret = PianoXmlParsePlaylist (ph, req->responseData, -					&reqData->retPlaylist); +			json_object *items = json_object_object_get (result, "items"); +			assert (items != NULL); + +			for (size_t i=0; i < json_object_array_length (items); i++) { +				json_object *s = json_object_array_get_idx (items, i); +				PianoSong_t *song; + +				if ((song = calloc (1, sizeof (*song))) == NULL) { +					return PIANO_RET_OUT_OF_MEMORY; +				} + +				if (json_object_object_get (s, "artistName") == NULL) { +					free (song); +					continue; +				} +				song->audioUrl = strdup (json_object_get_string (json_object_object_get (json_object_object_get (json_object_object_get (s, "audioUrlMap"), "highQuality"), "audioUrl"))); +				song->artist = PianoJsonStrdup (s, "artistName"); +				song->album = PianoJsonStrdup (s, "albumName"); +				song->title = PianoJsonStrdup (s, "songName"); +				song->trackToken = PianoJsonStrdup (s, "trackToken"); +				song->stationId = PianoJsonStrdup (s, "stationId"); +				song->fileGain = json_object_get_double ( +						json_object_object_get (s, "trackGain")); +				song->audioFormat = PIANO_AF_AACPLUS; +				switch (json_object_get_int (json_object_object_get (s, +						"songRating"))) { +					case 1: +						song->rating = PIANO_RATE_LOVE; +						break; +				} + +				/* begin linked list or append */ +				if (playlist == NULL) { +					playlist = song; +				} else { +					PianoSong_t *curSong = playlist; +					while (curSong->next != NULL) { +						curSong = curSong->next; +					} +					curSong->next = song; +				} +			} + +			reqData->retPlaylist = playlist;  			break;  		} -		case PIANO_REQUEST_RATE_SONG: +		case PIANO_REQUEST_RATE_SONG: {  			/* love/ban song */ -			assert (req->responseData != NULL); - -			ret = PianoXmlParseSimple (req->responseData); -			if (ret == PIANO_RET_OK) { -				PianoRequestDataRateSong_t *reqData = req->data; -				reqData->song->rating = reqData->rating; -			} +			PianoRequestDataRateSong_t *reqData = req->data; +			reqData->song->rating = reqData->rating;  			break; +		}  		case PIANO_REQUEST_ADD_FEEDBACK:  			/* never ever use this directly, low-level call */  			assert (0);  			break; +		case PIANO_REQUEST_RENAME_STATION: { +			/* rename station and update PianoStation_t structure */ +			PianoRequestDataRenameStation_t *reqData = req->data; + +			assert (reqData != NULL); +			assert (reqData->station != NULL); +			assert (reqData->newName != NULL); + +			free (reqData->station->name); +			reqData->station->name = strdup (reqData->newName); +			break; +		} +  		case PIANO_REQUEST_MOVE_SONG: {  			/* move song to different station */  			PianoRequestDataMoveSong_t *reqData = req->data; @@ -977,107 +920,204 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {  			assert (reqData != NULL);  			assert (reqData->step < 2); -			ret = PianoXmlParseSimple (req->responseData); -			if (ret == PIANO_RET_OK && reqData->step == 0) { +			if (reqData->step == 0) {  				ret = PIANO_RET_CONTINUE_REQUEST;  				++reqData->step;  			}  			break;  		} -		case PIANO_REQUEST_RENAME_STATION: -			/* rename station and update PianoStation_t structure */ -			assert (req->responseData != NULL); - -			if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { -				PianoRequestDataRenameStation_t *reqData = req->data; +		case PIANO_REQUEST_DELETE_STATION: { +			/* delete station from server and station list */ +			PianoStation_t *station = req->data; -				assert (reqData != NULL); -				assert (reqData->station != NULL); -				assert (reqData->newName != NULL); +			assert (station != NULL); -				free (reqData->station->name); -				reqData->station->name = strdup (reqData->newName); +			/* delete station from local station list */ +			PianoStation_t *curStation = ph->stations, *lastStation = NULL; +			while (curStation != NULL) { +				if (curStation == station) { +					if (lastStation != NULL) { +						lastStation->next = curStation->next; +					} else { +						/* first station in list */ +						ph->stations = curStation->next; +					} +					PianoDestroyStation (curStation); +					free (curStation); +					break; +				} +				lastStation = curStation; +				curStation = curStation->next;  			}  			break; +		} + +		case PIANO_REQUEST_SEARCH: { +			/* search artist/song */ +			PianoRequestDataSearch_t *reqData = req->data; +			PianoSearchResult_t *searchResult; -		case PIANO_REQUEST_DELETE_STATION: -			/* delete station from server and station list */  			assert (req->responseData != NULL); +			assert (reqData != NULL); -			if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) { -				PianoStation_t *station = req->data; +			searchResult = &reqData->searchResult; +			memset (searchResult, 0, sizeof (*searchResult)); -				assert (station != NULL); +			/* get artists */ +			json_object *artists = json_object_object_get (result, "artists"); +			if (artists != NULL) { +				for (size_t i=0; i < json_object_array_length (artists); i++) { +					json_object *a = json_object_array_get_idx (artists, i); +					PianoArtist_t *artist; -				/* delete station from local station list */ -				PianoStation_t *curStation = ph->stations, *lastStation = NULL; -				while (curStation != NULL) { -					if (curStation == station) { -						if (lastStation != NULL) { -							lastStation->next = curStation->next; -						} else { -							/* first station in list */ -							ph->stations = curStation->next; +					if ((artist = calloc (1, sizeof (*artist))) == NULL) { +						return PIANO_RET_OUT_OF_MEMORY; +					} + +					artist->name = PianoJsonStrdup (a, "artistName"); +					artist->musicId = PianoJsonStrdup (a, "musicToken"); + +					/* add result to linked list */ +					if (searchResult->artists == NULL) { +						searchResult->artists = artist; +					} else { +						PianoArtist_t *curArtist = searchResult->artists; +						while (curArtist->next != NULL) { +							curArtist = curArtist->next;  						} -						PianoDestroyStation (curStation); -						free (curStation); -						break; +						curArtist->next = artist;  					} -					lastStation = curStation; -					curStation = curStation->next;  				}  			} -			break; -		case PIANO_REQUEST_SEARCH: { -			/* search artist/song */ -			PianoRequestDataSearch_t *reqData = req->data; +			/* get songs */ +			json_object *songs = json_object_object_get (result, "songs"); +			if (songs != NULL) { +				for (size_t i=0; i < json_object_array_length (songs); i++) { +					json_object *s = json_object_array_get_idx (songs, i); +					PianoSong_t *song; -			assert (req->responseData != NULL); -			assert (reqData != NULL); +					if ((song = calloc (1, sizeof (*song))) == NULL) { +						return PIANO_RET_OUT_OF_MEMORY; +					} -			ret = PianoXmlParseSearch (req->responseData, &reqData->searchResult); +					song->title = PianoJsonStrdup (s, "songName"); +					song->artist = PianoJsonStrdup (s, "artistName"); +					song->musicId = PianoJsonStrdup (s, "musicToken"); + +					/* add result to linked list */ +					if (searchResult->songs == NULL) { +						searchResult->songs = song; +					} else { +						PianoSong_t *curSong = searchResult->songs; +						while (curSong->next != NULL) { +							curSong = curSong->next; +						} +						curSong->next = song; +					} +				} +			}  			break;  		}  		case PIANO_REQUEST_CREATE_STATION: {  			/* create station, insert new station into station list on success */ -			assert (req->responseData != NULL); +			PianoStation_t *tmpStation; -			ret = PianoXmlParseCreateStation (ph, req->responseData); -			break; -		} - -		case PIANO_REQUEST_ADD_SEED: { -			/* add seed to station, updates station structure */ -			PianoRequestDataAddSeed_t *reqData = req->data; +			if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { +				return PIANO_RET_OUT_OF_MEMORY; +			} -			assert (req->responseData != NULL); -			assert (reqData != NULL); -			assert (reqData->station != NULL); +			PianoJsonParseStation (result, tmpStation); -			/* FIXME: update station data instead of replacing them */ -			ret = PianoXmlParseAddSeed (ph, req->responseData, reqData->station); +			/* start new linked list or append */ +			if (ph->stations == NULL) { +				ph->stations = tmpStation; +			} else { +				PianoStation_t *curStation = ph->stations; +				while (curStation->next != NULL) { +					curStation = curStation->next; +				} +				curStation->next = tmpStation; +			}  			break;  		} +		case PIANO_REQUEST_ADD_SEED:  		case PIANO_REQUEST_ADD_TIRED_SONG:  		case PIANO_REQUEST_SET_QUICKMIX:  		case PIANO_REQUEST_BOOKMARK_SONG:  		case PIANO_REQUEST_BOOKMARK_ARTIST:  		case PIANO_REQUEST_DELETE_FEEDBACK: -			assert (req->responseData != NULL); - -			ret = PianoXmlParseSimple (req->responseData); +		case PIANO_REQUEST_DELETE_SEED: +			/* response unused */  			break; -		case PIANO_REQUEST_GET_GENRE_STATIONS: +		case PIANO_REQUEST_GET_GENRE_STATIONS: {  			/* get genre stations */ -			assert (req->responseData != NULL); +			json_object *categories = json_object_object_get (result, "categories"); +			if (categories != NULL) { +				for (size_t i = 0; i < json_object_array_length (categories); i++) { +					json_object *c = json_object_array_get_idx (categories, i); +					PianoGenreCategory_t *tmpGenreCategory; + +					if ((tmpGenreCategory = calloc (1, +							sizeof (*tmpGenreCategory))) == NULL) { +						return PIANO_RET_OUT_OF_MEMORY; +					} -			ret = PianoXmlParseGenreExplorer (ph, req->responseData); +					tmpGenreCategory->name = PianoJsonStrdup (c, +							"categoryName"); + +					/* get genre subnodes */ +					json_object *stations = json_object_object_get (c, +							"stations"); +					if (stations != NULL) { +						for (size_t k = 0; +								k < json_object_array_length (stations); k++) { +							json_object *s = +									json_object_array_get_idx (stations, k); +							PianoGenre_t *tmpGenre; + +							if ((tmpGenre = calloc (1, +									sizeof (*tmpGenre))) == NULL) { +								return PIANO_RET_OUT_OF_MEMORY; +							} + +							/* get genre attributes */ +							tmpGenre->name = PianoJsonStrdup (s, +									"stationName"); +							tmpGenre->musicId = PianoJsonStrdup (s, +									"stationToken"); + +							/* append station */ +							if (tmpGenreCategory->genres == NULL) { +								tmpGenreCategory->genres = tmpGenre; +							} else { +								PianoGenre_t *curGenre = +										tmpGenreCategory->genres; +								while (curGenre->next != NULL) { +									curGenre = curGenre->next; +								} +								curGenre->next = tmpGenre; +							} +						} +					} +					/* append category */ +					if (ph->genreStations == NULL) { +						ph->genreStations = tmpGenreCategory; +					} else { +						PianoGenreCategory_t *curCat = ph->genreStations; +						while (curCat->next != NULL) { +							curCat = curCat->next; +						} +						curCat->next = tmpGenreCategory; +					} +				} +			}  			break; +		}  		case PIANO_REQUEST_TRANSFORM_STATION: {  			/* transform shared station into private and update isCreator flag */ @@ -1086,27 +1126,51 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {  			assert (req->responseData != NULL);  			assert (station != NULL); -			/* though this call returns a bunch of "new" data only this one is -			 * changed and important (at the moment) */ -			if ((ret = PianoXmlParseTranformStation (req->responseData)) == -					PIANO_RET_OK) { -				station->isCreator = 1; -			} +			station->isCreator = 1;  			break;  		}  		case PIANO_REQUEST_EXPLAIN: {  			/* explain why song was selected */  			PianoRequestDataExplain_t *reqData = req->data; +			const size_t strSize = 1024; +			size_t pos = 0; -			assert (req->responseData != NULL);  			assert (reqData != NULL); -			ret = PianoXmlParseNarrative (req->responseData, &reqData->retExplain); +			json_object *explanations = json_object_object_get (result, +					"explanations"); +			if (explanations != NULL) { +				reqData->retExplain = malloc (strSize * +						sizeof (*reqData->retExplain)); +				strncpy (reqData->retExplain, "We're playing this track " +						"because it features ", strSize); +				pos = strlen (reqData->retExplain); +				for (size_t i=0; i < json_object_array_length (explanations); i++) { +					json_object *e = json_object_array_get_idx (explanations, +							i); +					const char *s = json_object_get_string ( +							json_object_object_get (e, "focusTraitName")); + +					strncpy (&reqData->retExplain[pos], s, strSize-pos-1); +					pos += strlen (s); +					if (i < json_object_array_length (explanations)-2) { +						strncpy (&reqData->retExplain[pos], ", ", strSize-pos-1); +						pos += 2; +					} else if (i == json_object_array_length (explanations)-2) { +						strncpy (&reqData->retExplain[pos], " and ", strSize-pos-1); +						pos += 5; +					} else { +						strncpy (&reqData->retExplain[pos], ".", strSize-pos-1); +						pos += 1; +					} +				} +			}  			break;  		}  		case PIANO_REQUEST_GET_SEED_SUGGESTIONS: { +#if 0  			/* find similar artists */  			PianoRequestDataGetSeedSuggestions_t *reqData = req->data; @@ -1115,29 +1179,122 @@ PianoReturn_t PianoResponse (PianoHandle_t *ph, PianoRequest_t *req) {  			ret = PianoXmlParseSeedSuggestions (req->responseData,  					&reqData->searchResult); +#endif  			break;  		}  		case PIANO_REQUEST_GET_STATION_INFO: {  			/* get station information (seeds and feedback) */  			PianoRequestDataGetStationInfo_t *reqData = req->data; +			PianoStationInfo_t *info; -			assert (req->responseData != NULL);  			assert (reqData != NULL); -			ret = PianoXmlParseGetStationInfo (req->responseData, -					&reqData->info); -			break; -		} +			info = &reqData->info; +			assert (info != NULL); + +			/* parse music seeds */ +			json_object *music = json_object_object_get (result, "music"); +			if (music != NULL) { +				/* songs */ +				json_object *songs = json_object_object_get (music, "songs"); +				if (songs != NULL) { +					for (size_t i = 0; i < json_object_array_length (songs); i++) { +						json_object *s = json_object_array_get_idx (songs, i); +						PianoSong_t *seedSong; + +						seedSong = calloc (1, sizeof (*seedSong)); +						if (seedSong == NULL) { +							return PIANO_RET_OUT_OF_MEMORY; +						} -		case PIANO_REQUEST_DELETE_SEED: { -			assert (req->responseData != NULL); +						seedSong->title = PianoJsonStrdup (s, "songName"); +						seedSong->artist = PianoJsonStrdup (s, "artistName"); +						seedSong->seedId = PianoJsonStrdup (s, "seedId"); -			/* dummy function, checks for errors only */ -			ret = PianoXmlParseTranformStation (req->responseData); +						if (info->songSeeds == NULL) { +							info->songSeeds = seedSong; +						} else { +							PianoSong_t *curSong = info->songSeeds; +							while (curSong->next != NULL) { +								curSong = curSong->next; +							} +							curSong->next = seedSong; +						} +					} +				} + +				/* artists */ +				json_object *artists = json_object_object_get (music, +						"artists"); +				if (artists != NULL) { +					for (size_t i = 0; i < json_object_array_length (artists); i++) { +						json_object *a = json_object_array_get_idx (artists, i); +						PianoArtist_t *seedArtist; + +						seedArtist = calloc (1, sizeof (*seedArtist)); +						if (seedArtist == NULL) { +							return PIANO_RET_OUT_OF_MEMORY; +						} + +						seedArtist->name = PianoJsonStrdup (a, "artistName"); +						seedArtist->seedId = PianoJsonStrdup (a, "seedId"); + +						if (info->artistSeeds == NULL) { +							info->artistSeeds = seedArtist; +						} else { +							PianoArtist_t *curArtist = info->artistSeeds; +							while (curArtist->next != NULL) { +								curArtist = curArtist->next; +							} +							curArtist->next = seedArtist; +						} +					} +				} +			} + +			/* parse feedback */ +			json_object *feedback = json_object_object_get (result, +					"feedback"); +			if (feedback != NULL) { +				json_object_object_foreach (feedback, key, val) { +					for (size_t i = 0; i < json_object_array_length (val); i++) { +						json_object *s = json_object_array_get_idx (val, i); +						PianoSong_t *feedbackSong; + +						feedbackSong = calloc (1, sizeof (*feedbackSong)); +						if (feedbackSong == NULL) { +							return PIANO_RET_OUT_OF_MEMORY; +						} + +						feedbackSong->title = PianoJsonStrdup (s, "songName"); +						feedbackSong->artist = PianoJsonStrdup (s, +								"artistName"); +						feedbackSong->feedbackId = PianoJsonStrdup (s, +								"feedbackId"); +						feedbackSong->rating = json_object_get_boolean ( +								json_object_object_get (s, "isPositive")) ? +								PIANO_RATE_LOVE : PIANO_RATE_BAN; + + +						if (info->feedback == NULL) { +							info->feedback = feedbackSong; +						} else { +							PianoSong_t *curSong = info->feedback; +							while (curSong->next != NULL) { +								curSong = curSong->next; +							} +							curSong->next = feedbackSong; +						} +					} +				} +			} +			break;  		}  	} +	json_object_put (j); +  	return ret;  } @@ -1171,70 +1328,51 @@ const char *PianoErrorToStr (PianoReturn_t ret) {  			return "Unknown.";  			break; -		case PIANO_RET_XML_INVALID: -			return "Invalid XML."; -			break; - -		case PIANO_RET_AUTH_TOKEN_INVALID: -			return "Invalid auth token."; -			break; -		 -		case PIANO_RET_AUTH_USER_PASSWORD_INVALID: -			return "Username and/or password not correct."; -			break; - -		case PIANO_RET_NOT_AUTHORIZED: -			return "Not authorized."; -			break; - -		case PIANO_RET_PROTOCOL_INCOMPATIBLE: -			return "Protocol incompatible. Please upgrade " PACKAGE "."; +		case PIANO_RET_INVALID_RESPONSE: +			return "Invalid response.";  			break; -		case PIANO_RET_READONLY_MODE: -			return "Request cannot be completed at this time, please try " -					"again later."; -			break; - -		case PIANO_RET_STATION_CODE_INVALID: -			return "Station id is invalid."; +		case PIANO_RET_CONTINUE_REQUEST: +			/* never shown to the user */ +			assert (0); +			return "Fix your program.";  			break; -		case PIANO_RET_IP_REJECTED: -			return "Your ip address was rejected. Please setup a control " -					"proxy (see manpage)."; +		case PIANO_RET_OUT_OF_MEMORY: +			return "Out of memory.";  			break; -		case PIANO_RET_STATION_NONEXISTENT: -			return "Station does not exist."; +		/* pandora error messages */ +		case PIANO_RET_P_INTERNAL: +			return "Internal error.";  			break; -		case PIANO_RET_OUT_OF_MEMORY: -			return "Out of memory."; +		case PIANO_RET_P_CALL_NOT_ALLOWED: +			return "Call not allowed.";  			break; -		case PIANO_RET_OUT_OF_SYNC: -			return "Out of sync. Please correct your system's time."; +		case PIANO_RET_P_INVALID_AUTH_TOKEN: +			return "Invalid auth token.";  			break; -		case PIANO_RET_PLAYLIST_END: -			return "Playlist end."; +		case PIANO_RET_P_MAINTENANCE_MODE: +			return "Maintenance mode.";  			break; -		case PIANO_RET_QUICKMIX_NOT_PLAYABLE: -			return "Quickmix not playable."; +		case PIANO_RET_P_MAX_STATIONS_REACHED: +			return "Max number of stations reached.";  			break; -		case PIANO_RET_REMOVING_TOO_MANY_SEEDS: -			return "Last seed cannot be removed."; +		case PIANO_RET_P_READ_ONLY_MODE: +			return "Read only mode. Try again later.";  			break; -		case PIANO_RET_EXCESSIVE_ACTIVITY: -			return "Excessive activity."; +		case PIANO_RET_P_STATION_DOES_NOT_EXIST: +			return "Station does not exist.";  			break; -		case PIANO_RET_DAILY_SKIP_LIMIT_REACHED: -			return "Daily skip limit reached."; +		case PIANO_RET_P_INVALID_PARTNER_LOGIN: +			return "Invalid partner login.";  			break;  		default: diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h index d582844..8a21d05 100644 --- a/src/libpiano/piano.h +++ b/src/libpiano/piano.h @@ -31,11 +31,13 @@ THE SOFTWARE.   * all strings _must_ be utf-8 encoded. i won't care, but pandora does. so   * be nice and check the encoding of your strings. thanks :) */ -#define PIANO_RPC_HOST "www.pandora.com" -#define PIANO_RPC_PORT "80" +/* Pandora API documentation is available at + * http://pan-do-ra-api.wikia.com + */ + +#define PIANO_RPC_HOST "tuner.pandora.com"  typedef struct PianoUserInfo { -	char *webAuthToken;  	char *listenerId;  	char *authToken;  } PianoUserInfo_t; @@ -66,10 +68,8 @@ typedef enum {  typedef struct PianoSong {  	char *artist; -	char *artistMusicId;  	char *stationId;  	char *album; -	char *userSeed;  	char *audioUrl;  	char *coverArt;  	char *musicId; @@ -106,12 +106,13 @@ typedef struct PianoGenreCategory {  } PianoGenreCategory_t;  typedef struct PianoHandle { -	char routeId[9];  	PianoUserInfo_t user;  	/* linked lists */  	PianoStation_t *stations;  	PianoGenreCategory_t *genreStations;  	int timeOffset; +	char *partnerAuthToken; +	unsigned int partnerId;  } PianoHandle_t;  typedef struct PianoSearchResult { @@ -235,26 +236,58 @@ typedef struct {  	PianoStation_t *station;  } PianoRequestDataDeleteSeed_t; +/* pandora error code offset */ +#define PIANO_RET_OFFSET 1024  typedef enum {  	PIANO_RET_ERR = 0,  	PIANO_RET_OK = 1, -	PIANO_RET_XML_INVALID = 2, -	PIANO_RET_AUTH_TOKEN_INVALID = 3, -	PIANO_RET_AUTH_USER_PASSWORD_INVALID = 4, -	PIANO_RET_CONTINUE_REQUEST = 5, -	PIANO_RET_NOT_AUTHORIZED = 6, -	PIANO_RET_PROTOCOL_INCOMPATIBLE = 7, -	PIANO_RET_READONLY_MODE = 8, -	PIANO_RET_STATION_CODE_INVALID = 9, -	PIANO_RET_IP_REJECTED = 10, -	PIANO_RET_STATION_NONEXISTENT = 11, -	PIANO_RET_OUT_OF_MEMORY = 12, -	PIANO_RET_OUT_OF_SYNC = 13, -	PIANO_RET_PLAYLIST_END = 14, -	PIANO_RET_QUICKMIX_NOT_PLAYABLE = 15, -	PIANO_RET_REMOVING_TOO_MANY_SEEDS = 16, -	PIANO_RET_EXCESSIVE_ACTIVITY = 17, -	PIANO_RET_DAILY_SKIP_LIMIT_REACHED = 18, +	PIANO_RET_INVALID_RESPONSE = 2, +	PIANO_RET_CONTINUE_REQUEST = 3, +	PIANO_RET_OUT_OF_MEMORY = 4, + +	PIANO_RET_P_INTERNAL = PIANO_RET_OFFSET+0, +	PIANO_RET_P_API_VERSION_NOT_SUPPORTED = PIANO_RET_OFFSET+11, +	PIANO_RET_P_BIRTH_YEAR_INVALID = PIANO_RET_OFFSET+1025, +	PIANO_RET_P_BIRTH_YEAR_TOO_YOUNG = PIANO_RET_OFFSET+1026, +	PIANO_RET_P_CALL_NOT_ALLOWED = PIANO_RET_OFFSET+1008, +	PIANO_RET_P_CERTIFICATE_REQUIRED = PIANO_RET_OFFSET+7, +	PIANO_RET_P_COMPLIMENTARY_PERIOD_ALREADY_IN_USE = PIANO_RET_OFFSET+1007, +	PIANO_RET_P_DAILY_TRIAL_LIMIT_REACHED = PIANO_RET_OFFSET+1035, +	PIANO_RET_P_DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT = PIANO_RET_OFFSET+1014, +	PIANO_RET_P_DEVICE_DISABLED = PIANO_RET_OFFSET+1034, +	PIANO_RET_P_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1023, +	PIANO_RET_P_DEVICE_NOT_FOUND = PIANO_RET_OFFSET+1009, +	PIANO_RET_P_EXPLICIT_PIN_INCORRECT = PIANO_RET_OFFSET+1018, +	PIANO_RET_P_EXPLICIT_PIN_MALFORMED = PIANO_RET_OFFSET+1020, +	PIANO_RET_P_INSUFFICIENT_CONNECTIVITY = PIANO_RET_OFFSET+13, +	PIANO_RET_P_INVALID_AUTH_TOKEN = PIANO_RET_OFFSET+1001, +	PIANO_RET_P_INVALID_COUNTRY_CODE = PIANO_RET_OFFSET+1027, +	PIANO_RET_P_INVALID_GENDER = PIANO_RET_OFFSET+1027, +	PIANO_RET_P_INVALID_PARTNER_LOGIN = PIANO_RET_OFFSET+1002, +	PIANO_RET_P_INVALID_PASSWORD = PIANO_RET_OFFSET+1012, +	PIANO_RET_P_INVALID_SPONSOR = PIANO_RET_OFFSET+1036, +	PIANO_RET_P_INVALID_USERNAME = PIANO_RET_OFFSET+1011, +	PIANO_RET_P_LICENSING_RESTRICTIONS = PIANO_RET_OFFSET+12, +	PIANO_RET_P_MAINTENANCE_MODE = PIANO_RET_OFFSET+1, +	PIANO_RET_P_MAX_STATIONS_REACHED = PIANO_RET_OFFSET+1005, +	PIANO_RET_P_PARAMETER_MISSING = PIANO_RET_OFFSET+9, +	PIANO_RET_P_PARAMETER_TYPE_MISMATCH = PIANO_RET_OFFSET+8, +	PIANO_RET_P_PARAMETER_VALUE_INVALID = PIANO_RET_OFFSET+10, +	PIANO_RET_P_PARTNER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1010, +	PIANO_RET_P_READ_ONLY_MODE = PIANO_RET_OFFSET+1000, +	PIANO_RET_P_SECURE_PROTOCOL_REQUIRED = PIANO_RET_OFFSET+6, +	PIANO_RET_P_STATION_DOES_NOT_EXIST = PIANO_RET_OFFSET+1006, +	PIANO_RET_P_UPGRADE_DEVICE_MODEL_INVALID = PIANO_RET_OFFSET+1015, +	PIANO_RET_P_URL_PARAM_MISSING_AUTH_TOKEN = PIANO_RET_OFFSET+3, +	PIANO_RET_P_URL_PARAM_MISSING_METHOD = PIANO_RET_OFFSET+2, +	PIANO_RET_P_URL_PARAM_MISSING_PARTNER_ID = PIANO_RET_OFFSET+4, +	PIANO_RET_P_URL_PARAM_MISSING_USER_ID = PIANO_RET_OFFSET+5, +	PIANO_RET_P_USERNAME_ALREADY_EXISTS = PIANO_RET_OFFSET+1013, +	PIANO_RET_P_USER_ALREADY_USED_TRIAL = PIANO_RET_OFFSET+1037, +	PIANO_RET_P_USER_NOT_ACTIVE = PIANO_RET_OFFSET+1003, +	PIANO_RET_P_USER_NOT_AUTHORIZED = PIANO_RET_OFFSET+1004, +	PIANO_RET_P_ZIP_CODE_INVALID = PIANO_RET_OFFSET+1024, +  } PianoReturn_t;  void PianoInit (PianoHandle_t *); diff --git a/src/libpiano/xml.c b/src/libpiano/xml.c deleted file mode 100644 index d194963..0000000 --- a/src/libpiano/xml.c +++ /dev/null @@ -1,980 +0,0 @@ -/* -Copyright (c) 2008-2011 -	Lars-Dominik Braun <lars@6xq.net> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef __FreeBSD__ -#define _BSD_SOURCE /* required by strdup() */ -#define _DARWIN_C_SOURCE /* strdup() on OS X */ -#endif - -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <ezxml.h> -#include <assert.h> - -#include "xml.h" -#include "piano.h" -#include "crypt.h" -#include "config.h" -#include "piano_private.h" - -static void PianoXmlStructParser (const ezxml_t, -		void (*callback) (const char *, const ezxml_t, void *), void *); -static char *PianoXmlGetNodeText (const ezxml_t); - -/*	parse fault and get fault type - *	@param xml <name> content - *	@param xml <value> node - *	@param return error string - *	@return nothing - */ -static void PianoXmlIsFaultCb (const char *key, const ezxml_t value, -		void *data) { -	PianoReturn_t *ret = data; -	char *valueStr = PianoXmlGetNodeText (value); -	char *matchStart, *matchEnd; - -	if (strcmp ("faultString", key) == 0) { -		*ret = PIANO_RET_ERR; -		/* find fault identifier in a string like this: -		 * com.savagebeast.radio.api.protocol.xmlrpc.RadioXmlRpcException: -		 * 192.168.160.78|1213101717317|AUTH_INVALID_TOKEN| -		 * Invalid auth token */ -		if ((matchStart = strchr (valueStr, '|')) != NULL) { -			if ((matchStart = strchr (matchStart+1, '|')) != NULL) { -				if ((matchEnd = strchr (matchStart+1, '|')) != NULL) { -					/* changes text in xml node, but we don't care... */ -					*matchEnd = '\0'; -					++matchStart; -					/* translate to our error message system */ -					if (strcmp ("AUTH_INVALID_TOKEN", matchStart) == 0) { -						*ret = PIANO_RET_AUTH_TOKEN_INVALID; -					} else if (strcmp ("AUTH_INVALID_USERNAME_PASSWORD", -							matchStart) == 0) { -						*ret = PIANO_RET_AUTH_USER_PASSWORD_INVALID; -					} else if (strcmp ("LISTENER_NOT_AUTHORIZED", -							matchStart) == 0) { -						*ret = PIANO_RET_NOT_AUTHORIZED; -					} else if (strcmp ("INCOMPATIBLE_VERSION", -							matchStart) == 0) { -						*ret = PIANO_RET_PROTOCOL_INCOMPATIBLE; -					} else if (strcmp ("READONLY_MODE", matchStart) == 0) { -						*ret = PIANO_RET_READONLY_MODE; -					} else if (strcmp ("STATION_CODE_INVALID", -							matchStart) == 0) { -						*ret = PIANO_RET_STATION_CODE_INVALID; -					} else if (strcmp ("STATION_DOES_NOT_EXIST", -							matchStart) == 0) { -						*ret = PIANO_RET_STATION_NONEXISTENT; -					} else if (strcmp ("OUT_OF_SYNC", matchStart) == 0) { -						*ret = PIANO_RET_OUT_OF_SYNC; -					} else if (strcmp ("PLAYLIST_END", matchStart) == 0) { -						*ret = PIANO_RET_PLAYLIST_END; -					} else if (strcmp ("QUICKMIX_NOT_PLAYABLE", matchStart) == 0) { -						*ret = PIANO_RET_QUICKMIX_NOT_PLAYABLE; -					} else if (strcmp ("REMOVING_TOO_MANY_SEEDS", matchStart) == 0) { -						*ret = PIANO_RET_REMOVING_TOO_MANY_SEEDS; -					} else if (strcmp ("EXCESSIVE_ACTIVITY", matchStart) == 0) { -						*ret = PIANO_RET_EXCESSIVE_ACTIVITY; -					} else if (strcmp ("DAILY_SKIP_LIMIT_REACHED", matchStart) == 0) { -						*ret = PIANO_RET_DAILY_SKIP_LIMIT_REACHED; -					} else { -						*ret = PIANO_RET_ERR; -						printf (PACKAGE ": Unknown error %s in %s\n", -								matchStart, valueStr); -					} -				} -			} -		} -	} else if (strcmp ("faultCode", key) == 0) { -		/* some errors can only be identified by looking at their id */ -		/* detect pandora's ip restriction */ -		if (strcmp ("12", valueStr) == 0) { -			*ret = PIANO_RET_IP_REJECTED; -		} -	} -} - -/*	check whether pandora returned an error or not - *	@param document root of xml doc - *	@return _RET_OK or fault code (_RET_*) - */ -static PianoReturn_t PianoXmlIsFault (ezxml_t xmlDoc) { -	PianoReturn_t ret; - -	if ((xmlDoc = ezxml_child (xmlDoc, "fault")) != NULL) { -		xmlDoc = ezxml_get (xmlDoc, "value", 0, "struct", -1); -		PianoXmlStructParser (xmlDoc, PianoXmlIsFaultCb, &ret); -		return ret; -	} -	return PIANO_RET_OK; -} - -/*	parses things like this: - *	<struct> - *		<member> - *			<name /> - *			<value /> - *		</member> - *		<!-- ... --> - *	</struct> - *	@param xml node named "struct" (or containing a similar structure) - *	@param who wants to use this data? callback: content of <name> as - *			string, content of <value> as xmlNode (may contain other nodes - *			or text), additional data used by callback(); don't forget - *			to *copy* data taken from <name> or <value> as they will be - *			freed soon - *	@param extra data for callback - */ -static void PianoXmlStructParser (const ezxml_t structRoot, -		void (*callback) (const char *, const ezxml_t, void *), void *data) { -	ezxml_t curNode, keyNode, valueNode; -	char *key; - -	/* get all <member> nodes */ -    for (curNode = ezxml_child (structRoot, "member"); curNode; curNode = curNode->next) { -		/* reset variables */ -		key = NULL; -		valueNode = keyNode = NULL; - -		keyNode = ezxml_child (curNode, "name"); -		if (keyNode != NULL) { -			key = ezxml_txt (keyNode); -		} - -		valueNode = ezxml_child (curNode, "value"); -		/* this will ignore empty <value /> nodes, but well... */ -		if (*key != '\0' && valueNode != NULL) { -			(*callback) ((char *) key, valueNode, data); -		} -	} -} - -/*	create xml parser from string - *	@param xml document - *	@param returns document pointer (needed to free memory later) - *	@param returns document root - *	@return _OK or error - */ -static PianoReturn_t PianoXmlInitDoc (char *xmlStr, ezxml_t *xmlDoc) { -	PianoReturn_t ret; - -	if ((*xmlDoc = ezxml_parse_str (xmlStr, strlen (xmlStr))) == NULL) { -		return PIANO_RET_XML_INVALID; -	} - -	if ((ret = PianoXmlIsFault (*xmlDoc)) != PIANO_RET_OK) { -		ezxml_free (*xmlDoc); -		return ret; -	} - -	return PIANO_RET_OK; -} - -/*	get text from <value> nodes; some of them have <boolean>, <string> - *	or <int> subnodes, just ignore them - *	@param xml node <value> - */ -static char *PianoXmlGetNodeText (const ezxml_t node) { -	char *retTxt = NULL; - -	retTxt = ezxml_txt (node); -	/* no text => empty string */ -	if (*retTxt == '\0') { -		retTxt = ezxml_txt (node->child); -	} -	return retTxt; -} - -/*	structParser callback; writes userinfo to PianoUserInfo structure - *	@param value identifier - *	@param value node - *	@param pointer to userinfo structure - *	@return nothing - */ -static void PianoXmlParseUserinfoCb (const char *key, const ezxml_t value, -		void *data) { -	PianoUserInfo_t *user = data; -	char *valueStr = PianoXmlGetNodeText (value); - -	if (strcmp ("webAuthToken", key) == 0) { -		user->webAuthToken = strdup (valueStr); -	} else if (strcmp ("authToken", key) == 0) { -		user->authToken = strdup (valueStr); -	} else if (strcmp ("listenerId", key) == 0) { -		user->listenerId = strdup (valueStr); -	} -} - -static void PianoXmlParseStationsCb (const char *key, const ezxml_t value, -		void *data) { -	PianoStation_t *station = data; -	char *valueStr = PianoXmlGetNodeText (value); - -	if (strcmp ("stationName", key) == 0) { -		station->name = strdup (valueStr); -	} else if (strcmp ("stationId", key) == 0) { -		station->id = strdup (valueStr); -	} else if (strcmp ("isQuickMix", key) == 0) { -		station->isQuickMix = (strcmp (valueStr, "1") == 0); -	} else if (strcmp ("isCreator", key) == 0) { -		station->isCreator = (strcmp (valueStr, "1") == 0); -	} -} - -static void PianoXmlParsePlaylistCb (const char *key, const ezxml_t value, -		void *data) { -	PianoSong_t *song = data; -	char *valueStr = PianoXmlGetNodeText (value); - -	if (strcmp ("audioURL", key) == 0) { -		/* last 48 chars of audioUrl are encrypted, but they put the key -		 * into the door's lock... */ -		const char urlTailN = 48; -		const size_t valueStrN = strlen (valueStr); -		char *urlTail = NULL, -				*urlTailCrypted = &valueStr[valueStrN - urlTailN]; - -		/* don't try to decrypt if string is too short (=> invalid memory -		 * reads/writes) */ -		if (valueStrN > urlTailN && -				(urlTail = PianoDecryptString (urlTailCrypted, NULL)) != NULL) { -			if ((song->audioUrl = calloc (valueStrN + 1, -					sizeof (*song->audioUrl))) != NULL) { -				memcpy (song->audioUrl, valueStr, valueStrN - urlTailN); -				/* FIXME: the key seems to be broken... so ignore 8 x 0x08 -				 * postfix; urlTailN/2 because the encrypted hex string is now -				 * decoded */ -				memcpy (&song->audioUrl[valueStrN - urlTailN], urlTail, -						urlTailN/2 - 8); -			} -			free (urlTail); -		} -	} else if (strcmp ("artRadio", key) == 0) { -		song->coverArt = strdup (valueStr); -	} else if (strcmp ("artistSummary", key) == 0) { -		song->artist = strdup (valueStr); -	} else if (strcmp ("musicId", key) == 0) { -		song->musicId = strdup (valueStr); -	} else if (strcmp ("userSeed", key) == 0) { -		song->userSeed = strdup (valueStr); -	} else if (strcmp ("songTitle", key) == 0) { -		song->title = strdup (valueStr); -	} else if (strcmp ("rating", key) == 0) { -		if (strcmp (valueStr, "1") == 0) { -			song->rating = PIANO_RATE_LOVE; -		} else { -			song->rating = PIANO_RATE_NONE; -		} -	} else if (strcmp ("isPositive", key) == 0) { -		if (strcmp (valueStr, "1") == 0) { -			song->rating = PIANO_RATE_LOVE; -		} else { -			song->rating = PIANO_RATE_BAN; -		} -	} else if (strcmp ("stationId", key) == 0) { -		song->stationId = strdup (valueStr); -	} else if (strcmp ("albumTitle", key) == 0) { -		song->album = strdup (valueStr); -	} else if (strcmp ("fileGain", key) == 0) { -		song->fileGain = atof (valueStr); -	} else if (strcmp ("audioEncoding", key) == 0) { -		if (strcmp (valueStr, "aacplus") == 0) { -			song->audioFormat = PIANO_AF_AACPLUS; -		} else if (strcmp (valueStr, "mp3") == 0) { -			song->audioFormat = PIANO_AF_MP3; -		} else if (strcmp (valueStr, "mp3-hifi") == 0) { -			song->audioFormat = PIANO_AF_MP3_HI; -		} -	} else if (strcmp ("artistMusicId", key) == 0) { -		song->artistMusicId = strdup (valueStr); -	} else if (strcmp ("feedbackId", key) == 0) { -		song->feedbackId = strdup (valueStr); -	} else if (strcmp ("songDetailURL", key) == 0) { -		song->detailUrl = strdup (valueStr); -	} else if (strcmp ("trackToken", key) == 0) { -		song->trackToken = strdup (valueStr); -	} -} - -/*	parses userinfos sent by pandora as login response - *	@param piano handle - *	@param utf-8 string - *	@return _RET_OK or error - */ -PianoReturn_t PianoXmlParseUserinfo (PianoHandle_t *ph, char *xml) { -	ezxml_t xmlDoc, structNode; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	/* <methodResponse> <params> <param> <value> <struct> */ -	structNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); -	PianoXmlStructParser (structNode, PianoXmlParseUserinfoCb, &ph->user); - -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -static void PianoXmlParseQuickMixStationsCb (const char *key, const ezxml_t value, -		void *data) { -	char ***retIds = data; -	char **ids = NULL; -	size_t idsN = 0; -	ezxml_t curNode; - -	if (strcmp ("quickMixStationIds", key) == 0) { -		for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value"); -				curNode; curNode = curNode->next) { -			idsN++; -			if (ids == NULL) { -				if ((ids = calloc (idsN, sizeof (*ids))) == NULL) { -					*retIds = NULL; -					return; -				} -			} else { -				/* FIXME: memory leak (on failure) */ -				if ((ids = realloc (ids, idsN * sizeof (*ids))) == NULL) { -					*retIds = NULL; -					return; -				} -			} -			ids[idsN-1] = strdup (PianoXmlGetNodeText (curNode)); -		} -		/* append NULL: list ends here */ -		idsN++; -		/* FIXME: copy&waste */ -		if (ids == NULL) { -			if ((ids = calloc (idsN, sizeof (*ids))) == NULL) { -				*retIds = NULL; -				return; -			} -		} else { -			if ((ids = realloc (ids, idsN * sizeof (*ids))) == NULL) { -				*retIds = NULL; -				return; -			} -		} -		ids[idsN-1] = NULL; - -		*retIds = ids; -	} -} - -/*	parse stations returned by pandora - *	@param piano handle - *	@param xml returned by pandora - *	@return _RET_OK or error - */ -PianoReturn_t PianoXmlParseStations (PianoHandle_t *ph, char *xml) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret; -	char **quickMixIds = NULL, **curQuickMixId = NULL; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "array", -			0, "data", -1); - -	for (dataNode = ezxml_child (dataNode, "value"); dataNode; -			dataNode = dataNode->next) { -		PianoStation_t *tmpStation; - -		if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { -			ezxml_free (xmlDoc); -			return PIANO_RET_OUT_OF_MEMORY; -		} - -		PianoXmlStructParser (ezxml_child (dataNode, "struct"), -				PianoXmlParseStationsCb, tmpStation); - -		/* get stations selected for quickmix */ -		if (tmpStation->isQuickMix) { -			PianoXmlStructParser (ezxml_child (dataNode, "struct"), -					PianoXmlParseQuickMixStationsCb, &quickMixIds); -		} -		/* start new linked list or append */ -		if (ph->stations == NULL) { -			ph->stations = tmpStation; -		} else { -			PianoStation_t *curStation = ph->stations; -			while (curStation->next != NULL) { -				curStation = curStation->next; -			} -			curStation->next = tmpStation; -		} -	} -	/* set quickmix flags after all stations are read */ -	if (quickMixIds != NULL) { -		curQuickMixId = quickMixIds; -		while (*curQuickMixId != NULL) { -			PianoStation_t *curStation = PianoFindStationById (ph->stations, -					*curQuickMixId); -			if (curStation != NULL) { -				curStation->useQuickMix = 1; -			} -			free (*curQuickMixId); -			curQuickMixId++; -		} -		free (quickMixIds); -	} - -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -/*	parse "create station" answer (it returns a new station structure) - *	@param piano handle - *	@param xml document - *	@return nothing yet - */ -PianoReturn_t PianoXmlParseCreateStation (PianoHandle_t *ph, char *xml) { -	ezxml_t xmlDoc, dataNode; -	PianoStation_t *tmpStation; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); - -	if ((tmpStation = calloc (1, sizeof (*tmpStation))) == NULL) { -		ezxml_free (xmlDoc); -		return PIANO_RET_OUT_OF_MEMORY; -	} -	PianoXmlStructParser (dataNode, PianoXmlParseStationsCb, tmpStation); -	/* FIXME: copy & waste */ -	/* start new linked list or append */ -	if (ph->stations == NULL) { -		ph->stations = tmpStation; -	} else { -		PianoStation_t *curStation = ph->stations; -		while (curStation->next != NULL) { -			curStation = curStation->next; -		} -		curStation->next = tmpStation; -	} -	 -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -/*	parse "add seed" answer, nearly the same as ParseCreateStation - *	@param piano handle - *	@param xml document - *	@param update this station - */ -PianoReturn_t PianoXmlParseAddSeed (PianoHandle_t *ph, char *xml, -		PianoStation_t *station) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); -	PianoDestroyStation (station); -	PianoXmlStructParser (dataNode, PianoXmlParseStationsCb, station); -	 -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -static PianoReturn_t PianoXmlParsePlaylistStruct (ezxml_t xml, -		PianoSong_t **retSong) { -	PianoSong_t *playlist = *retSong, *tmpSong; -	 -	if ((tmpSong = calloc (1, sizeof (*tmpSong))) == NULL) { -		return PIANO_RET_OUT_OF_MEMORY; -	} - -	PianoXmlStructParser (ezxml_child (xml, "struct"), PianoXmlParsePlaylistCb, -			tmpSong); -	/* begin linked list or append */ -	if (playlist == NULL) { -		playlist = tmpSong; -	} else { -		PianoSong_t *curSong = playlist; -		while (curSong->next != NULL) { -			curSong = curSong->next; -		} -		curSong->next = tmpSong; -	} - -	*retSong = playlist; - -	return PIANO_RET_OK; -} - -/*	parses playlist; used when searching too - *	@param piano handle - *	@param xml document - *	@param return: playlist - */ -PianoReturn_t PianoXmlParsePlaylist (PianoHandle_t *ph, char *xml, -		PianoSong_t **retPlaylist) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret = PIANO_RET_OK; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "array", -			0, "data", -1); - -	for (dataNode = ezxml_child (dataNode, "value"); dataNode; -			dataNode = dataNode->next) { -		if ((ret = PianoXmlParsePlaylistStruct (dataNode, retPlaylist)) != -				PIANO_RET_OK) { -			break; -		} -	} - -	ezxml_free (xmlDoc); - -	return ret; -} - -/*	check for exception only - *	@param xml string - *	@return _OK or error - */ -PianoReturn_t PianoXmlParseSimple (char *xml) { -	ezxml_t xmlDoc; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	ezxml_free (xmlDoc); - -	return ret; -} - -/*	xml struct parser callback, used in PianoXmlParseSearchCb - */ -static void PianoXmlParseSearchArtistCb (const char *key, const ezxml_t value, -		void *data) { -	PianoArtist_t *artist = data; -	char *valueStr = PianoXmlGetNodeText (value); - -	if (strcmp ("artistName", key) == 0) { -		artist->name = strdup (valueStr); -	} else if (strcmp ("musicId", key) == 0) { -		artist->musicId = strdup (valueStr); -	} -} - -/*	callback for xml struct parser used in PianoXmlParseSearch, "switch" for - *	PianoXmlParseSearchArtistCb and PianoXmlParsePlaylistCb - */ -static void PianoXmlParseSearchCb (const char *key, const ezxml_t value, -		void *data) { -	PianoSearchResult_t *searchResult = data; -	ezxml_t curNode; - -	if (strcmp ("artists", key) == 0) { -		/* skip <value><array><data> */ -		for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value"); -				curNode; curNode = curNode->next) { -			PianoArtist_t *artist; -			 -			if ((artist = calloc (1, sizeof (*artist))) == NULL) { -				/* fail silently */ -				break; -			} - -			memset (artist, 0, sizeof (*artist)); - -			PianoXmlStructParser (ezxml_child (curNode, "struct"), -					PianoXmlParseSearchArtistCb, artist); - -			/* add result to linked list */ -			if (searchResult->artists == NULL) { -				searchResult->artists = artist; -			} else { -				PianoArtist_t *curArtist = searchResult->artists; -				while (curArtist->next != NULL) { -					curArtist = curArtist->next; -				} -				curArtist->next = artist; -			} -		} -	} else if (strcmp ("songs", key) == 0) { -		for (curNode = ezxml_child (ezxml_get (value, "array", 0, "data", -1), "value"); -				curNode; curNode = curNode->next) { -			if (PianoXmlParsePlaylistStruct (curNode, &searchResult->songs) != -					PIANO_RET_OK) { -				break; -			} -		} -	} -} - -/*	parse search result; searchResult is nulled before use - *	@param xml document - *	@param returns search result - *	@return nothing yet - */ -PianoReturn_t PianoXmlParseSearch (char *xml, -		PianoSearchResult_t *searchResult) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} -	 -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); -	/* we need a "clean" search result (with null pointers) */ -	memset (searchResult, 0, sizeof (*searchResult)); -	PianoXmlStructParser (dataNode, PianoXmlParseSearchCb, searchResult); - -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -/*	FIXME: copy&waste (PianoXmlParseSearch) - */ -PianoReturn_t PianoXmlParseSeedSuggestions (char *xml, -		PianoSearchResult_t *searchResult) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} -	 -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", -1); -	/* we need a "clean" search result (with null pointers) */ -	memset (searchResult, 0, sizeof (*searchResult)); -	/* reuse seach result parser; structure is nearly the same */ -	PianoXmlParseSearchCb ("artists", dataNode, searchResult); - -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -/*	encode reserved xml chars - *	TODO: remove and use ezxml_ampencode - *	@param encode this - *	@return encoded string or NULL - */ -char *PianoXmlEncodeString (const char *s) { -	static const char *replacements[] = {"&&", "''", "\""", -			"<<", ">>", NULL}; -	const char **r; -	char *sOut, *sOutCurr, found; - -	if ((sOut = calloc (strlen (s) * 5 + 1, sizeof (*sOut))) == NULL) { -		return NULL; -	} - -	sOutCurr = sOut; - -	while (*s != '\0') { -		r = replacements; -		found = 0; -		while (*r != NULL) { -			if (*s == *r[0]) { -				found = 1; -				strcat (sOutCurr, (*r) + 1); -				sOutCurr += strlen ((*r) + 1); -				break; -			} -			r++; -		} -		if (!found) { -			*sOutCurr = *s; -			sOutCurr++; -		} -		s++; -	} -	return sOut; -} - -PianoReturn_t PianoXmlParseGenreExplorer (PianoHandle_t *ph, char *xml) { -	ezxml_t xmlDoc, catNode; -	PianoReturn_t ret; - -    if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -        return ret; -    } - -	/* get all <member> nodes */ -    for (catNode = ezxml_child (xmlDoc, "category"); catNode; -			catNode = catNode->next) { -		PianoGenreCategory_t *tmpGenreCategory; -		ezxml_t genreNode; - -		if ((tmpGenreCategory = calloc (1, sizeof (*tmpGenreCategory))) == NULL) { -			ezxml_free (xmlDoc); -			return PIANO_RET_OUT_OF_MEMORY; -		} - -		tmpGenreCategory->name = strdup (ezxml_attr (catNode, "categoryName")); - -		/* get genre subnodes */ -		for (genreNode = ezxml_child (catNode, "genre"); genreNode; -				genreNode = genreNode->next) { -			PianoGenre_t *tmpGenre; - -			if ((tmpGenre = calloc (1, sizeof (*tmpGenre))) == NULL) { -				ezxml_free (xmlDoc); -				return PIANO_RET_OUT_OF_MEMORY; -			} - -			/* get genre attributes */ -			tmpGenre->name = strdup (ezxml_attr (genreNode, "name")); -			tmpGenre->musicId = strdup (ezxml_attr (genreNode, "musicId")); - -			/* append station */ -			if (tmpGenreCategory->genres == NULL) { -				tmpGenreCategory->genres = tmpGenre; -			} else { -				PianoGenre_t *curGenre = -						tmpGenreCategory->genres; -				while (curGenre->next != NULL) { -					curGenre = curGenre->next; -				} -				curGenre->next = tmpGenre; -			} -		} -		/* append category */ -		if (ph->genreStations == NULL) { -			ph->genreStations = tmpGenreCategory; -		} else { -			PianoGenreCategory_t *curCat = ph->genreStations; -			while (curCat->next != NULL) { -				curCat = curCat->next; -			} -			curCat->next = tmpGenreCategory; -		} -	} - -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -/*	dummy function, only checks for errors - *	@param xml doc - *	@return _OK or error - */ -PianoReturn_t PianoXmlParseTranformStation (char *xml) { -	ezxml_t xmlDoc; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} -	 -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - -/*	parses "why did you play ...?" answer - *	@param xml - *	@param returns the answer - *	@return _OK or error - */ -PianoReturn_t PianoXmlParseNarrative (char *xml, char **retNarrative) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} - -	/* <methodResponse> <params> <param> <value> $textnode */ -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", -1); -	*retNarrative = strdup (ezxml_txt (dataNode)); - -	ezxml_free (xmlDoc); - -	return ret; -} - -/*	seed bag, required because seedId is not part of artist/song struct in - *	pandora's xml response - */ -struct PianoXmlParseSeedBag { -	char *seedId; -	PianoSong_t *song; -	PianoArtist_t *artist; -	PianoStation_t *station; -}; - -/*	parse seed struct - */ -static void PianoXmlParseSeedCb (const char *key, const ezxml_t value, -		void *data) { -	struct PianoXmlParseSeedBag *bag = data; - -	assert (bag != NULL); - -	if (strcmp ("song", key) == 0) { -		assert (bag->song == NULL); - -		if ((bag->song = calloc (1, sizeof (*bag->song))) == NULL) { -			return; -		} - -		PianoXmlStructParser (ezxml_child (value, "struct"), -				PianoXmlParsePlaylistCb, bag->song); -	} else if (strcmp ("artist", key) == 0) { -		assert (bag->artist == NULL); - -		if ((bag->artist = calloc (1, sizeof (*bag->artist))) == NULL) { -			return; -		} - -		PianoXmlStructParser (ezxml_child (value, "struct"), -				PianoXmlParseSearchArtistCb, bag->artist); -	} else if (strcmp ("nonGenomeStation", key) == 0) { -		/* genre stations are "non genome" station seeds */ -		assert (bag->station == NULL); - -		if ((bag->station = calloc (1, sizeof (*bag->station))) == NULL) { -			return; -		} - -		PianoXmlStructParser (ezxml_child (value, "struct"), -				PianoXmlParseStationsCb, bag->station); -	} else if (strcmp ("seedId", key) == 0) { -		char *valueStr = PianoXmlGetNodeText (value); -		bag->seedId = strdup (valueStr); -	} -} - -/*	parse getStation xml struct - */ -static void PianoXmlParseGetStationInfoCb (const char *key, const ezxml_t value, -		void *data) { -	PianoStationInfo_t *info = data; - -	if (strcmp ("seeds", key) == 0) { -		const ezxml_t dataNode = ezxml_get (value, "array", 0, "data", -1); -		for (ezxml_t seedNode = ezxml_child (dataNode, "value"); seedNode; -					seedNode = seedNode->next) { -			struct PianoXmlParseSeedBag bag; -			memset (&bag, 0, sizeof (bag)); - -			PianoXmlStructParser (ezxml_child (seedNode, "struct"), -					PianoXmlParseSeedCb, &bag); - -			assert (bag.song != NULL || bag.artist != NULL || -					bag.station != NULL); - -			if (bag.seedId == NULL) { -				/* seeds without id are useless */ -				continue; -			} - -			/* FIXME: copy&waste */ -			if (bag.song != NULL) { -				bag.song->seedId = bag.seedId; - -				if (info->songSeeds == NULL) { -					info->songSeeds = bag.song; -				} else { -					PianoSong_t *curSong = info->songSeeds; -					while (curSong->next != NULL) { -						curSong = curSong->next; -					} -					curSong->next = bag.song; -				} -			} else if (bag.artist != NULL) { -				bag.artist->seedId = bag.seedId; - -				if (info->artistSeeds == NULL) { -					info->artistSeeds = bag.artist; -				} else { -					PianoArtist_t *curSong = info->artistSeeds; -					while (curSong->next != NULL) { -						curSong = curSong->next; -					} -					curSong->next = bag.artist; -				} -			} else if (bag.station != NULL) { -				bag.station->seedId = bag.seedId; - -				if (info->stationSeeds == NULL) { -					info->stationSeeds = bag.station; -				} else { -					PianoStation_t *curStation = info->stationSeeds; -					while (curStation->next != NULL) { -						curStation = curStation->next; -					} -					curStation->next = bag.station; -				} -			} else { -				free (bag.seedId); -			} -		} -	} else if (strcmp ("feedback", key) == 0) { -		const ezxml_t dataNode = ezxml_get (value, "array", 0, "data", -1); -		for (ezxml_t feedbackNode = ezxml_child (dataNode, "value"); feedbackNode; -					feedbackNode = feedbackNode->next) { -			if (PianoXmlParsePlaylistStruct (feedbackNode, &info->feedback) != -					PIANO_RET_OK) { -				break; -			} -		} -	} -} - -/*	parse getStation response - */ -PianoReturn_t PianoXmlParseGetStationInfo (char *xml, -		PianoStationInfo_t *stationInfo) { -	ezxml_t xmlDoc, dataNode; -	PianoReturn_t ret; - -	if ((ret = PianoXmlInitDoc (xml, &xmlDoc)) != PIANO_RET_OK) { -		return ret; -	} -	 -	dataNode = ezxml_get (xmlDoc, "params", 0, "param", 0, "value", 0, "struct", -1); -	PianoXmlStructParser (dataNode, PianoXmlParseGetStationInfoCb, stationInfo); - -	ezxml_free (xmlDoc); - -	return PIANO_RET_OK; -} - diff --git a/src/libpiano/xml.h b/src/libpiano/xml.h deleted file mode 100644 index 58ee28f..0000000 --- a/src/libpiano/xml.h +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright (c) 2008-2011 -	Lars-Dominik Braun <lars@6xq.net> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -#ifndef _XML_H -#define _XML_H - -#include "piano.h" - -PianoReturn_t PianoXmlParseUserinfo (PianoHandle_t *ph, char *xml); -PianoReturn_t PianoXmlParseStations (PianoHandle_t *ph, char *xml); -PianoReturn_t PianoXmlParsePlaylist (PianoHandle_t *ph, char *xml, -		PianoSong_t **); -PianoReturn_t PianoXmlParseSearch (char *searchXml, -		PianoSearchResult_t *searchResult); -PianoReturn_t PianoXmlParseSimple (char *xml); -PianoReturn_t PianoXmlParseCreateStation (PianoHandle_t *ph, -		char *xml); -PianoReturn_t PianoXmlParseAddSeed (PianoHandle_t *ph, char *xml, -		PianoStation_t *station); -PianoReturn_t PianoXmlParseGenreExplorer (PianoHandle_t *ph, -		char *xmlContent); -PianoReturn_t PianoXmlParseTranformStation (char *searchXml); -PianoReturn_t PianoXmlParseNarrative (char *xml, char **retNarrative); -PianoReturn_t PianoXmlParseSeedSuggestions (char *, PianoSearchResult_t *); -PianoReturn_t PianoXmlParseGetStationInfo (char *, PianoStationInfo_t *); - -char *PianoXmlEncodeString (const char *s); - -#endif /* _XML_H */  | 
