diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libezxml/COPYING | 20 | ||||
-rw-r--r-- | src/libezxml/ezxml.c | 718 | ||||
-rw-r--r-- | src/libezxml/ezxml.h | 93 | ||||
-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 | ||||
-rw-r--r-- | src/settings.c | 4 | ||||
-rw-r--r-- | src/ui.c | 4 | ||||
-rw-r--r-- | src/ui_act.c | 6 |
11 files changed, 755 insertions, 2549 deletions
diff --git a/src/libezxml/COPYING b/src/libezxml/COPYING deleted file mode 100644 index 80e4e88..0000000 --- a/src/libezxml/COPYING +++ /dev/null @@ -1,20 +0,0 @@ -Copyright 2004-2006 Aaron Voisine <aaron@voisine.org> - -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. diff --git a/src/libezxml/ezxml.c b/src/libezxml/ezxml.c deleted file mode 100644 index 0e1dd5d..0000000 --- a/src/libezxml/ezxml.c +++ /dev/null @@ -1,718 +0,0 @@ -/* ezxml.c - * - * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org> - * - * 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 /* required by strdup() on OS X */ -#endif - -#include <stdlib.h> -#include <stdio.h> -#include <stdarg.h> -#include <string.h> -#include <ctype.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include "ezxml.h" - -#define EZXML_WS "\t\r\n " // whitespace -#define EZXML_ERRL 128 // maximum error string length - -typedef struct ezxml_root *ezxml_root_t; -struct ezxml_root { // additional data for the root tag - struct ezxml xml; // is a super-struct built on top of ezxml struct - ezxml_t cur; // current xml tree insertion point - char *m; // original xml string - size_t len; // length of allocated memory for mmap, -1 for malloc - char *u; // UTF-8 conversion of string if original was UTF-16 - char *s; // start of work area - char *e; // end of work area - char **ent; // general entities (ampersand sequences) - char ***attr; // default attributes - char ***pi; // processing instructions - short standalone; // non-zero if <?xml standalone="yes"?> - char err[EZXML_ERRL]; // error string -}; - -char *EZXML_NIL[] = { NULL }; // empty, null terminated array of strings - -// sets a flag for the given tag and returns the tag -static ezxml_t ezxml_set_flag(ezxml_t xml, short flag) { - if (xml) xml->flags |= flag; - return xml; -} - -// inserts an existing tag into an ezxml structure -static ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off) -{ - ezxml_t cur, prev, head; - - xml->next = xml->sibling = xml->ordered = NULL; - xml->off = off; - xml->parent = dest; - - if ((head = dest->child)) { // already have sub tags - if (head->off <= off) { // not first subtag - for (cur = head; cur->ordered && cur->ordered->off <= off; - cur = cur->ordered); - xml->ordered = cur->ordered; - cur->ordered = xml; - } - else { // first subtag - xml->ordered = head; - dest->child = xml; - } - - for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name); - prev = cur, cur = cur->sibling); // find tag type - if (cur && cur->off <= off) { // not first of type - while (cur->next && cur->next->off <= off) cur = cur->next; - xml->next = cur->next; - cur->next = xml; - } - else { // first tag of this type - if (prev && cur) prev->sibling = cur->sibling; // remove old first - xml->next = cur; // old first tag is now next - for (cur = head, prev = NULL; cur && cur->off <= off; - prev = cur, cur = cur->sibling); // new sibling insert point - xml->sibling = cur; - if (prev) prev->sibling = xml; - } - } - else dest->child = xml; // only sub tag - - return xml; -} - -// Adds a child tag. off is the offset of the child tag relative to the start -// of the parent tag's character content. Returns the child tag. -static ezxml_t ezxml_add_child(ezxml_t xml, char *name, size_t off) -{ - ezxml_t child; - - if (! xml) return NULL; - child = (ezxml_t)memset(malloc(sizeof(struct ezxml)), '\0', - sizeof(struct ezxml)); - child->name = (char *)name; - child->attr = EZXML_NIL; - child->txt = ""; - - return ezxml_insert(child, xml, off); -} - -// returns the first child tag with the given name or NULL if not found -ezxml_t ezxml_child(ezxml_t xml, const char *name) -{ - xml = (xml) ? xml->child : NULL; - while (xml && strcmp(name, xml->name)) xml = xml->sibling; - return xml; -} - -// returns a new empty ezxml structure with the given root tag name -static ezxml_t ezxml_new(char *name) -{ - static char *ent[] = { "lt;", "<", "gt;", ">", "quot;", """, - "apos;", "'", "amp;", "&", NULL }; - ezxml_root_t root = (ezxml_root_t)memset(malloc(sizeof(struct ezxml_root)), - '\0', sizeof(struct ezxml_root)); - root->xml.name = (char *)name; - root->cur = &root->xml; - strcpy(root->err, root->xml.txt = ""); - root->ent = memcpy(malloc(sizeof(ent)), ent, sizeof(ent)); - root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL); - return &root->xml; -} - -// returns the Nth tag with the same name in the same subsection or NULL if not -// found -ezxml_t ezxml_idx(ezxml_t xml, int idx) -{ - for (; xml && idx; idx--) xml = xml->next; - return xml; -} - -// returns the value of the requested tag attribute or NULL if not found -const char *ezxml_attr(ezxml_t xml, const char *attr) -{ - int i = 0, j = 1; - ezxml_root_t root = (ezxml_root_t)xml; - - if (! xml || ! xml->attr) return NULL; - while (xml->attr[i] && strcmp(attr, xml->attr[i])) i += 2; - if (xml->attr[i]) return xml->attr[i + 1]; // found attribute - - while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag - for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++); - if (! root->attr[i]) return NULL; // no matching default attributes - while (root->attr[i][j] && strcmp(attr, root->attr[i][j])) j += 3; - return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default -} - -// same as ezxml_get but takes an already initialized va_list -static ezxml_t ezxml_vget(ezxml_t xml, va_list ap) -{ - char *name = va_arg(ap, char *); - int idx = -1; - - if (name && *name) { - idx = va_arg(ap, int); - xml = ezxml_child(xml, name); - } - return (idx < 0) ? xml : ezxml_vget(ezxml_idx(xml, idx), ap); -} - -// Traverses the xml tree to retrieve a specific subtag. Takes a variable -// length list of tag names and indexes. The argument list must be terminated -// by either an index of -1 or an empty string tag name. Example: -// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1); -// This retrieves the title of the 3rd book on the 1st shelf of library. -// Returns NULL if not found. -ezxml_t ezxml_get(ezxml_t xml, ...) -{ - va_list ap; - ezxml_t r; - - va_start(ap, xml); - r = ezxml_vget(xml, ap); - va_end(ap); - return r; -} - -// set an error string and return root -static ezxml_t ezxml_err(ezxml_root_t root, char *s, const char *err, ...) -{ - va_list ap; - int line = 1; - char *t, fmt[EZXML_ERRL]; - - for (t = root->s; t < s; t++) if (*t == '\n') line++; - snprintf(fmt, EZXML_ERRL, "[error near line %d]: %s", line, err); - - va_start(ap, err); - vsnprintf(root->err, EZXML_ERRL, fmt, ap); - va_end(ap); - - return &root->xml; -} - -// Recursively decodes entity and character references and normalizes new lines -// ent is a null terminated array of alternating entity names and values. set t -// to '&' for general entity decoding, '%' for parameter entity decoding, 'c' -// for cdata sections, ' ' for attribute normalization, or '*' for non-cdata -// attribute normalization. Returns s, or if the decoded string is longer than -// s, returns a malloced string that must be freed. -static char *ezxml_decode(char *s, char **ent, char t) -{ - char *e, *r = s, *m = s; - long b, c, d, l; - - for (; *s; s++) { // normalize line endings - while (*s == '\r') { - *(s++) = '\n'; - if (*s == '\n') memmove(s, (s + 1), strlen(s)); - } - } - - for (s = r; ; ) { - while (*s && *s != '&' && (*s != '%' || t != '%') && !isspace(*s)) s++; - - if (! *s) break; - else if (t != 'c' && ! strncmp(s, "&#", 2)) { // character reference - if (s[2] == 'x') c = strtol(s + 3, &e, 16); // base 16 - else c = strtol(s + 2, &e, 10); // base 10 - if (! c || *e != ';') { s++; continue; } // not a character ref - - if (c < 0x80) *(s++) = c; // US-ASCII subset - else { // multi-byte UTF-8 sequence - for (b = 0, d = c; d; d /= 2) b++; // number of bits in c - b = (b - 2) / 5; // number of bytes in payload - *(s++) = (0xFF << (7 - b)) | (c >> (6 * b)); // head - while (b) *(s++) = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload - } - - memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';'))); - } - else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) || - (*s == '%' && t == '%')) { // entity reference - for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b])); - b += 2); // find entity in entity list - - if (ent[b++]) { // found a match - if ((c = strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s) { - l = (d = (s - r)) + c + strlen(e); // new length - r = (r == m) ? strcpy(malloc(l), r) : realloc(r, l); - e = strchr((s = r + d), ';'); // fix up pointers - } - - memmove(s + c, e + 1, strlen(e)); // shift rest of string - strncpy(s, ent[b], c); // copy in replacement text - } - else s++; // not a known entity - } - else if ((t == ' ' || t == '*') && isspace(*s)) *(s++) = ' '; - else s++; // no decoding needed - } - - if (t == '*') { // normalize spaces for non-cdata attributes - for (s = r; *s; s++) { - if ((l = strspn(s, " "))) memmove(s, s + l, strlen(s + l) + 1); - while (*s && *s != ' ') s++; - } - if (--s >= r && *s == ' ') *s = '\0'; // trim any trailing space - } - return r; -} - -// called when parser finds start of new tag -static void ezxml_open_tag(ezxml_root_t root, char *name, char **attr) -{ - ezxml_t xml = root->cur; - - if (xml->name) xml = ezxml_add_child(xml, name, strlen(xml->txt)); - else xml->name = name; // first open tag - - xml->attr = attr; - root->cur = xml; // update tag insertion point -} - -// called when parser finds character content between open and closing tag -static void ezxml_char_content(ezxml_root_t root, char *s, size_t len, char t) -{ - ezxml_t xml = root->cur; - char *m = s; - size_t l; - - if (! xml || ! xml->name || ! len) return; // sanity check - - s[len] = '\0'; // null terminate text (calling functions anticipate this) - len = strlen(s = ezxml_decode(s, root->ent, t)) + 1; - - if (! *(xml->txt)) xml->txt = s; // initial character content - else { // allocate our own memory and make a copy - xml->txt = (xml->flags & EZXML_TXTM) // allocate some space - ? realloc(xml->txt, (l = strlen(xml->txt)) + len) - : strcpy(malloc((l = strlen(xml->txt)) + len), xml->txt); - strcpy(xml->txt + l, s); // add new char content - if (s != m) free(s); // free s if it was malloced by ezxml_decode() - } - - if (xml->txt != m) ezxml_set_flag(xml, EZXML_TXTM); -} - -// called when parser finds closing tag -static ezxml_t ezxml_close_tag(ezxml_root_t root, char *name, char *s) -{ - if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name)) - return ezxml_err(root, s, "unexpected closing tag </%s>", name); - - root->cur = root->cur->parent; - return NULL; -} - -// checks for circular entity references, returns non-zero if no circular -// references are found, zero otherwise -static int ezxml_ent_ok(char *name, char *s, char **ent) -{ - int i; - - for (; ; s++) { - while (*s && *s != '&') s++; // find next entity reference - if (! *s) return 1; - if (! strncmp(s + 1, name, strlen(name))) return 0; // circular ref. - for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2); - if (ent[i] && ! ezxml_ent_ok(name, ent[i + 1], ent)) return 0; - } -} - -// called when the parser finds a processing instruction -static void ezxml_proc_inst(ezxml_root_t root, char *s, size_t len) -{ - int i = 0, j = 1; - char *target = s; - - s[len] = '\0'; // null terminate instruction - if (*(s += strcspn(s, EZXML_WS))) { - *s = '\0'; // null terminate target - s += strspn(s + 1, EZXML_WS) + 1; // skip whitespace after target - } - - if (! strcmp(target, "xml")) { // <?xml ... ?> - if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10, - EZXML_WS "='\"") + 10, "yes", 3)) root->standalone = 1; - return; - } - - if (! root->pi[0]) *(root->pi = malloc(sizeof(char **))) = NULL; //first pi - - while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target - if (! root->pi[i]) { // new target - root->pi = realloc(root->pi, sizeof(char **) * (i + 2)); - root->pi[i] = malloc(sizeof(char *) * 3); - root->pi[i][0] = target; - root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list - root->pi[i][2] = strdup(""); // empty document position list - } - - while (root->pi[i][j]) j++; // find end of instruction list for this target - root->pi[i] = realloc(root->pi[i], sizeof(char *) * (j + 3)); - root->pi[i][j + 2] = realloc(root->pi[i][j + 1], j + 1); - strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<"); - root->pi[i][j + 1] = NULL; // null terminate pi list for this target - root->pi[i][j] = s; // set instruction -} - -// called when the parser finds an internal doctype subset -static short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len) -{ - char q, *c, *t, *n = NULL, *v, **ent, **pe; - int i, j; - - pe = memcpy(malloc(sizeof(EZXML_NIL)), EZXML_NIL, sizeof(EZXML_NIL)); - - for (s[len] = '\0'; s; ) { - while (*s && *s != '<' && *s != '%') s++; // find next declaration - - if (! *s) break; - else if (! strncmp(s, "<!ENTITY", 8)) { // parse entity definitions - c = s += strspn(s + 8, EZXML_WS) + 8; // skip white space separator - n = s + strspn(s, EZXML_WS "%"); // find name - *(s = n + strcspn(n, EZXML_WS)) = ';'; // append ; to name - - v = s + strspn(s + 1, EZXML_WS) + 1; // find value - if ((q = *(v++)) != '"' && q != '\'') { // skip externals - s = strchr(s, '>'); - continue; - } - - for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++); - ent = realloc(ent, (i + 3) * sizeof(char *)); // space for next ent - if (*c == '%') pe = ent; - else root->ent = ent; - - *(++s) = '\0'; // null terminate name - if ((s = strchr(v, q))) *(s++) = '\0'; // null terminate value - ent[i + 1] = ezxml_decode(v, pe, '%'); // set value - ent[i + 2] = NULL; // null terminate entity list - if (! ezxml_ent_ok(n, ent[i + 1], ent)) { // circular reference - if (ent[i + 1] != v) free(ent[i + 1]); - ezxml_err(root, v, "circular entity declaration &%s", n); - break; - } - else ent[i] = n; // set entity name - } - else if (! strncmp(s, "<!ATTLIST", 9)) { // parse default attributes - t = s + strspn(s + 9, EZXML_WS) + 9; // skip whitespace separator - if (! *t) { ezxml_err(root, t, "unclosed <!ATTLIST"); break; } - if (*(s = t + strcspn(t, EZXML_WS ">")) == '>') continue; - else *s = '\0'; // null terminate tag name - for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++); - - ++s; // ansi cpr - while (*(n = s + strspn(s, EZXML_WS)) && *n != '>') { - if (*(s = n + strcspn(n, EZXML_WS))) *s = '\0'; // attr name - else { ezxml_err(root, t, "malformed <!ATTLIST"); break; } - - s += strspn(s + 1, EZXML_WS) + 1; // find next token - c = (strncmp(s, "CDATA", 5)) ? "*" : " "; // is it cdata? - if (! strncmp(s, "NOTATION", 8)) - s += strspn(s + 8, EZXML_WS) + 8; - s = (*s == '(') ? strchr(s, ')') : s + strcspn(s, EZXML_WS); - if (! s) { ezxml_err(root, t, "malformed <!ATTLIST"); break; } - - s += strspn(s, EZXML_WS ")"); // skip white space separator - if (! strncmp(s, "#FIXED", 6)) - s += strspn(s + 6, EZXML_WS) + 6; - if (*s == '#') { // no default value - s += strcspn(s, EZXML_WS ">") - 1; - if (*c == ' ') continue; // cdata is default, nothing to do - v = NULL; - } - else if ((*s == '"' || *s == '\'') && // default value - (s = strchr(v = s + 1, *s))) *s = '\0'; - else { ezxml_err(root, t, "malformed <!ATTLIST"); break; } - - if (! root->attr[i]) { // new tag name - root->attr = (! i) ? malloc(2 * sizeof(char **)) - : realloc(root->attr, - (i + 2) * sizeof(char **)); - root->attr[i] = malloc(2 * sizeof(char *)); - root->attr[i][0] = t; // set tag name - root->attr[i][1] = (char *)(root->attr[i + 1] = NULL); - } - - for (j = 1; root->attr[i][j]; j += 3); // find end of list - root->attr[i] = realloc(root->attr[i], - (j + 4) * sizeof(char *)); - - root->attr[i][j + 3] = NULL; // null terminate list - root->attr[i][j + 2] = c; // is it cdata? - root->attr[i][j + 1] = (v) ? ezxml_decode(v, root->ent, *c) - : NULL; - root->attr[i][j] = n; // attribute name - ++s; - } - } - else if (! strncmp(s, "<!--", 4)) s = strstr(s + 4, "-->"); // comments - else if (! strncmp(s, "<?", 2)) { // processing instructions - if ((s = strstr(c = s + 2, "?>"))) - ezxml_proc_inst(root, c, s++ - c); - } - else if (*s == '<') s = strchr(s, '>'); // skip other declarations - else if (*(s++) == '%' && ! root->standalone) break; - } - - free(pe); - return ! *root->err; -} - -// Converts a UTF-16 string to UTF-8. Returns a new string that must be freed -// or NULL if no conversion was needed. -static char *ezxml_str2utf8(char **s, size_t *len) -{ - char *u; - size_t l = 0, sl, max = *len; - long c, d; - int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1; - - if (be == -1) return NULL; // not UTF-16 - - u = malloc(max); - for (sl = 2; sl < *len - 1; sl += 2) { - c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) //UTF-16BE - : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE - if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1) { // high-half - d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) - : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); - c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000; - } - - while (l + 6 > max) u = realloc(u, max += EZXML_BUFSIZE); - if (c < 0x80) u[l++] = c; // US-ASCII subset - else { // multi-byte UTF-8 sequence - for (b = 0, d = c; d; d /= 2) b++; // bits in c - b = (b - 2) / 5; // bytes in payload - u[l++] = (0xFF << (7 - b)) | (c >> (6 * b)); // head - while (b) u[l++] = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload - } - } - return *s = realloc(u, *len = l); -} - -// frees a tag attribute list -static void ezxml_free_attr(char **attr) { - int i = 0; - char *m; - - if (! attr || attr == EZXML_NIL) return; // nothing to free - while (attr[i]) i += 2; // find end of attribute list - m = attr[i + 1]; // list of which names and values are malloced - for (i = 0; m[i]; i++) { - if (m[i] & EZXML_NAMEM) free(attr[i * 2]); - if (m[i] & EZXML_TXTM) free(attr[(i * 2) + 1]); - } - free(m); - free(attr); -} - -// parse the given xml string and return an ezxml structure -ezxml_t ezxml_parse_str(char *s, size_t len) -{ - ezxml_root_t root = (ezxml_root_t)ezxml_new(NULL); - char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning - int l, i, j; - - root->m = s; - if (! len) return ezxml_err(root, NULL, "root tag missing"); - root->u = ezxml_str2utf8(&s, &len); // convert utf-16 to utf-8 - root->e = (root->s = s) + len; // record start and end of work area - - e = s[len - 1]; // save end char - s[len - 1] = '\0'; // turn end char into null terminator - - while (*s && *s != '<') s++; // find first tag - if (! *s) return ezxml_err(root, s, "root tag missing"); - - for (; ; ) { - attr = (char **)EZXML_NIL; - d = ++s; - - if (isalpha(*s) || *s == '_' || *s == ':' || *s < '\0') { // new tag - if (! root->cur) - return ezxml_err(root, d, "markup outside of root element"); - - s += strcspn(s, EZXML_WS "/>"); - while (isspace(*s)) *(s++) = '\0'; // null terminate tag name - - if (*s && *s != '/' && *s != '>') // find tag in default attr list - for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++); - - for (l = 0; *s && *s != '/' && *s != '>'; l += 2) { // new attrib - attr = (l) ? realloc(attr, (l + 4) * sizeof(char *)) - : malloc(4 * sizeof(char *)); // allocate space - attr[l + 3] = (l) ? realloc(attr[l + 1], (l / 2) + 2) - : malloc(2); // mem for list of maloced vals - strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced - attr[l + 2] = NULL; // null terminate list - attr[l + 1] = ""; // temporary attribute value - attr[l] = s; // set attribute name - - s += strcspn(s, EZXML_WS "=/>"); - if (*s == '=' || isspace(*s)) { - *(s++) = '\0'; // null terminate tag attribute name - q = *(s += strspn(s, EZXML_WS "=")); - if (q == '"' || q == '\'') { // attribute value - attr[l + 1] = ++s; - while (*s && *s != q) s++; - if (*s) *(s++) = '\0'; // null terminate attribute val - else { - ezxml_free_attr(attr); - return ezxml_err(root, d, "missing %c", q); - } - - for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j +=3); - attr[l + 1] = ezxml_decode(attr[l + 1], root->ent, (a - && a[j]) ? *a[j + 2] : ' '); - if (attr[l + 1] < d || attr[l + 1] > s) - attr[l + 3][l / 2] = EZXML_TXTM; // value malloced - } - } - while (isspace(*s)) s++; - } - - if (*s == '/') { // self closing tag - *(s++) = '\0'; - if ((*s && *s != '>') || (! *s && e != '>')) { - if (l) ezxml_free_attr(attr); - return ezxml_err(root, d, "missing >"); - } - ezxml_open_tag(root, d, attr); - ezxml_close_tag(root, d, s); - } - else if ((q = *s) == '>' || (! *s && e == '>')) { // open tag - *s = '\0'; // temporarily null terminate tag name - ezxml_open_tag(root, d, attr); - *s = q; - } - else { - if (l) ezxml_free_attr(attr); - return ezxml_err(root, d, "missing >"); - } - } - else if (*s == '/') { // close tag - s += strcspn(d = s + 1, EZXML_WS ">") + 1; - if (! (q = *s) && e != '>') return ezxml_err(root, d, "missing >"); - *s = '\0'; // temporarily null terminate tag name - if (ezxml_close_tag(root, d, s)) return &root->xml; - if (isspace(*s = q)) s += strspn(s, EZXML_WS); - } - else if (! strncmp(s, "!--", 3)) { // xml comment - if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) || - (! *s && e != '>')) return ezxml_err(root, d, "unclosed <!--"); - } - else if (! strncmp(s, "![CDATA[", 8)) { // cdata - if ((s = strstr(s, "]]>"))) - ezxml_char_content(root, d + 8, (s += 2) - d - 10, 'c'); - else return ezxml_err(root, d, "unclosed <![CDATA["); - } - else if (! strncmp(s, "!DOCTYPE", 8)) { // dtd - for (l = 0; *s && ((! l && *s != '>') || (l && (*s != ']' || - *(s + strspn(s + 1, EZXML_WS) + 1) != '>'))); - l = (*s == '[') ? 1 : l) s += strcspn(s + 1, "[]>") + 1; - if (! *s && e != '>') - return ezxml_err(root, d, "unclosed <!DOCTYPE"); - d = (l) ? strchr(d, '[') + 1 : d; - if (l && ! ezxml_internal_dtd(root, d, s++ - d)) return &root->xml; - } - else if (*s == '?') { // <?...?> processing instructions - do { s = strchr(s, '?'); } while (s && *(++s) && *s != '>'); - if (! s || (! *s && e != '>')) - return ezxml_err(root, d, "unclosed <?"); - else ezxml_proc_inst(root, d + 1, s - d - 2); - } - else return ezxml_err(root, d, "unexpected <"); - - if (! s || ! *s) break; - *s = '\0'; - d = ++s; - if (*s && *s != '<') { // tag character content - while (*s && *s != '<') s++; - if (*s) ezxml_char_content(root, d, s - d, '&'); - else break; - } - else if (! *s) break; - } - - if (! root->cur) return &root->xml; - else if (! root->cur->name) return ezxml_err(root, d, "root tag missing"); - else return ezxml_err(root, d, "unclosed tag <%s>", root->cur->name); -} - -// free the memory allocated for the ezxml structure -void ezxml_free(ezxml_t xml) -{ - ezxml_root_t root = (ezxml_root_t)xml; - int i, j; - char **a, *s; - - if (! xml) return; - ezxml_free(xml->child); - ezxml_free(xml->ordered); - - if (! xml->parent) { // free root tag allocations - for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"') - if ((s = root->ent[i + 1]) < root->s || s > root->e) free(s); - free(root->ent); // free list of general entities - - for (i = 0; (a = root->attr[i]); i++) { - for (j = 1; a[j++]; j += 2) // free malloced attribute values - if (a[j] && (a[j] < root->s || a[j] > root->e)) free(a[j]); - free(a); - } - if (root->attr[0]) free(root->attr); // free default attribute list - - for (i = 0; root->pi[i]; i++) { - for (j = 1; root->pi[i][j]; j++); - free(root->pi[i][j + 1]); - free(root->pi[i]); - } - if (root->pi[0]) free(root->pi); // free processing instructions - - if (root->len == -1) free(root->m); // malloced xml data - if (root->u) free(root->u); // utf8 conversion - } - - ezxml_free_attr(xml->attr); // tag attributes - if ((xml->flags & EZXML_TXTM)) free(xml->txt); // character content - if ((xml->flags & EZXML_NAMEM)) free(xml->name); // tag name - free(xml); -} - -// return parser error message or empty string if none -const char *ezxml_error(ezxml_t xml) -{ - while (xml && xml->parent) xml = xml->parent; // find root tag - return (xml) ? ((ezxml_root_t)xml)->err : ""; -} - diff --git a/src/libezxml/ezxml.h b/src/libezxml/ezxml.h deleted file mode 100644 index 0ee2513..0000000 --- a/src/libezxml/ezxml.h +++ /dev/null @@ -1,93 +0,0 @@ -/* ezxml.h - * - * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org> - * - * 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 _EZXML_H -#define _EZXML_H - -#include <stdlib.h> -#include <stdio.h> -#include <stdarg.h> -#include <fcntl.h> - -#define EZXML_BUFSIZE 1024 // size of internal memory buffers -#define EZXML_NAMEM 0x80 // name is malloced -#define EZXML_TXTM 0x40 // txt is malloced -#define EZXML_DUP 0x20 // attribute name and value are strduped - -typedef struct ezxml *ezxml_t; -struct ezxml { - char *name; // tag name - char **attr; // tag attributes { name, value, name, value, ... NULL } - char *txt; // tag character content, empty string if none - size_t off; // tag offset from start of parent tag character content - ezxml_t next; // next tag with same name in this section at this depth - ezxml_t sibling; // next tag with different name in same section and depth - ezxml_t ordered; // next tag, same section and depth, in original order - ezxml_t child; // head of sub tag list, NULL if none - ezxml_t parent; // parent tag, NULL if current tag is root tag - short flags; // additional information -}; - -// Given a string of xml data and its length, parses it and creates an ezxml -// structure. For efficiency, modifies the data by adding null terminators -// and decoding ampersand sequences. If you don't want this, copy the data and -// pass in the copy. Returns NULL on failure. -ezxml_t ezxml_parse_str(char *s, size_t len); - -// returns the first child tag (one level deeper) with the given name or NULL -// if not found -ezxml_t ezxml_child(ezxml_t xml, const char *name); - -// returns the next tag of the same name in the same section and depth or NULL -// if not found -#define ezxml_next(xml) ((xml) ? xml->next : NULL) - -// Returns the Nth tag with the same name in the same section at the same depth -// or NULL if not found. An index of 0 returns the tag given. -ezxml_t ezxml_idx(ezxml_t xml, int idx); - -// returns the name of the given tag -#define ezxml_name(xml) ((xml) ? xml->name : NULL) - -// returns the given tag's character content or empty string if none -#define ezxml_txt(xml) ((xml) ? xml->txt : "") - -// returns the value of the requested tag attribute, or NULL if not found -const char *ezxml_attr(ezxml_t xml, const char *attr); - -// Traverses the ezxml sturcture to retrieve a specific subtag. Takes a -// variable length list of tag names and indexes. The argument list must be -// terminated by either an index of -1 or an empty string tag name. Example: -// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1); -// This retrieves the title of the 3rd book on the 1st shelf of library. -// Returns NULL if not found. -ezxml_t ezxml_get(ezxml_t xml, ...); - -// frees the memory allocated for an ezxml structure -void ezxml_free(ezxml_t xml); - -// returns parser error message or empty string if none -const char *ezxml_error(ezxml_t xml); - -#endif // _EZXML_H 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 */ diff --git a/src/settings.c b/src/settings.c index 51c2400..d1dcc7f 100644 --- a/src/settings.c +++ b/src/settings.c @@ -133,8 +133,8 @@ void BarSettingsRead (BarSettings_t *settings) { settings->listSongFormat = strdup ("%i) %a - %t%r"); settings->fifo = malloc (PATH_MAX * sizeof (*settings->fifo)); BarGetXdgConfigDir (PACKAGE "/ctl", settings->fifo, PATH_MAX); - memcpy (settings->tlsFingerprint, "\xD9\x98\x0B\xA2\xCC\x0F\x97\xBB" - "\x03\x82\x2C\x62\x11\xEA\xEA\x4A\x06\xEE\xF4\x27", + memcpy (settings->tlsFingerprint, "\xA2\xA0\xBE\x8A\x37\x92\x39\xAE" + "\x2B\x2E\x71\x4C\x56\xB3\x8B\xC1\x2A\x9B\x4B\x77", sizeof (settings->tlsFingerprint)); settings->msgFormat[MSG_NONE].prefix = NULL; @@ -138,7 +138,7 @@ void BarUiMsg (const BarSettings_t *settings, const BarUiMsg_t type, */ static WaitressReturn_t BarPianoHttpRequest (WaitressHandle_t *waith, PianoRequest_t *req, bool forceTls) { - waith->extraHeaders = "Content-Type: text/xml\r\n"; + waith->extraHeaders = "Content-Type: text/plain\r\n"; waith->postData = req->postData; waith->method = WAITRESS_METHOD_POST; waith->url.path = req->urlPath; @@ -186,7 +186,7 @@ int BarUiPianoCall (BarApp_t * const app, PianoRequestType_t type, *pRet = PianoResponse (&app->ph, &req); if (*pRet != PIANO_RET_CONTINUE_REQUEST) { /* checking for request type avoids infinite loops */ - if (*pRet == PIANO_RET_AUTH_TOKEN_INVALID && + if (*pRet == PIANO_RET_P_INVALID_AUTH_TOKEN && type != PIANO_REQUEST_LOGIN) { /* reauthenticate */ PianoReturn_t authpRet; diff --git a/src/ui_act.c b/src/ui_act.c index 76b9411..93ba623 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -261,8 +261,7 @@ BarUiActCallback(BarUiActDebug) { "rating:\t%i\n" "stationId:\t%s\n" "title:\t%s\n" - "trackToken:\t%s\n" - "userSeed:\t%s\n", + "trackToken:\t%s\n", selSong->album, selSong->artist, selSong->audioFormat, @@ -274,8 +273,7 @@ BarUiActCallback(BarUiActDebug) { selSong->rating, selSong->stationId, selSong->title, - selSong->trackToken, - selSong->userSeed); + selSong->trackToken); } /* rate current song |