summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars-Dominik Braun <PromyLOPh@lavabit.com>2010-11-23 21:59:31 +0100
committerLars-Dominik Braun <lars@6xq.net>2010-12-26 15:47:42 +0100
commit13106ac8bb95e325cf522817f91ad1f3b0fcecb0 (patch)
tree5488d884b5fac8bf5f1946cae92e536fa2d0d345 /src
parentd074480b9159a53fc4e6bdb40463289c69c2f6a7 (diff)
downloadpianobar-windows-13106ac8bb95e325cf522817f91ad1f3b0fcecb0.tar.gz
pianobar-windows-13106ac8bb95e325cf522817f91ad1f3b0fcecb0.tar.bz2
pianobar-windows-13106ac8bb95e325cf522817f91ad1f3b0fcecb0.zip
Better directory layout
Removed useless AUTHORS, COPYING and README files. Move manpage to contrib (it's not exactly source code).
Diffstat (limited to 'src')
-rw-r--r--src/libezxml/COPYING20
-rw-r--r--src/libezxml/ezxml.c715
-rw-r--r--src/libezxml/ezxml.h93
-rw-r--r--src/libpiano/config.h1
-rw-r--r--src/libpiano/crypt.c197
-rw-r--r--src/libpiano/crypt.h30
-rw-r--r--src/libpiano/crypt_key_input.h305
-rw-r--r--src/libpiano/crypt_key_output.h303
-rw-r--r--src/libpiano/piano.c1114
-rw-r--r--src/libpiano/piano.h244
-rw-r--r--src/libpiano/piano_private.h31
-rw-r--r--src/libpiano/xml.c826
-rw-r--r--src/libpiano/xml.h48
-rw-r--r--src/libwaitress/config.h1
-rw-r--r--src/libwaitress/waitress.c573
-rw-r--r--src/libwaitress/waitress.h78
-rw-r--r--src/pianobar.1227
17 files changed, 4579 insertions, 227 deletions
diff --git a/src/libezxml/COPYING b/src/libezxml/COPYING
new file mode 100644
index 0000000..80e4e88
--- /dev/null
+++ b/src/libezxml/COPYING
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 0000000..967d535
--- /dev/null
+++ b/src/libezxml/ezxml.c
@@ -0,0 +1,715 @@
+/* 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.
+ */
+
+#define _BSD_SOURCE /* required by strdup() */
+
+#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, const 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(const char *name)
+{
+ static char *ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
+ "apos;", "&#39;", "amp;", "&#38;", 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
new file mode 100644
index 0000000..0ee2513
--- /dev/null
+++ b/src/libezxml/ezxml.h
@@ -0,0 +1,93 @@
+/* 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/config.h b/src/libpiano/config.h
new file mode 100644
index 0000000..febad25
--- /dev/null
+++ b/src/libpiano/config.h
@@ -0,0 +1 @@
+#define PACKAGE "libpiano"
diff --git a/src/libpiano/crypt.c b/src/libpiano/crypt.c
new file mode 100644
index 0000000..8a8ad01
--- /dev/null
+++ b/src/libpiano/crypt.c
@@ -0,0 +1,197 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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.
+*/
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <arpa/inet.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
+ * @param hex string
+ * @return decrypted string or NULL
+ */
+#define INITIAL_SHIFT 28
+#define SHIFT_DEC 4
+unsigned char *PianoDecryptString (const unsigned char *strInput) {
+ /* hex-decode => strlen/2 + null-byte */
+ uint32_t *iDecrypt;
+ unsigned char *strDecrypted;
+ unsigned char shift = INITIAL_SHIFT, intsDecoded = 0, j;
+ /* blowfish blocks, 32-bit */
+ uint32_t f, l, r, lrExchange;
+
+ if ((iDecrypt = calloc (strlen ((char *) strInput)/2/sizeof (*iDecrypt)+1,
+ sizeof (*iDecrypt))) == NULL) {
+ return NULL;
+ }
+ strDecrypted = (unsigned 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;
+ }
+
+ return strDecrypted;
+}
+#undef INITIAL_SHIFT
+#undef SHIFT_DEC
+
+/* blowfish-encrypt/hex-encode string
+ * @param encrypt this
+ * @return encrypted, hex-encoded string
+ */
+unsigned char *PianoEncryptString (const unsigned char *strInput) {
+ const size_t strInputN = strlen ((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) {
+ 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;
+ }
+
+ free (blockInput);
+
+ return strHex;
+}
diff --git a/src/libpiano/crypt.h b/src/libpiano/crypt.h
new file mode 100644
index 0000000..e20847c
--- /dev/null
+++ b/src/libpiano/crypt.h
@@ -0,0 +1,30 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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 _CRYPH_H
+#define _CRYPT_H
+
+char *PianoDecryptString (const char *strInput);
+char *PianoEncryptString (const char *strInput);
+
+#endif /* _CRYPT_H */
diff --git a/src/libpiano/crypt_key_input.h b/src/libpiano/crypt_key_input.h
new file mode 100644
index 0000000..a02e668
--- /dev/null
+++ b/src/libpiano/crypt_key_input.h
@@ -0,0 +1,305 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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 _CRYPT_KEY_INPUT_H
+#define _CRYPT_KEY_INPUT_H
+
+#include <stdint.h>
+
+/* decryption key for last 48 bytes of audio urls
+ * search for rpc.input in the decompiled pandora actionscript
+ */
+
+const unsigned int in_key_n = 16;
+
+static const uint32_t in_key_p [16 + 2] = {
+ 0xCB42446BL, 0x84C9ADD7L, 0x58BB40B6L, 0x67309C28L,
+ 0x847AE93FL, 0x634B08FAL, 0x7B41B0E7L, 0x6CA77EA8L,
+ 0x680A3F07L, 0x32C26224L, 0x15F68F8FL, 0x84983ECEL,
+ 0xE88938C0L, 0xFB2F1633L, 0x172C14BDL, 0xEDD40F8CL,
+ 0xFF10C4FBL, 0x22B7AB11L,
+ };
+
+static const uint32_t in_key_s [4][256] = {{
+ 0x44B440E2L, 0xE99DBE06L, 0xB9F1A081L, 0x0933FCA1L,
+ 0x6B412E99L, 0xB30F4B41L, 0xE32063E6L, 0xF9C0101EL,
+ 0x5265F727L, 0x678F8106L, 0xACF39C5CL, 0x28F2868FL,
+ 0x5475BE2FL, 0x404DE0BCL, 0x23468728L, 0x184A1488L,
+ 0xEA971385L, 0x2F886887L, 0x5E95611CL, 0x239508CCL,
+ 0x88355159L, 0x5E7E789FL, 0x9A805DAFL, 0xEC5AB7E0L,
+ 0x4A3E4C9FL, 0xA2BE130CL, 0xEE7E0076L, 0x05B9A2F8L,
+ 0x6D7AD1C3L, 0x3CAA426EL, 0x2879FC42L, 0x29BB3A22L,
+ 0x62AE8B5DL, 0xC561999BL, 0x98D278B1L, 0x0373DDA7L,
+ 0x3CED65D5L, 0x4540C197L, 0x8AE57D79L, 0x24F37705L,
+ 0x54E2BF0CL, 0xCAF2D2C8L, 0xAB85D018L, 0xBBD96449L,
+ 0xB36A13B9L, 0x3C44EB0AL, 0xD820C26BL, 0xF90B784DL,
+ 0xAB672603L, 0xD532CF15L, 0x7FFB6686L, 0x7BE27118L,
+ 0x793DDA02L, 0xE73107DDL, 0x2632F764L, 0x6FBE7A13L,
+ 0x65982C19L, 0xAFF729A3L, 0x69B5BBF7L, 0x2864B0FAL,
+ 0x8AED80D3L, 0x0AEB3597L, 0x13689958L, 0x80B40D77L,
+ 0x3D9641A8L, 0xF24E7E40L, 0xB9E7A890L, 0xDC02983DL,
+ 0xD9050DA9L, 0xFB874500L, 0xB2C64291L, 0xDC12F8F5L,
+ 0x128A5CD2L, 0x527F82D3L, 0xCD7C1081L, 0xC5EBDB5FL,
+ 0xF968BDD0L, 0xF3E427A3L, 0xBCF76022L, 0xA94CA0BBL,
+ 0x28884495L, 0x8214C39CL, 0x6851B166L, 0xEBA17650L,
+ 0x95729279L, 0xCE1666C5L, 0x6A57605DL, 0x1BD16C4BL,
+ 0x59EF9ECCL, 0xE7C09A6AL, 0x24673702L, 0x0E7CE01EL,
+ 0x3636E13AL, 0x40F429BAL, 0x9FBA277EL, 0x9AE49DD0L,
+ 0x300E6BABL, 0x201ADB58L, 0xC029E2D9L, 0xE5C74ED7L,
+ 0x5C143553L, 0x8E747F2CL, 0x159043C5L, 0x379614D4L,
+ 0x9075D06FL, 0x585C1BC2L, 0xE9D22CC1L, 0x0914AEC0L,
+ 0xB3B46C73L, 0xC96D2F00L, 0xB14BB0D9L, 0x46A5C5B6L,
+ 0x0E3516F4L, 0xEB9AB01EL, 0x5C04E434L, 0x284AAFB4L,
+ 0x1BCAF462L, 0xF1A5542AL, 0xBBD03E4AL, 0x4BDC3DA7L,
+ 0x6BD0FCC9L, 0x40D21E00L, 0x32E2AEF1L, 0xEF62B668L,
+ 0x344EA1BAL, 0x46B1BD02L, 0xCAFE4DC6L, 0x15BE27F0L,
+ 0xBFF5A998L, 0x3B0D9B30L, 0x27CA9A40L, 0x84C597FFL,
+ 0xD6859FE3L, 0x5D045192L, 0x2A002659L, 0xCC8B3052L,
+ 0xFAEFE7C2L, 0xEB9F883EL, 0x69772177L, 0x5FA05797L,
+ 0x69DF2134L, 0xDACC373BL, 0x3C035B9EL, 0x977A58DCL,
+ 0x84D49E26L, 0x912CFF20L, 0xDFA2D5A6L, 0x0CA8AA62L,
+ 0x53CC93D9L, 0x3DD8536AL, 0xF40CF797L, 0xC6EF5FA5L,
+ 0xDDBF6EA2L, 0xFF79AD8BL, 0x9CC8F9B6L, 0x9070482EL,
+ 0xB6779612L, 0x0A7C86DFL, 0x968E17A3L, 0x28CA9EF1L,
+ 0x86DB025BL, 0xF2A80541L, 0x2631C384L, 0x5750F7E3L,
+ 0x78191CBFL, 0x4025BF50L, 0x1E032A2BL, 0xC3B4DE8DL,
+ 0xAC98A492L, 0xCD90D98FL, 0x92B02F24L, 0xFC462ECFL,
+ 0x435695A9L, 0xEDECDDA4L, 0x297B6304L, 0xD973A25AL,
+ 0x65A7991DL, 0x8B727303L, 0xC8F89BD7L, 0xCF18B1D7L,
+ 0x60A234FFL, 0x13E68F78L, 0xF919D31AL, 0x5249D0A5L,
+ 0x117B11AAL, 0xEF6D0783L, 0x2128D583L, 0xA7A24FEAL,
+ 0xD9C379ECL, 0x86A4C5ADL, 0xF3CDAA1CL, 0x04A8A51FL,
+ 0xE19C74E7L, 0x578622A0L, 0xFD70277DL, 0xEDD3226AL,
+ 0x503434D8L, 0x31A8EBB5L, 0x9122632EL, 0x7EF237DAL,
+ 0xA59DEF47L, 0xDD87BA93L, 0x7965AB65L, 0x26790F7CL,
+ 0xDFDF407BL, 0xAF80CDF0L, 0xA9E55C4FL, 0x54B77138L,
+ 0x39B26324L, 0xBE3FD4CDL, 0xDEB06C51L, 0xEEB11B71L,
+ 0x90B974D4L, 0xE125702EL, 0x9F6E26EAL, 0x6E4574E1L,
+ 0x0ABC33CAL, 0x3F51A4B0L, 0x225E548AL, 0x7D275103L,
+ 0x3849134BL, 0xE32DC3B0L, 0x08297936L, 0x788C457BL,
+ 0x17F9219EL, 0xE7E6FE7DL, 0xD79DB0B0L, 0xD3F8FEF0L,
+ 0x8B0D4DBFL, 0x210883B0L, 0xEACAC580L, 0xAEC1E03EL,
+ 0xF2A7678AL, 0x76457B5BL, 0xB518F841L, 0x31E3B067L,
+ 0x4E972C3BL, 0x19E5A9BCL, 0xB7438C21L, 0x4C5B5F28L,
+ 0xFBB2E5BDL, 0x7734BB35L, 0x705A9013L, 0xD132FAC5L,
+ 0x99F8BC98L, 0x8E6B0A9DL, 0xB287744FL, 0x4CBC0613L,
+ 0xF094CEB8L, 0xF93137B0L, 0x9242EE26L, 0xC25D9AEBL,
+ 0xD517C2F2L, 0xEC2F7F38L, 0xE18380AFL, 0x22C2678AL,
+ }, {
+ 0xDE0A562AL, 0x596C7989L, 0x2E99CBDEL, 0x5CC946AFL,
+ 0x2DA232BFL, 0x64145592L, 0x7885E518L, 0x85B53628L,
+ 0xFB83003EL, 0x5F643977L, 0x193D31BFL, 0x0FDB8849L,
+ 0x2BA77CF5L, 0x86D0A1A5L, 0x48803977L, 0xB670B7DDL,
+ 0x2924E754L, 0x333CAA9FL, 0x994FF797L, 0x9858B8BEL,
+ 0x22BFD9ADL, 0x1A51D7DCL, 0x372CD282L, 0xB38A5CA4L,
+ 0xB0B6858DL, 0x4F5CAD72L, 0x3C0DC2D6L, 0xEB090786L,
+ 0xC45E17FCL, 0xF0C37D9CL, 0x8315CA42L, 0x336E14D8L,
+ 0xE4DA980CL, 0x14EEBD73L, 0xA82E556CL, 0x4ABA084AL,
+ 0x9EEC5155L, 0x0CA47152L, 0x03BE2A25L, 0x874F8618L,
+ 0xC3C31219L, 0x8554B382L, 0x26FFF693L, 0xD9FE05D6L,
+ 0xC69BB513L, 0xC547ED34L, 0x44F66E01L, 0x58B32FD0L,
+ 0xF9F7FA8AL, 0xFEF4A493L, 0x2999E20BL, 0x6D23743FL,
+ 0x97E2A9DCL, 0x1916908AL, 0x905CBF0DL, 0xE0290BDBL,
+ 0xC06B65EFL, 0xE5A45154L, 0x45ABB6ECL, 0xDD329C85L,
+ 0xEDF93835L, 0xE5D485D5L, 0x248B2CD5L, 0x60CC627BL,
+ 0x2B3224F1L, 0xD7DFA43DL, 0xD5D6DA9AL, 0x7A3F9A29L,
+ 0x26605F98L, 0x435FB927L, 0xED9A7CD7L, 0x6020C2EFL,
+ 0x43AC15D0L, 0x05FE8353L, 0x74AF1FB8L, 0xADFDC949L,
+ 0x3BA09A14L, 0x2BC91C0DL, 0x7D681AD5L, 0xA08CA21CL,
+ 0x208C85ADL, 0x0F3DE514L, 0xB4C87BE1L, 0x455233DCL,
+ 0xFF35FDF1L, 0xCE93E7E9L, 0x409BA175L, 0x817C4198L,
+ 0x749B8E5AL, 0xBA599286L, 0x44E40068L, 0x7FA013C6L,
+ 0x9D62E731L, 0x453F48E4L, 0x375CADBEL, 0xC3D661D5L,
+ 0xA96A5BF9L, 0x9D2B321AL, 0x0A144AFBL, 0x01590B67L,
+ 0x9887312FL, 0x7592FB46L, 0x99AC6DD8L, 0xAA347510L,
+ 0x0F3DC8A9L, 0x9BE58B17L, 0x49C674FCL, 0x3D1F5303L,
+ 0x138E2052L, 0x1B7CA3EBL, 0xB3C8BE68L, 0xB5C6CB1DL,
+ 0xBA62EA1CL, 0x269241DBL, 0xA0EE1AB3L, 0x8EB3E79CL,
+ 0x94EA0E9FL, 0xB86FFB54L, 0x56DCBF7FL, 0x3404FD03L,
+ 0x8076B757L, 0xB05A9529L, 0x8CC7B224L, 0x4F29804CL,
+ 0x6CD2C621L, 0x99424188L, 0xF89E7F5DL, 0xFDF62C52L,
+ 0x94D264B1L, 0x9AE26508L, 0xE7C1DF25L, 0xFCE03375L,
+ 0x4111F745L, 0x46978B8AL, 0x6D3DEBE0L, 0xA8C9A4A1L,
+ 0xBF9ADEE2L, 0x651A9A66L, 0x37EBEFEDL, 0x6BAC6274L,
+ 0xED007BFAL, 0x8B00DB5DL, 0x8EB88E4FL, 0x7247626BL,
+ 0xC70E0D71L, 0xB06D9EC3L, 0x777CBB67L, 0x9812CC56L,
+ 0x12ACE7D0L, 0xEE395981L, 0xFE39A6EFL, 0xC1F592CDL,
+ 0xB8C0866DL, 0xC0D8526EL, 0xC6AA7A35L, 0x91113015L,
+ 0x36CC0A0DL, 0xD1E8DBA0L, 0x074756C8L, 0xFC272600L,
+ 0x6C33093EL, 0x44D5C245L, 0xCA27B4C7L, 0x5A794CFAL,
+ 0x459D3DD0L, 0x0F7D4917L, 0x139A3245L, 0x9B2E5E3CL,
+ 0x31018582L, 0x759B9D72L, 0x8BDD1D9BL, 0xA16CB7FCL,
+ 0x3BBE42B1L, 0x87E40683L, 0x9BE04779L, 0xE9DDE72BL,
+ 0x0BE9A89AL, 0x1CB85941L, 0x96134FD9L, 0xFB347B02L,
+ 0xD908504AL, 0x71F33096L, 0x705269F0L, 0x3EF468FDL,
+ 0xC97983DAL, 0x71277190L, 0x8C2B030FL, 0x8CAE4554L,
+ 0x2E643F45L, 0xE16A9CE9L, 0x2C06928EL, 0xC7E0F46AL,
+ 0xBDDCEB74L, 0x74985EDBL, 0xBC223E1DL, 0x7F8F5B2AL,
+ 0x1EBFAFBCL, 0x3B60AE99L, 0x63083D95L, 0x2905083CL,
+ 0xF926F42EL, 0xD5389D31L, 0x6CA90A33L, 0xE1158CAAL,
+ 0x352434A4L, 0xB45FD6B1L, 0x17389418L, 0x70D400F6L,
+ 0x9127D3E0L, 0xA8EB8D18L, 0x39020FBEL, 0x417AB2E5L,
+ 0xA5E92B88L, 0x5F36AEFCL, 0x4057731CL, 0xD1E966F7L,
+ 0x0DA44875L, 0x3BE7D0DBL, 0x1C7850CDL, 0xE3B1462EL,
+ 0xF9AFEFF6L, 0x210E1CCEL, 0x36FBB8AEL, 0x8584030DL,
+ 0x2DD9731AL, 0x9554A44BL, 0x2C6F8100L, 0x983A1BE5L,
+ 0x25FAF22DL, 0x338316A7L, 0x0E5A690DL, 0xE702EBEBL,
+ 0x52AC420EL, 0xEAC503EFL, 0xD2FE1B98L, 0xE1D55B98L,
+ 0xD7DD5C6EL, 0xE756FFF0L, 0xD3FD66EBL, 0x1FD30A11L,
+ 0x03E18282L, 0x856F2216L, 0x6917F24CL, 0x12975941L,
+ 0xD98387CAL, 0x2B9E153FL, 0xFD7005E0L, 0x05A501F2L,
+ 0xA731DB07L, 0xBF268F18L, 0x47E9E9C2L, 0x442148F8L,
+ 0x1CCF6FACL, 0x239820BEL, 0x56C0EC64L, 0xC6C59DF1L,
+ },{
+ 0x1D985BEEL, 0x336930D2L, 0xCA40E672L, 0x7036E21CL,
+ 0x7B43BD3CL, 0xA02DBD7DL, 0x7DFA1A92L, 0xB13508C5L,
+ 0x01633D3CL, 0x6CBD1AA5L, 0xCDC61031L, 0x15993E15L,
+ 0x669E1B6FL, 0x78B79587L, 0x61D58836L, 0x888D5578L,
+ 0x8B99F671L, 0x346906B6L, 0x5EA37C50L, 0xC32C7220L,
+ 0xAA6101DEL, 0xAF0CD647L, 0x9840E7DFL, 0xBC4EB801L,
+ 0x071126BAL, 0xCD6D71A4L, 0x0CDA5F16L, 0x7838C18CL,
+ 0x8F1641CBL, 0x7DF307E3L, 0xB83E78D1L, 0x24E91D07L,
+ 0x0F5BB3BCL, 0x172BDFFBL, 0xD24AA952L, 0x41CF15C3L,
+ 0x78A696A5L, 0x15CD0C0AL, 0xD24D916DL, 0xA2CF942CL,
+ 0xA1EC1AAAL, 0x0B48FCE0L, 0xE1779859L, 0x99F8806AL,
+ 0x34333D69L, 0xABBA62D0L, 0xF6E91D76L, 0x0B0D5FDAL,
+ 0x5A467847L, 0x74DA0A4DL, 0x0335F980L, 0xE7C55B44L,
+ 0x6DD5E917L, 0xC7305285L, 0xCF1D07C6L, 0x8D017ECBL,
+ 0xE10A16C1L, 0x0DB9438CL, 0x2FA12B58L, 0x332FA54DL,
+ 0x02BD7743L, 0x4B544614L, 0x7907B6AEL, 0x75401EBAL,
+ 0xF7382064L, 0xF1325592L, 0x06FB5C05L, 0x9F5FEF1FL,
+ 0x3A641771L, 0x807BF700L, 0xC63EDF23L, 0x9FFD35E9L,
+ 0x437A410EL, 0x225D18FBL, 0xAD901352L, 0x9D2F8585L,
+ 0xEA317135L, 0xCB724C48L, 0xCA568A14L, 0xB153340AL,
+ 0x88794F5FL, 0x0C208094L, 0x85BE94E6L, 0x86C5F48AL,
+ 0xFC4AC759L, 0xFCCEA783L, 0x491AF14BL, 0x23C9622CL,
+ 0x85660BAFL, 0x110300CBL, 0xDE2D8E21L, 0xBAD7D22BL,
+ 0x3649D2A8L, 0x64439BF3L, 0xFDD7F2ADL, 0x68129C07L,
+ 0x3D03801DL, 0xE2D3B28CL, 0xA94193D2L, 0xEE2C34F0L,
+ 0x457DCDF1L, 0x4E21BC94L, 0x8F557C82L, 0x13EE8AB5L,
+ 0x0B2A8EC9L, 0x7796592BL, 0x5D9E3346L, 0x3621C85FL,
+ 0x1EB7FF52L, 0xCCDF71D8L, 0x538DD6AEL, 0x748274A9L,
+ 0x6552081CL, 0xFA61EE48L, 0xFB2A7098L, 0xA02BA33FL,
+ 0xB145EA01L, 0xE4C5DEC9L, 0x9E2BF9B1L, 0x6413006DL,
+ 0x8F7C7501L, 0x9B51E842L, 0x5ADD5755L, 0x06CAF452L,
+ 0x80050406L, 0xCB961521L, 0xEFC54E27L, 0x420EE737L,
+ 0xADB18EAEL, 0xF8B0B2FBL, 0xC8CDC88FL, 0x3A026D91L,
+ 0x5F99BB22L, 0x391C12F8L, 0xBDF1CEE6L, 0xFEBE0726L,
+ 0x6CC79280L, 0x3F7AC7F2L, 0x3B92345CL, 0x5C7BB986L,
+ 0xC60C12D7L, 0x4F29C1FAL, 0x04276434L, 0xC1272CD2L,
+ 0x5E64BC58L, 0xD473D8F8L, 0x38E9D502L, 0x5C00EE4EL,
+ 0x80A1A552L, 0xD49CD6D2L, 0xEA96CBE7L, 0x029E2A86L,
+ 0x0D04BA72L, 0xE9B0389DL, 0x4AC913EBL, 0x85927E61L,
+ 0x134A0D51L, 0xE63AFF82L, 0x5D7C3FEEL, 0x9373DA83L,
+ 0x361324B4L, 0x18569865L, 0x7EAC6BD8L, 0x945CA42CL,
+ 0x6A1FD8B8L, 0x6C9D06F7L, 0x01D4072FL, 0xAED3BEA5L,
+ 0x9C7BADA7L, 0x740C8802L, 0xAD1D6843L, 0xB84145EBL,
+ 0x2804CAEBL, 0x31EB19FBL, 0x402E5195L, 0x05451516L,
+ 0xF9770FC4L, 0x547FA291L, 0xF9B980EAL, 0xD6DC914DL,
+ 0x8A009CDFL, 0xFDFEDDADL, 0xEBAC5EF5L, 0x575B2AD2L,
+ 0x67A8655FL, 0x54590146L, 0x21FA9A1FL, 0xB8CD9B23L,
+ 0x1D313DFFL, 0xC5D15FFFL, 0xB6734243L, 0xD1B44EFFL,
+ 0xEE6DFF7EL, 0x0A6DB2BCL, 0x2A2AE34EL, 0x596782E6L,
+ 0x1DF3E26BL, 0xCE3EA01AL, 0x3CCBBEEFL, 0xD4B8AF1FL,
+ 0x555D5325L, 0x78F81205L, 0x528EFD8CL, 0x01F3D29FL,
+ 0xAD12326CL, 0x816D4F2EL, 0x6DD2FFE9L, 0x03C4EE90L,
+ 0xE9E561B6L, 0x28237064L, 0x82143F5FL, 0xEED4AA29L,
+ 0x9E63A1C6L, 0xB25E2AEAL, 0x516B9A8AL, 0xB52B4B71L,
+ 0xDDEF7CFFL, 0x6A8FFBDDL, 0x877EC0E5L, 0x72AF36D8L,
+ 0xF9EC2EC3L, 0xBD89E9AFL, 0x0A2FC438L, 0xD0E8BEE7L,
+ 0xD8979CF1L, 0x0AF071BEL, 0x0BC600C9L, 0x7E1F6318L,
+ 0xBFF4BF36L, 0x01DF67EEL, 0x6B35580BL, 0x2A3487ABL,
+ 0x1694328EL, 0x68358F88L, 0x23C60980L, 0xBE312A5BL,
+ 0x444E80C9L, 0x7ECE3A97L, 0xC3CE446CL, 0x5269084CL,
+ 0xC611CCE5L, 0xA156FBADL, 0xA2331EC8L, 0x1065EA4BL,
+ 0xA59F0F71L, 0xE46F2C17L, 0x2E2247DCL, 0xB5EBAA6FL,
+ 0xD8DE0349L, 0x0EA6F1B3L, 0x465FB0A0L, 0x0CE5A14CL,
+ 0x6A10A909L, 0xBCB590D3L, 0x8FA66C30L, 0xD1FCB44DL,
+ },{
+ 0x475CA7E8L, 0x4C51BBC1L, 0xA90763E1L, 0x7BDF2294L,
+ 0x04D96F34L, 0x11318CB9L, 0x54A65710L, 0x041597BEL,
+ 0xC49B15C2L, 0x09B9521AL, 0x5589972BL, 0x10167358L,
+ 0xA3927ED5L, 0x405F7752L, 0x2CBCB573L, 0x37106B2EL,
+ 0x4CF216A4L, 0x5AB1D0F2L, 0x3ED8CA8EL, 0xA612D957L,
+ 0xADC5D570L, 0xD45C1E98L, 0x53C28628L, 0xBCF19A34L,
+ 0x6CA25F5CL, 0x5C5E4FA2L, 0x7C155954L, 0xE99CE5F9L,
+ 0xD9A180F9L, 0x4F3CF09BL, 0x9DDF75A6L, 0x6D240EE0L,
+ 0x67973B52L, 0xE13895B4L, 0x36ADE486L, 0x22EC32D2L,
+ 0x075D6EAEL, 0x29C31053L, 0xAC06EE74L, 0x1A9D3316L,
+ 0x8A2D0CC3L, 0x57DC77D3L, 0x40B2D07FL, 0xDFA2051AL,
+ 0xFDC90DFCL, 0x342E9AC5L, 0xB04EB091L, 0x9E1C2D1FL,
+ 0x075A20A3L, 0xE45FA195L, 0x3F958FE3L, 0x18C98F15L,
+ 0xC70AE917L, 0x78040F9BL, 0xE4322DEBL, 0x0903841FL,
+ 0xAE71257EL, 0xA82C7909L, 0xA398504BL, 0x56A9A694L,
+ 0x7DA7F73BL, 0x12942475L, 0xAAC0C982L, 0xDFD0E53EL,
+ 0x27CE728BL, 0xE94327CBL, 0xF2EBA423L, 0x72B1476EL,
+ 0x317756F6L, 0x056654E3L, 0x074C0192L, 0x11B6FF21L,
+ 0x939FD4C5L, 0xE6E45B55L, 0x66211539L, 0x753C6C46L,
+ 0x536DB219L, 0x939C3AF0L, 0x9783EE73L, 0xC8978DE8L,
+ 0xBA0329F6L, 0x3B892181L, 0x4CDA3336L, 0xFD15F1EFL,
+ 0xAE2B2E61L, 0x2637C83BL, 0x9D9930BFL, 0x72ADFC41L,
+ 0x48DBFFFFL, 0x448E80F4L, 0xBA1CC3E1L, 0x61CCB37DL,
+ 0xD3CFEC32L, 0x0E6804A9L, 0x1ECE11A1L, 0x15809783L,
+ 0x9F7FD813L, 0x64A39648L, 0x9E67CA64L, 0x19B3E4B8L,
+ 0x8740466AL, 0xEBB79667L, 0x1CE86B36L, 0xC7BCE505L,
+ 0x2445215DL, 0x54ADCC77L, 0xAB187AC2L, 0x97B82A38L,
+ 0xA66B2D42L, 0xA3D07412L, 0x4A9CB0F4L, 0x2C51F98CL,
+ 0xCA7FC25BL, 0x73AEB532L, 0xF45769BBL, 0x8068AEF4L,
+ 0x48A14C08L, 0x011E7A0BL, 0xD4FEF360L, 0xB572B6ECL,
+ 0x947692FFL, 0x8EB6BE12L, 0xDEB04AF3L, 0xA9CD494EL,
+ 0x7C522A9DL, 0x4CC24357L, 0x616E3132L, 0x68073F0DL,
+ 0x20F7237AL, 0x7EA98584L, 0x3B6BF43CL, 0x29F11571L,
+ 0x759B5F60L, 0x7D5BCACCL, 0x8C86BC43L, 0xB378EF1BL,
+ 0xD00EAF88L, 0xBD85FE1AL, 0xB4A42C5DL, 0x2075442DL,
+ 0x184038CCL, 0xC3D9F6F8L, 0x1D72BAC3L, 0xDD8C41C0L,
+ 0xD85F7634L, 0xB48A6902L, 0x8BB3F160L, 0x178CCA70L,
+ 0x4EE8D16EL, 0x5121D6FDL, 0xF2F09DA9L, 0x84B55B38L,
+ 0x8D081959L, 0x13F21DADL, 0x19BCDA5CL, 0x909A9FDFL,
+ 0x11AD6731L, 0x2CE9D09FL, 0xAA8E9543L, 0x9F8D5555L,
+ 0x679FE7C1L, 0x251591CBL, 0xC4187630L, 0x57324F07L,
+ 0xB2C35182L, 0xBD16E4F0L, 0xC3B3DE3FL, 0xEA6CBA23L,
+ 0x00C810DBL, 0xBB040931L, 0xE0BA08A6L, 0xDC4AC24BL,
+ 0xEE1428BBL, 0x964A6F9FL, 0x2096F5DCL, 0x2170A50CL,
+ 0x3FBCA4BDL, 0x7C321FAAL, 0x88679D5DL, 0x0F5FFFDCL,
+ 0x88AA1865L, 0xFD51E01AL, 0xA35EF105L, 0x8B99A039L,
+ 0xD5C22176L, 0xB1A73D55L, 0xA0080F3DL, 0xDBC61A70L,
+ 0x3FB106E3L, 0xBAEA8E73L, 0x7E34C1B3L, 0x40F3DC19L,
+ 0xFD9AEEE4L, 0xA3E6A013L, 0xEECF6A5EL, 0xA83012D4L,
+ 0xDEDF0B25L, 0x7CB1D8C2L, 0x02C4180CL, 0xE38905D8L,
+ 0x2D631C38L, 0x36612C66L, 0x9845588CL, 0x9510F7FDL,
+ 0xBCD5C8B1L, 0x55D50272L, 0x9B35118EL, 0xFB7C4E3FL,
+ 0x2E3BD98FL, 0x56DD4BD7L, 0x4C3B6F27L, 0x0264DDB7L,
+ 0xBD4A811CL, 0x2A9A4F84L, 0x2292258AL, 0xE7799B02L,
+ 0xF5B529B2L, 0x894B5D58L, 0xD3392CAAL, 0x515CCAC1L,
+ 0xB6857B04L, 0xB2CBE5DFL, 0x28B230C2L, 0xE3CE03F5L,
+ 0x0581D4DFL, 0x3CC0D279L, 0xD0C1EDECL, 0x160B926FL,
+ 0x32F58056L, 0x9D574911L, 0xED9FB621L, 0xA2D920F7L,
+ 0xB4BE7EACL, 0x6947D33FL, 0xBB438F92L, 0xDD11B405L,
+ 0xDEFF0F70L, 0x03B3E6A8L, 0x4BD0A277L, 0xE28FA297L,
+ 0x7098CAF4L, 0xB74B88E5L, 0xC3330744L, 0x77E7975AL,
+ 0xB35AC1B1L, 0x3BFEA68CL, 0x84BF6163L, 0x101D5CC0L,
+ 0xF4558349L, 0x0CF6A28BL, 0xD76AF6B8L, 0xD6A3140DL,
+ 0x3F37D065L, 0x0E55EA90L, 0xC1A759D1L, 0x70265EABL,
+ }};
+
+#endif /* _CRYPT_KEY_INPUT_H */
diff --git a/src/libpiano/crypt_key_output.h b/src/libpiano/crypt_key_output.h
new file mode 100644
index 0000000..b8823d5
--- /dev/null
+++ b/src/libpiano/crypt_key_output.h
@@ -0,0 +1,303 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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.
+*/
+
+/* encryption key for xmlrpc */
+
+#ifndef _CRYPT_KEY_OUTPUT_H
+#define _CRYPT_KEY_OUTPUT_H
+
+#include <stdint.h>
+
+const unsigned int out_key_n = 16;
+
+static const uint32_t out_key_p[16 + 2] = {
+ 0x77B1CD94L, 0xDCB48217L, 0x69404CDCL, 0x2C02F724L,
+ 0x22961551L, 0xB497993BL, 0x5B5EEE8CL, 0xA209AE23L,
+ 0xE26B1B43L, 0x90F1CE4CL, 0xB86F975AL, 0xF3CB8371L,
+ 0xDED8E87CL, 0xB2882D4FL, 0x74984776L, 0x361650B2L,
+ 0x666FB475L, 0x85A10677L,
+ };
+
+static const uint32_t out_key_s[4][256] = {{
+ 0xBA448614L, 0xC35FBBD7L, 0x31B6EC14L, 0xA85F989BL,
+ 0x89A83B0EL, 0xAB11C2E7L, 0xFC376FBBL, 0x55B2E7B6L,
+ 0xA330F22EL, 0xA8229AE0L, 0x9D025EF0L, 0x5E917398L,
+ 0x3BE382F9L, 0x8F103C90L, 0x463C36A2L, 0xD223C350L,
+ 0x29DDD3F8L, 0x35075D14L, 0x0F963F64L, 0x74D02046L,
+ 0x28DCFBF1L, 0xEE12889EL, 0x46C74707L, 0xA1C5A67FL,
+ 0x8FFE2E9DL, 0xACE75324L, 0x49DA447CL, 0x3061FBB3L,
+ 0x14579E12L, 0xF1E8ED6EL, 0x76A679C1L, 0xD8EF0FEFL,
+ 0xA5EA7835L, 0xD63EDB8DL, 0x484F139DL, 0x3D3AD62AL,
+ 0x8A6331F1L, 0xEF43FE4EL, 0x3745B29FL, 0xEAF8A9ADL,
+ 0xF9FFF4AEL, 0x1E5BB3C5L, 0xFF988E0AL, 0x8C9E1147L,
+ 0x3EC6CED1L, 0x4CE0F56DL, 0x206E4341L, 0x79C0520FL,
+ 0x4FA3C2FDL, 0x1A8189E5L, 0xFA0BC58EL, 0xA0F29358L,
+ 0x42B379BBL, 0xEC55CB3DL, 0xBD7F1551L, 0x31E17BEAL,
+ 0xACC91098L, 0x7C36A984L, 0x8611A88BL, 0x255CE7CFL,
+ 0xC80DB988L, 0x743CEEBCL, 0x7AA258B6L, 0xE82424C9L,
+ 0xD1EAC3DBL, 0x2D562386L, 0x5250E40BL, 0x9B5285F5L,
+ 0x895D0124L, 0xAB8037D6L, 0xFD67BA8FL, 0x351B05D7L,
+ 0x0B124E65L, 0x20CF592BL, 0x91BE281FL, 0x879AD90AL,
+ 0x6609FEB6L, 0xD8A2305FL, 0x861194A2L, 0xD51DB0E3L,
+ 0xF735D810L, 0xAEFA5174L, 0x56A9577FL, 0x24F3408BL,
+ 0x10627B95L, 0xBEE74251L, 0x7ECD2211L, 0x95A8B55EL,
+ 0xBDE1028EL, 0xDEF339E2L, 0xD0768D34L, 0x6BD5A569L,
+ 0xC2240D0FL, 0xE171393DL, 0x14A24028L, 0x96760DFAL,
+ 0x4C78040FL, 0xBD6F76D0L, 0x5EE4BA83L, 0xB20C926FL,
+ 0xC0F0DCD8L, 0xEF5D4105L, 0x383D3B65L, 0x51E42A4AL,
+ 0x1505EECEL, 0x86B2BFFDL, 0x56EE7DFCL, 0x8734574BL,
+ 0xCAA9E52DL, 0xE0F66450L, 0x76B7037BL, 0x84AE5148L,
+ 0xFB7EC4EBL, 0x6A3DEB3DL, 0x06C7EA69L, 0x165F2DFFL,
+ 0x7B9DB92AL, 0xB9B24C67L, 0x43AB448AL, 0xA64F30D7L,
+ 0x998BE34DL, 0x628E747AL, 0x294162B9L, 0xAB8014C7L,
+ 0x22E1516FL, 0x74C3E589L, 0x003D14D0L, 0x8D724CE1L,
+ 0x2053C615L, 0x9AD67B30L, 0x29C3D0E8L, 0xBC3269B5L,
+ 0x7CD7D118L, 0x892FD6BEL, 0x30170170L, 0xF582162AL,
+ 0x35C8F272L, 0x19844866L, 0x85259078L, 0x7C744927L,
+ 0x1DA54115L, 0x5505DEC7L, 0x7A84A6D8L, 0xC7D8C609L,
+ 0x2E198969L, 0xF6020F4BL, 0x837DCD75L, 0x2DF45B36L,
+ 0x83C0DA9FL, 0x4DE5E15CL, 0x8F70E4A4L, 0x364CF096L,
+ 0xECBDAE6FL, 0x548DC21BL, 0xAFD70D50L, 0x60378522L,
+ 0x7A405AB5L, 0x60A18616L, 0xFA0905BCL, 0x953A1CCBL,
+ 0xBE5141D0L, 0xA9403F7BL, 0x5D1E37B6L, 0x500DA5D5L,
+ 0xBCDB815CL, 0xE78B89E3L, 0x63A3546FL, 0x5AAD9FD4L,
+ 0x5C6E8B71L, 0xD7971A6FL, 0x40856F48L, 0x91F6CA81L,
+ 0x956C447AL, 0xF966868FL, 0xF191BB51L, 0x7C015EAFL,
+ 0x58A8785BL, 0x43710423L, 0x2C246B80L, 0x0072DCD6L,
+ 0x40569C99L, 0xCB544BB9L, 0x2CC2B579L, 0xA3AF010DL,
+ 0x5638B6A5L, 0x8918B2D4L, 0x18928D95L, 0x557BAE61L,
+ 0xA0D578F2L, 0x3CF4B1F1L, 0x1D592634L, 0xED9B40B5L,
+ 0x0BE033EBL, 0xDD437BAEL, 0x6188202DL, 0x9F0A444DL,
+ 0x4DCD2CC0L, 0xC28D6DC5L, 0x9ABDD269L, 0xB0843012L,
+ 0x00951EE5L, 0x4715466DL, 0x3CC39115L, 0xB270DD7BL,
+ 0x6510D189L, 0x2BBC6F8FL, 0x331B8E06L, 0x54F13F9EL,
+ 0xA8223944L, 0xC7B1B375L, 0xFD79FA77L, 0xB7930324L,
+ 0x6197A094L, 0x46CB50D9L, 0xC49A908FL, 0xAC297BAFL,
+ 0x84D14B51L, 0x0A50682AL, 0x8AFB5E73L, 0x1C1172C6L,
+ 0x358A5364L, 0x88415003L, 0x4D5666F8L, 0xA287110CL,
+ 0xD61B0A4AL, 0x3CC0961BL, 0x44BF5FC3L, 0xD69E0492L,
+ 0xCFD94EE6L, 0x019813D5L, 0xB7D38A5FL, 0x69C15DC1L,
+ 0x0D896A60L, 0x7ACB18CEL, 0x5CE6D6E7L, 0x743A4295L,
+ 0xD7E5A8B7L, 0x457E7DD5L, 0x4A07442BL, 0x2ADA51DEL,
+ 0xF3202F3BL, 0xD4574157L, 0x81A94A0AL, 0xF26BDDFDL,
+ 0x775935B3L, 0xB9BC3B76L, 0x3FD81E05L, 0xB95EE989L,
+ 0x9A0F555DL, 0xFD7E49C8L, 0x3A1D12C5L, 0x7955E2EFL,
+ 0x7F4A75A1L, 0x16DD1739L, 0x3B3EF7E0L, 0x8795C597L,
+ }, {
+ 0x0640766DL, 0xCD661DAFL, 0x2BDECFBDL, 0xDAA77B87L,
+ 0x5A7E41B5L, 0x9A3CD4EAL, 0xC6CA8D2FL, 0x65F989EBL,
+ 0x736C3573L, 0xFA69679CL, 0xB317B71BL, 0x41A7E6FEL,
+ 0x72D83E2AL, 0xE22CAB56L, 0x08920117L, 0x7030D96EL,
+ 0x35CDE674L, 0x7405A058L, 0x97D69990L, 0xE95C5EF3L,
+ 0xDC992FB4L, 0xD33C9F8BL, 0xDAF1AD7AL, 0x03503095L,
+ 0x00967754L, 0x8CF12CD7L, 0x950FD719L, 0x1ADB3F8CL,
+ 0xDB4F120AL, 0x712C33D5L, 0x8626D609L, 0x1F2CA165L,
+ 0x3B8FB27FL, 0xBBA56B76L, 0xD0D7517CL, 0xE1B34706L,
+ 0x2D89956EL, 0xFFDFF151L, 0xD992E142L, 0x9C662E5AL,
+ 0xD3D210A4L, 0xD914DD04L, 0x712980F3L, 0xF5AE77E4L,
+ 0x1E186A6DL, 0xC3CED643L, 0x5A8841B0L, 0xD113DB05L,
+ 0x1C93C9FDL, 0xB0B64822L, 0xDCDA2B02L, 0x09F3B19AL,
+ 0xF59692CEL, 0x01117325L, 0xCA61CC6BL, 0xF3E6788AL,
+ 0xAE70FCF0L, 0x2BA3BDD7L, 0x359B04A2L, 0xB422ECFFL,
+ 0x4CF00372L, 0x6374AD34L, 0xEB0C631BL, 0x56587BA8L,
+ 0xC32CD2DEL, 0x5294BDC9L, 0x65B79362L, 0x5E38BFADL,
+ 0x2A8FBE8CL, 0x8E738683L, 0xC673FFD0L, 0x4B04FB4DL,
+ 0xCC76EE0FL, 0xF45FD305L, 0xB77848D3L, 0x0D3CB64EL,
+ 0x48772949L, 0xB5E5B2EEL, 0x354BFBEAL, 0x22BDE81FL,
+ 0x2181F5BEL, 0x40F5F099L, 0x99C49F11L, 0xEF46CF3BL,
+ 0x4F9B3DD0L, 0x2E34B17EL, 0xF35F478EL, 0xB0ADC5E2L,
+ 0x06A4CE4EL, 0xF0395C8AL, 0x3F344031L, 0x0B4A502CL,
+ 0x85DD868CL, 0x0E52D760L, 0x84124817L, 0x08197C7AL,
+ 0x5520AD9AL, 0xEA8268FCL, 0xA11B655BL, 0xE3204A0EL,
+ 0x9183F85FL, 0x9A294A29L, 0xE6350EC4L, 0x09202931L,
+ 0x28BA52EFL, 0x21B2DB1CL, 0xA20FF528L, 0x42918DAEL,
+ 0xB8E49971L, 0x9E203A81L, 0x2A07F36CL, 0xA2F84D75L,
+ 0x824D4513L, 0x1D8AC558L, 0x7E65E471L, 0x06002169L,
+ 0xBEBC8C82L, 0x9B7B8B6BL, 0x3FF164C2L, 0x5672E9C3L,
+ 0xBFC72AA3L, 0xD3C9D9E3L, 0xCB64E2DDL, 0xE196A84DL,
+ 0xCF336932L, 0xA5F3CA20L, 0xA8D3D903L, 0x81CB0B86L,
+ 0xFB4BF27DL, 0xFCA4C125L, 0x21F1D5B6L, 0xCF6BA988L,
+ 0x1D408BB7L, 0x629F088FL, 0xF4746130L, 0x1B5C2212L,
+ 0x47AD1F87L, 0x1E1E9B5CL, 0xCD01B844L, 0x5D3307E1L,
+ 0xA68FE8F7L, 0x3B13346BL, 0x602308E7L, 0x751416ABL,
+ 0x38030F2BL, 0x1ABC8EF6L, 0x8929E128L, 0x97040FCAL,
+ 0x13E59C8DL, 0xA121B4D9L, 0xDB599765L, 0x95EDA62DL,
+ 0xDAEA1CC5L, 0x2CAF5AC9L, 0x5490F679L, 0xFB410588L,
+ 0x023C15F3L, 0x5FCD4247L, 0x99455DD9L, 0x82CFC454L,
+ 0x2EBECB55L, 0xE6ED032FL, 0x876F578DL, 0xA4BF3657L,
+ 0x8DAD590EL, 0xF6ECC607L, 0xF8B5CD9CL, 0xEC124316L,
+ 0x4159E6DAL, 0xD7EFA744L, 0x2A20A160L, 0x982F527FL,
+ 0x60C84A77L, 0xFF60A850L, 0xFD5C6EAFL, 0xFD2D5797L,
+ 0x954F58C6L, 0xA48A9852L, 0x55B5A9D2L, 0xF3F29933L,
+ 0xB3580EC7L, 0xF7021CB8L, 0x25238BDBL, 0x7FE667E2L,
+ 0x47353A71L, 0xD9F9D37DL, 0x79374988L, 0x939566EFL,
+ 0xC1279574L, 0x835BD0DCL, 0xCB52DE4FL, 0x028364BDL,
+ 0xBE33E780L, 0xB09647D8L, 0x73FA4EABL, 0x859922EAL,
+ 0xC7520CCAL, 0x72A331D2L, 0x3F5CFE19L, 0x008F9772L,
+ 0x5CBFD2B8L, 0xF1937A57L, 0xACC6DBA9L, 0xEFF7AB89L,
+ 0x72A55667L, 0x628DE4B7L, 0xA0CE9591L, 0x3BFD1D5FL,
+ 0xCA8D7811L, 0x55BE1BD2L, 0x8B4E3C73L, 0xE0ADA4A4L,
+ 0xB0A9AB99L, 0xFE319FE4L, 0x1C2BE3A3L, 0x037B2517L,
+ 0xC084B5D6L, 0x2BE02274L, 0xFF05F558L, 0x8482CABDL,
+ 0x8B3D719AL, 0x1CB98561L, 0x80DA6AEEL, 0x443B8093L,
+ 0x01D8FAA4L, 0xD7B783D6L, 0x33F0B71EL, 0x73CA82D8L,
+ 0x4D9DFDA4L, 0xA91F57FEL, 0x17AB093CL, 0x689F8D37L,
+ 0x40EE7199L, 0xFA702024L, 0xD1DD8C1FL, 0x5F9BD69BL,
+ 0x64D28176L, 0xF9DAA31BL, 0xBB186496L, 0x078787B1L,
+ 0x3783C428L, 0xBDA68050L, 0x7E709830L, 0x1BBDB10EL,
+ 0x6349027FL, 0xF8306215L, 0xD8855420L, 0x0BE3D1A8L,
+ 0xD3D9C187L, 0x5329049FL, 0xE3395F69L, 0x65AC7995L,
+ 0x664848D1L, 0x68C64650L, 0x0F9C1F75L, 0xA4808E20L,
+ }, {
+ 0x5CFD009FL, 0x4C09F290L, 0xA786F99CL, 0x76A5A243L,
+ 0x2DC97400L, 0x424D9AAEL, 0x6644DE0CL, 0x30DAFAE5L,
+ 0x5ED77CC3L, 0xD1003D99L, 0xCD3C1222L, 0x1CA766C1L,
+ 0x5B976615L, 0x014E4796L, 0x0A715936L, 0x405D0D57L,
+ 0x0AB115F1L, 0x8A963877L, 0xBE96D670L, 0xE2310AACL,
+ 0x148D00F2L, 0x9787F4BFL, 0xE5F62A68L, 0xFD025DA4L,
+ 0x194B6DF7L, 0x960D3E7BL, 0xB330C5C1L, 0x2932C25BL,
+ 0x13738072L, 0x9D62ABA1L, 0x0ECD92CAL, 0xF75ACC9BL,
+ 0x229E433AL, 0xD0A247BFL, 0xE4B0D9BAL, 0xFA69F70EL,
+ 0x9B7D254FL, 0xD2849281L, 0x4132F364L, 0xF2E4B87FL,
+ 0x4189B43CL, 0x7E807CBCL, 0x10498724L, 0xF48C5F29L,
+ 0x03312ACEL, 0x239307BDL, 0x3541CEBCL, 0x1B1AE36DL,
+ 0xF993F1B1L, 0x3B6BE060L, 0x9782191AL, 0xACC1CC1BL,
+ 0xA8B4798EL, 0x486399CDL, 0x59A7ECB9L, 0x46490B98L,
+ 0xB80EA77EL, 0x1071EE10L, 0x8FE10517L, 0xE29D8F08L,
+ 0x9BDA44C2L, 0x629C5056L, 0xE40E10E3L, 0x0048CBB1L,
+ 0xF8E698E7L, 0x09369CB7L, 0x898942DCL, 0x0F49BFE0L,
+ 0x3600B868L, 0x44EE4C88L, 0x6625BBFBL, 0x7C956C83L,
+ 0x5C42B182L, 0x080AF33BL, 0x1503CC24L, 0xAE64DA10L,
+ 0x9F3537D3L, 0x99618740L, 0xE7D50FFDL, 0x6CBB4AFFL,
+ 0x56062EE1L, 0x70C1AD52L, 0xAC54BF35L, 0x5A7D4D07L,
+ 0x65DC58DEL, 0x7B362255L, 0x6133AFC8L, 0x4C2ACE68L,
+ 0x858FA998L, 0x5C336C93L, 0x78193EA7L, 0x5613E9BDL,
+ 0x8B1F58ACL, 0x563D5D47L, 0x6163AF27L, 0x71183690L,
+ 0x5944DDC3L, 0x817D18BEL, 0x41260F8BL, 0x259ED297L,
+ 0xA3CCEE2EL, 0xDBB13DDDL, 0x1009CD47L, 0xD12B82C3L,
+ 0x9A6FF89DL, 0x4248C9D4L, 0x31484739L, 0x7C3DEEC3L,
+ 0x2E2CBAC1L, 0x9597DDA3L, 0xF64414B0L, 0x066FC96FL,
+ 0x31F4AAA0L, 0xB2DBBCFFL, 0x50B991ADL, 0x95AC5272L,
+ 0x0CAA50C4L, 0x0864DE61L, 0xF38A70D4L, 0x08CE8D6CL,
+ 0x53941C89L, 0xC54DBEBEL, 0xE4DBC2B0L, 0x2B3E9E66L,
+ 0x6F3E0BA4L, 0x164DC014L, 0xCC6CEC5FL, 0x08238664L,
+ 0x30988E18L, 0xFDE4501AL, 0x030B3099L, 0xE6F49802L,
+ 0x94A08714L, 0xC3A784E5L, 0xECDA930DL, 0x0B40E2F9L,
+ 0x0EE564F8L, 0xF5993B88L, 0x5B8DB5DBL, 0xF77CCA85L,
+ 0x9C4FCDA9L, 0x2114F372L, 0x2AA12CCEL, 0x2B07C3F1L,
+ 0xD660E47AL, 0xD0B26A65L, 0x56F8945CL, 0x4C07FCA5L,
+ 0x5EF2197FL, 0x073CBFFCL, 0xF83E8935L, 0x04DB4798L,
+ 0x79DE4FB4L, 0x207E0BB1L, 0xD0D47C14L, 0x9DADB205L,
+ 0x5EBD3EA0L, 0xCA83B290L, 0xC8BF53A1L, 0x4DCBF491L,
+ 0xB590CD79L, 0x9C98C12EL, 0x8C877D6EL, 0xD0F17FD2L,
+ 0xB3FFF22FL, 0xE8D38B07L, 0x15641B63L, 0x6FE5D245L,
+ 0x04045C48L, 0xEF16069BL, 0xB58781CEL, 0x7D07653DL,
+ 0xCFB9BD0CL, 0x21CA5DE7L, 0xB35606D9L, 0xA7854DE0L,
+ 0xC134207CL, 0xFE978430L, 0x0C830455L, 0xCB784991L,
+ 0xC95A3072L, 0xC0AC5E17L, 0x7B999149L, 0xA289D877L,
+ 0xB4E3254EL, 0x743F72B1L, 0x98CF8054L, 0x7E4E1C3FL,
+ 0x0A64C32CL, 0xF04CEB0FL, 0x0488ABF6L, 0x004554B3L,
+ 0x359E3441L, 0x192ABA6FL, 0x28DC322EL, 0xDDE52491L,
+ 0xF1D8C2A7L, 0xEAE3E74AL, 0xA10B3376L, 0x7A879F55L,
+ 0xB5F13C45L, 0x5194862CL, 0x6F65DC08L, 0x753F6AFEL,
+ 0xF08EA616L, 0x26D382C3L, 0x3315E1E5L, 0x538106B9L,
+ 0xEC0B9F78L, 0xF298C82DL, 0x0125FD86L, 0x07465A0DL,
+ 0x23953151L, 0x70569F93L, 0x75D261A5L, 0xC5AD33E5L,
+ 0x49464CDFL, 0xB4E0D04DL, 0xA0866011L, 0x383CC817L,
+ 0x545FBC56L, 0xA49BED33L, 0x4E4B516AL, 0x38CBF5D4L,
+ 0x6DBA987CL, 0x1DFE208FL, 0x65466F04L, 0x6D3DCD1DL,
+ 0xEB4D60C6L, 0xE3FB6AECL, 0xFA16DBDBL, 0xCD1CDF2BL,
+ 0xF07F845DL, 0x4DA0ECD1L, 0x0EEB40B0L, 0x1CB7A2F0L,
+ 0xCD8E54F3L, 0x37376D53L, 0xF7AAC8D3L, 0x8BA724AAL,
+ 0xEF5C922DL, 0x6F1BE181L, 0xD1808DF8L, 0x44764B58L,
+ 0x0CD247C4L, 0x0C137F60L, 0xB77F6981L, 0x4AC13745L,
+ 0xBA311B48L, 0x42A5DE75L, 0x4522E7E0L, 0x84E90F54L,
+ 0xCC0AD21CL, 0x00F8D9A7L, 0x9CA2CED9L, 0x4F8E0583L,
+ }, {
+ 0x6AFF5CA7L, 0x0D8AB1F0L, 0x75E3AD44L, 0x6B5A1A52L,
+ 0xE9658216L, 0x71B42FBBL, 0xF57D3F6CL, 0x0A79678EL,
+ 0x534F306FL, 0xB9C725ABL, 0x7157BF11L, 0x2A52F490L,
+ 0x4D01079CL, 0x6D18FC2BL, 0x94EC0BDBL, 0xA736324EL,
+ 0x7540D554L, 0x6B9DFB4DL, 0xFA158CA1L, 0xECE8E1ABL,
+ 0xAFAF64B2L, 0x61450E7BL, 0x4CEBA4FAL, 0xB2AFAAA2L,
+ 0xFE669447L, 0x4E63D10BL, 0xFBD8AEC8L, 0x5F6B6B1AL,
+ 0x70A8F15BL, 0xA91D68B6L, 0x59034211L, 0x98273D40L,
+ 0xB9A9C9ADL, 0xCE4DDCD7L, 0xC3159554L, 0xFD5D1C2DL,
+ 0x9ABF93B5L, 0x70A8C01FL, 0xA70FCF1AL, 0x641301DDL,
+ 0x4EDB9E0BL, 0xC548E7B1L, 0x4884B172L, 0x714610B7L,
+ 0x4086DCCFL, 0xC8D88CC6L, 0x77594608L, 0xBCABFDE1L,
+ 0x77962F00L, 0x56F6372CL, 0xB9507A98L, 0x0EF9473EL,
+ 0xDBA8C6E2L, 0x5BE1AA28L, 0xCEA5C805L, 0x750F1D15L,
+ 0x1F986C17L, 0x3693A790L, 0x71B0C4CCL, 0xC0776116L,
+ 0xD660B7BCL, 0x8D0ABAB1L, 0x7F1FDFB7L, 0x4900FDE1L,
+ 0x2208372BL, 0xB8263157L, 0xB53378F5L, 0x10DF5849L,
+ 0xC3C8CD69L, 0x5E842514L, 0x8085986AL, 0xC305CE78L,
+ 0x6C5D3C16L, 0xC11A49B6L, 0x391DBE69L, 0x3BACD18DL,
+ 0x87253FDEL, 0x56D409DEL, 0x68629118L, 0x1C1E9C6FL,
+ 0x7F737B29L, 0x96838CC1L, 0xC300D7B2L, 0x2A23D185L,
+ 0x52271100L, 0xCB4B3F01L, 0xB78A9B86L, 0xB9BEDEF5L,
+ 0x5D8075FFL, 0x5C325E39L, 0x48A76A2BL, 0x96A61231L,
+ 0x933ADA05L, 0xF9971C9CL, 0xFF2C75FCL, 0x27FA5DAAL,
+ 0xAEF8AD6EL, 0xF2DEA20BL, 0x11F247FBL, 0xC383DA2AL,
+ 0xF85A5ED1L, 0xC35FBC79L, 0xBDCEF0ADL, 0xEEBBCFB3L,
+ 0xCA711BF4L, 0xB7DABFB3L, 0xE03177D0L, 0xBB727F1FL,
+ 0x142CC33DL, 0xDB6C384EL, 0xAC6D38B4L, 0xF55B4325L,
+ 0xF020F44FL, 0x5630D9C1L, 0xED39B172L, 0xACB5AFF7L,
+ 0x8D98DC58L, 0xB6DF90A1L, 0xEC98CD13L, 0xE0C6A237L,
+ 0xAC64BDF0L, 0x3560D439L, 0x7B0DDB44L, 0x1D3682C3L,
+ 0x664EBF1AL, 0xD41A4A0CL, 0x0D5DFE5BL, 0x4E133D8CL,
+ 0xB129ECD6L, 0xD9DA3194L, 0xE45DADBAL, 0x6B27E47FL,
+ 0xBA9600DDL, 0x8E1936D2L, 0x1F8B1E51L, 0x3860AE03L,
+ 0xEE540893L, 0x5F31C76BL, 0xE9302FA1L, 0x452CB6B9L,
+ 0x5D9CA533L, 0xA19F4B2FL, 0x6C0046A4L, 0x657713DDL,
+ 0x1B5D5A04L, 0xC3D594E6L, 0xBB80FA30L, 0x9BF9E647L,
+ 0x716F2F97L, 0xF2B8ADA6L, 0x7F242755L, 0x40FB6614L,
+ 0x4F85BCF9L, 0x18799EB2L, 0x76B4124AL, 0xFCAEC005L,
+ 0x38231D8AL, 0xD449CE69L, 0xC6877AD2L, 0x16B37A06L,
+ 0x7DAF096BL, 0xCE11C2B4L, 0x750754B5L, 0xCAC33C86L,
+ 0x10E20D73L, 0xE4997D0FL, 0x6B06DE8DL, 0x1D70D15FL,
+ 0xBF060E4FL, 0x18BC4C59L, 0xD1C8585EL, 0x1AEC12B4L,
+ 0x9AB3F241L, 0x46CFC4CDL, 0xCF16D587L, 0xF1722424L,
+ 0x0ECA4D9CL, 0xCC6131F3L, 0x0E990CC3L, 0x9771D4BFL,
+ 0x4C624E9FL, 0x5CA79CBCL, 0x02368C90L, 0x25826D54L,
+ 0x32D1B4C9L, 0xDA9A049BL, 0x616CC784L, 0x47B562F3L,
+ 0xB5553B6EL, 0xF205F397L, 0x6FA01B63L, 0xC857201FL,
+ 0xC5C7C8A1L, 0xA27C47E2L, 0xDB185877L, 0x67236F63L,
+ 0xBF369D88L, 0x4E7054BDL, 0xD6544BEAL, 0x36EF3BC3L,
+ 0x60F4B74DL, 0x430EA61EL, 0xC3E9C76DL, 0x499A0517L,
+ 0x91D45BCDL, 0xDCAEADFAL, 0x355FBC9DL, 0x8CA1770EL,
+ 0x820BE4F9L, 0x00AE9FD2L, 0x9E63CE49L, 0x46FE33E8L,
+ 0xA07048A3L, 0x225FBD27L, 0xAF0FD02CL, 0x8F9D7C95L,
+ 0xD4F0E647L, 0x6DFB3949L, 0xE289B0B0L, 0xF9CC7F56L,
+ 0x7F168AD2L, 0xED8433E0L, 0xEBB189B5L, 0x765AD69CL,
+ 0x7730DCF0L, 0xD54C3A0DL, 0xACF6BD6BL, 0x1E19DF12L,
+ 0xBD984D49L, 0xC4FC38B8L, 0x0D36C9FFL, 0xCC3AD912L,
+ 0x0A6E4ED6L, 0x27B27847L, 0x604F6917L, 0xD4C1ADADL,
+ 0xCB19F4BEL, 0x5127D2FDL, 0x38FFA03AL, 0xB983BC5FL,
+ 0x5CAF3A6EL, 0x84E94F45L, 0xFC3FA473L, 0xC9299B07L,
+ 0x322F38AFL, 0x2A228E93L, 0x76D63922L, 0x1326A518L,
+ 0xFEC3F151L, 0xB821D755L, 0x8D1C67ACL, 0x5DA96628L,
+ 0x04F7335FL, 0xC6F5CD2EL, 0x7D211328L, 0xE19180D3L,
+ }};
+
+#endif /* _CRYPT_KEY_OUTPUT_H */
diff --git a/src/libpiano/piano.c b/src/libpiano/piano.c
new file mode 100644
index 0000000..f6343b4
--- /dev/null
+++ b/src/libpiano/piano.c
@@ -0,0 +1,1114 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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.
+*/
+
+#define _BSD_SOURCE /* required by strdup() */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+#include <stdint.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 "29"
+#define PIANO_RPC_HOST "www.pandora.com"
+#define PIANO_RPC_PORT "80"
+#define PIANO_RPC_PATH "/radio/xmlrpc/v" PIANO_PROTOCOL_VERSION "?"
+#define PIANO_SEND_BUFFER_SIZE 10000
+
+/* initialize piano handle
+ * @param piano handle
+ * @return nothing
+ */
+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);
+}
+
+/* free complete search result
+ * @public yes
+ * @param search result
+ */
+void PianoDestroySearchResult (PianoSearchResult_t *searchResult) {
+ PianoArtist_t *curArtist, *lastArtist;
+ PianoSong_t *curSong, *lastSong;
+
+ curArtist = searchResult->artists;
+ while (curArtist != NULL) {
+ free (curArtist->name);
+ free (curArtist->musicId);
+ lastArtist = curArtist;
+ curArtist = curArtist->next;
+ free (lastArtist);
+ }
+
+ curSong = searchResult->songs;
+ while (curSong != NULL) {
+ free (curSong->title);
+ free (curSong->artist);
+ free (curSong->musicId);
+ lastSong = curSong;
+ curSong = curSong->next;
+ free (lastSong);
+ }
+}
+
+/* free single station
+ * @param station
+ */
+void PianoDestroyStation (PianoStation_t *station) {
+ free (station->name);
+ free (station->id);
+ memset (station, 0, sizeof (station));
+}
+
+/* free complete station list
+ * @param piano handle
+ */
+void PianoDestroyStations (PianoStation_t *stations) {
+ PianoStation_t *curStation, *lastStation;
+
+ curStation = stations;
+ while (curStation != NULL) {
+ lastStation = curStation;
+ curStation = curStation->next;
+ PianoDestroyStation (lastStation);
+ free (lastStation);
+ }
+}
+
+/* FIXME: copy & waste */
+/* free _all_ elements of playlist
+ * @param piano handle
+ * @return nothing
+ */
+void PianoDestroyPlaylist (PianoSong_t *playlist) {
+ PianoSong_t *curSong, *lastSong;
+
+ curSong = playlist;
+ while (curSong != NULL) {
+ free (curSong->audioUrl);
+ free (curSong->coverArt);
+ free (curSong->artist);
+ free (curSong->musicId);
+ free (curSong->title);
+ free (curSong->userSeed);
+ free (curSong->stationId);
+ free (curSong->album);
+ free (curSong->artistMusicId);
+ lastSong = curSong;
+ curSong = curSong->next;
+ free (lastSong);
+ }
+}
+
+/* destroy genre linked list
+ */
+void PianoDestroyGenres (PianoGenre_t *genres) {
+ PianoGenre_t *curGenre, *lastGenre;
+
+ curGenre = genres;
+ while (curGenre != NULL) {
+ free (curGenre->name);
+ free (curGenre->musicId);
+ lastGenre = curGenre;
+ curGenre = curGenre->next;
+ free (lastGenre);
+ }
+}
+
+/* destroy user information
+ */
+void PianoDestroyUserInfo (PianoUserInfo_t *user) {
+ free (user->webAuthToken);
+ free (user->authToken);
+ free (user->listenerId);
+}
+
+/* frees the whole piano handle structure
+ * @param piano handle
+ * @return nothing
+ */
+void PianoDestroy (PianoHandle_t *ph) {
+ PianoDestroyUserInfo (&ph->user);
+ PianoDestroyStations (ph->stations);
+ /* destroy genre stations */
+ PianoGenreCategory_t *curGenreCat = ph->genreStations, *lastGenreCat;
+ while (curGenreCat != NULL) {
+ PianoDestroyGenres (curGenreCat->genres);
+ free (curGenreCat->name);
+ lastGenreCat = curGenreCat;
+ curGenreCat = curGenreCat->next;
+ free (lastGenreCat);
+ }
+ memset (ph, 0, sizeof (*ph));
+}
+
+/* destroy request, free post data. req->responseData is *not* freed here, as
+ * it might be allocated by something else than malloc!
+ * @param piano request
+ */
+void PianoDestroyRequest (PianoRequest_t *req) {
+ free (req->postData);
+ memset (req, 0, sizeof (*req));
+}
+
+/* convert audio format id to string that can be used in xml requests
+ * @param format id
+ * @return constant string
+ */
+static const char *PianoAudioFormatToString (PianoAudioFormat_t format) {
+ switch (format) {
+ case PIANO_AF_AACPLUS:
+ return "aacplus";
+ break;
+
+ case PIANO_AF_MP3:
+ return "mp3";
+ break;
+
+ case PIANO_AF_MP3_HI:
+ return "mp3-hifi";
+ break;
+
+ default:
+ return NULL;
+ break;
+ }
+}
+
+/* prepare piano request (initializes request type, urlpath and postData)
+ * @param piano handle
+ * @param request structure
+ * @param request type
+ */
+PianoReturn_t PianoRequest (PianoHandle_t *ph, PianoRequest_t *req,
+ PianoRequestType_t type) {
+ char xmlSendBuf[PIANO_SEND_BUFFER_SIZE];
+ /* corrected timestamp */
+ time_t timestamp = time (NULL) - ph->timeOffset;
+
+ assert (ph != NULL);
+ assert (req != NULL);
+
+ req->type = type;
+
+ switch (req->type) {
+ case PIANO_REQUEST_LOGIN: {
+ /* authenticate user */
+ PianoRequestDataLogin_t *logindata = req->data;
+
+ assert (logindata != NULL);
+
+ 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);
+ break;
+
+ case 1:
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf),
+ "<?xml version=\"1.0\"?><methodCall>"
+ "<methodName>listener.authenticateListener</methodName>"
+ "<params><param><value><int>%lu</int></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "</params></methodCall>", (unsigned long) timestamp,
+ logindata->user, logindata->password);
+ snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
+ "rid=%s&method=authenticateListener", ph->routeId);
+ break;
+ }
+ break;
+ }
+
+ 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);
+ break;
+
+ case PIANO_REQUEST_GET_PLAYLIST: {
+ /* get playlist for specified station */
+ PianoRequestDataGetPlaylist_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->station != NULL);
+ 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));
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_FEEDBACK: {
+ /* low-level, don't use directly (see _RATE_SONG and _MOVE_SONG) */
+ PianoRequestDataAddFeedback_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->stationId != NULL);
+ assert (reqData->musicId != 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>"
+ /* music id */
+ "<param><value><string>%s</string></value></param>"
+ /* user seed */
+ "<param><value><string>%s</string></value></param>"
+ /* test strategy */
+ "<param><value>%u</value></param>"
+ /* positive */
+ "<param><value><boolean>%i</boolean></value></param>"
+ /* "is-creator-quickmix" */
+ "<param><value><boolean>0</boolean></value></param>"
+ /* song type */
+ "<param><value><int>%u</int></value></param>"
+ "</params></methodCall>", (unsigned long) timestamp,
+ ph->user.authToken, reqData->stationId, reqData->musicId,
+ (reqData->userSeed == NULL) ? "" : reqData->userSeed,
+ reqData->testStrategy,
+ (reqData->rating == PIANO_RATE_LOVE) ? 1 : 0,
+ reqData->songType);
+ snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s"
+ "&arg3=%s&arg4=%u&arg5=%s&arg6=false&arg7=%u",
+ ph->routeId, ph->user.listenerId, reqData->stationId,
+ reqData->musicId,
+ (reqData->userSeed == NULL) ? "" : reqData->userSeed,
+ reqData->testStrategy,
+ (reqData->rating == PIANO_RATE_LOVE) ? "true" : "false",
+ reqData->songType);
+ 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);
+
+ free (urlencodedNewName);
+ free (xmlencodedNewName);
+ break;
+ }
+
+ case PIANO_REQUEST_DELETE_STATION: {
+ /* delete station */
+ PianoStation_t *station = req->data;
+
+ assert (station != 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);
+ 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);
+
+ free (urlencodedSearchStr);
+ free (xmlencodedSearchStr);
+ break;
+ }
+
+ case PIANO_REQUEST_CREATE_STATION: {
+ /* create new station from specified musicid (type=mi, get one by
+ * performing a search) or shared station id (type=sh) */
+ PianoRequestDataCreateStation_t *reqData = req->data;
+
+ 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>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s%s</string></value></param>"
+ "</params></methodCall>", (unsigned long) timestamp,
+ ph->user.authToken, reqData->type, reqData->id);
+
+ snprintf (req->urlPath, sizeof (req->urlPath), PIANO_RPC_PATH
+ "rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId,
+ ph->user.listenerId, reqData->type, reqData->id);
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_SEED: {
+ /* add another seed to specified station */
+ PianoRequestDataAddSeed_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ 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>"
+ "<param><value><string>%s</string></value></param>"
+ "<param><value><string>%s</string></value></param>"
+ "<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);
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_TIRED_SONG: {
+ /* ban song for a month from all stations */
+ PianoSong_t *song = req->data;
+
+ 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);
+ 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;
+
+ 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;
+ }
+ /* 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>"
+ /* content type */
+ "<param><value><string>CUSTOM</string></value></param>"
+ /* genre */
+ "<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=CUSTOM&arg4=",
+ ph->routeId, ph->user.listenerId, urlArgBuf);
+ break;
+ }
+
+ 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);
+ break;
+
+ case PIANO_REQUEST_TRANSFORM_STATION: {
+ /* transform shared station into private */
+ PianoStation_t *station = req->data;
+
+ 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);
+ break;
+ }
+
+ case PIANO_REQUEST_EXPLAIN: {
+ /* explain why particular song was played */
+ PianoRequestDataExplain_t *reqData = req->data;
+
+ 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);
+ break;
+ }
+
+ case PIANO_REQUEST_GET_SEED_SUGGESTIONS: {
+ /* find similar artists */
+ PianoRequestDataGetSeedSuggestions_t *reqData = req->data;
+
+ assert (reqData != NULL);
+ assert (reqData->musicId != NULL);
+ assert (reqData->max != 0);
+
+ snprintf (xmlSendBuf, sizeof (xmlSendBuf), "<?xml version=\"1.0\"?>"
+ "<methodCall><methodName>music.getSeedSuggestions</methodName>"
+ "<params><param><value><int>%lu</int></value></param>"
+ /* auth token */
+ "<param><value><string>%s</string></value></param>"
+ /* seed music id */
+ "<param><value><string>%s</string></value></param>"
+ /* max */
+ "<param><value><int>%u</int></value></param>"
+ "</params></methodCall>", (unsigned long) timestamp,
+ ph->user.authToken, reqData->musicId, reqData->max);
+ 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);
+ break;
+ }
+
+ case PIANO_REQUEST_BOOKMARK_SONG: {
+ /* bookmark song */
+ PianoSong_t *song = req->data;
+
+ 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);
+ break;
+ }
+
+ case PIANO_REQUEST_BOOKMARK_ARTIST: {
+ /* bookmark artist */
+ PianoSong_t *song = req->data;
+
+ 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);
+ break;
+ }
+
+ /* "high-level" wrapper */
+ case PIANO_REQUEST_RATE_SONG: {
+ /* love/ban song */
+ PianoRequestDataRateSong_t *reqData = req->data;
+ PianoReturn_t pRet;
+
+ assert (reqData != NULL);
+ assert (reqData->song != NULL);
+ assert (reqData->rating != PIANO_RATE_NONE);
+
+ PianoRequestDataAddFeedback_t transformedReqData;
+ transformedReqData.stationId = reqData->song->stationId;
+ transformedReqData.musicId = reqData->song->musicId;
+ transformedReqData.userSeed = reqData->song->userSeed;
+ transformedReqData.rating = reqData->rating;
+ transformedReqData.testStrategy = reqData->song->testStrategy;
+ transformedReqData.songType = reqData->song->songType;
+ req->data = &transformedReqData;
+
+ /* create request data (url, post data) */
+ pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
+ /* and reset request type/data */
+ req->type = PIANO_REQUEST_RATE_SONG;
+ req->data = reqData;
+
+ return pRet;
+ break;
+ }
+
+ case PIANO_REQUEST_MOVE_SONG: {
+ /* move song to a different station, needs two requests */
+ PianoRequestDataMoveSong_t *reqData = req->data;
+ PianoRequestDataAddFeedback_t transformedReqData;
+ PianoReturn_t pRet;
+
+ assert (reqData != NULL);
+ assert (reqData->song != NULL);
+ assert (reqData->from != NULL);
+ assert (reqData->to != NULL);
+ assert (reqData->step < 2);
+
+ transformedReqData.musicId = reqData->song->musicId;
+ transformedReqData.userSeed = "";
+ transformedReqData.songType = reqData->song->songType;
+ transformedReqData.testStrategy = reqData->song->testStrategy;
+ req->data = &transformedReqData;
+
+ switch (reqData->step) {
+ case 0:
+ transformedReqData.stationId = reqData->from->id;
+ transformedReqData.rating = PIANO_RATE_BAN;
+ break;
+
+ case 1:
+ transformedReqData.stationId = reqData->to->id;
+ transformedReqData.rating = PIANO_RATE_LOVE;
+ break;
+ }
+
+ /* create request data (url, post data) */
+ pRet = PianoRequest (ph, req, PIANO_REQUEST_ADD_FEEDBACK);
+ /* and reset request type/data */
+ req->type = PIANO_REQUEST_MOVE_SONG;
+ req->data = reqData;
+
+ return pRet;
+ break;
+ }
+ }
+
+ if ((req->postData = PianoEncryptString (xmlSendBuf)) == NULL) {
+ return PIANO_RET_OUT_OF_MEMORY;
+ }
+
+ return PIANO_RET_OK;
+}
+
+/* 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;
+
+ assert (ph != NULL);
+ assert (req != NULL);
+
+ switch (req->type) {
+ case PIANO_REQUEST_LOGIN: {
+ /* authenticate user */
+ PianoRequestDataLogin_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ switch (reqData->step) {
+ case 0: {
+ char *cryptedTimestamp = NULL;
+
+ assert (req->responseData != NULL);
+
+ /* abusing parseNarrative; has same xml structure */
+ ret = PianoXmlParseNarrative (req->responseData, &cryptedTimestamp);
+ if (cryptedTimestamp != NULL) {
+ unsigned long timestamp = 0;
+ time_t realTimestamp = time (NULL);
+ char *decryptedTimestamp = NULL, *decryptedPos = NULL;
+ unsigned char i = 4;
+
+ if ((decryptedTimestamp = PianoDecryptString (cryptedTimestamp)) != NULL) {
+ decryptedPos = decryptedTimestamp;
+ /* skip four bytes garbage? at beginning */
+ while (i-- > 0 && *decryptedPos++ != '\0');
+ timestamp = strtoul (decryptedPos, NULL, 0);
+ ph->timeOffset = realTimestamp - timestamp;
+
+ free (decryptedTimestamp);
+ }
+ free (cryptedTimestamp);
+ }
+ ret = PIANO_RET_CONTINUE_REQUEST;
+ ++reqData->step;
+ break;
+ }
+
+ case 1:
+ /* information exists when reauthenticating, destroy to
+ * avoid memleak */
+ if (ph->user.listenerId != NULL) {
+ PianoDestroyUserInfo (&ph->user);
+ }
+ ret = PianoXmlParseUserinfo (ph, req->responseData);
+ break;
+ }
+ break;
+ }
+
+ case PIANO_REQUEST_GET_STATIONS:
+ /* get stations */
+ assert (req->responseData != NULL);
+
+ ret = PianoXmlParseStations (ph, req->responseData);
+ break;
+
+ case PIANO_REQUEST_GET_PLAYLIST: {
+ /* get playlist, usually four songs */
+ PianoRequestDataGetPlaylist_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ reqData->retPlaylist = NULL;
+ ret = PianoXmlParsePlaylist (ph, req->responseData,
+ &reqData->retPlaylist);
+ break;
+ }
+
+ 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;
+ }
+ break;
+
+ case PIANO_REQUEST_ADD_FEEDBACK:
+ /* never ever use this directly, low-level call */
+ assert (0);
+ break;
+
+ case PIANO_REQUEST_MOVE_SONG: {
+ /* move song to different station */
+ PianoRequestDataMoveSong_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+ assert (reqData->step < 2);
+
+ ret = PianoXmlParseSimple (req->responseData);
+ if (ret == PIANO_RET_OK && 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;
+
+ 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_DELETE_STATION:
+ /* delete station from server and station list */
+ assert (req->responseData != NULL);
+
+ if ((ret = PianoXmlParseSimple (req->responseData)) == PIANO_RET_OK) {
+ PianoStation_t *station = req->data;
+
+ assert (station != NULL);
+
+ /* 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;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ ret = PianoXmlParseSearch (req->responseData, &reqData->searchResult);
+ break;
+ }
+
+ case PIANO_REQUEST_CREATE_STATION: {
+ /* create station, insert new station into station list on success */
+ assert (req->responseData != NULL);
+
+ ret = PianoXmlParseCreateStation (ph, req->responseData);
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_SEED: {
+ /* add seed to station, updates station structure */
+ PianoRequestDataAddSeed_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+ assert (reqData->station != NULL);
+
+ /* FIXME: update station data instead of replacing them */
+ ret = PianoXmlParseAddSeed (ph, req->responseData, reqData->station);
+ break;
+ }
+
+ case PIANO_REQUEST_ADD_TIRED_SONG:
+ case PIANO_REQUEST_SET_QUICKMIX:
+ case PIANO_REQUEST_BOOKMARK_SONG:
+ case PIANO_REQUEST_BOOKMARK_ARTIST:
+ assert (req->responseData != NULL);
+
+ ret = PianoXmlParseSimple (req->responseData);
+ break;
+
+ case PIANO_REQUEST_GET_GENRE_STATIONS:
+ /* get genre stations */
+ assert (req->responseData != NULL);
+
+ ret = PianoXmlParseGenreExplorer (ph, req->responseData);
+ break;
+
+ case PIANO_REQUEST_TRANSFORM_STATION: {
+ /* transform shared station into private and update isCreator flag */
+ PianoStation_t *station = req->data;
+
+ 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;
+ }
+ break;
+ }
+
+ case PIANO_REQUEST_EXPLAIN: {
+ /* explain why song was selected */
+ PianoRequestDataExplain_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ ret = PianoXmlParseNarrative (req->responseData, &reqData->retExplain);
+ break;
+ }
+
+ case PIANO_REQUEST_GET_SEED_SUGGESTIONS: {
+ /* find similar artists */
+ PianoRequestDataGetSeedSuggestions_t *reqData = req->data;
+
+ assert (req->responseData != NULL);
+ assert (reqData != NULL);
+
+ ret = PianoXmlParseSeedSuggestions (req->responseData,
+ &reqData->searchResult);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/* get station from list by id
+ * @param search here
+ * @param search for this
+ * @return the first station structure matching the given id
+ */
+PianoStation_t *PianoFindStationById (PianoStation_t *stations,
+ const char *searchStation) {
+ while (stations != NULL) {
+ if (strcmp (stations->id, searchStation) == 0) {
+ return stations;
+ }
+ stations = stations->next;
+ }
+ return NULL;
+}
+
+/* convert return value to human-readable string
+ * @param enum
+ * @return error string
+ */
+const char *PianoErrorToStr (PianoReturn_t ret) {
+ switch (ret) {
+ case PIANO_RET_OK:
+ return "Everything is fine :)";
+ break;
+
+ case PIANO_RET_ERR:
+ 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 ".";
+ 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.";
+ break;
+
+ case PIANO_RET_IP_REJECTED:
+ return "Your ip address was rejected. Please setup a control "
+ "proxy (see manpage).";
+ break;
+
+ case PIANO_RET_STATION_NONEXISTENT:
+ return "Station does not exist.";
+ break;
+
+ case PIANO_RET_OUT_OF_MEMORY:
+ return "Out of memory.";
+ break;
+
+ case PIANO_RET_OUT_OF_SYNC:
+ return "Out of sync. Please correct your system's time.";
+ break;
+
+ case PIANO_RET_PLAYLIST_END:
+ return "Playlist end.";
+ break;
+
+ case PIANO_RET_QUICKMIX_NOT_PLAYABLE:
+ return "Quickmix not playable.";
+ break;
+
+ default:
+ return "No error message available.";
+ break;
+ }
+}
+
diff --git a/src/libpiano/piano.h b/src/libpiano/piano.h
new file mode 100644
index 0000000..ef6e386
--- /dev/null
+++ b/src/libpiano/piano.h
@@ -0,0 +1,244 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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 _PIANO_H
+#define _PIANO_H
+
+/* this is our public API; don't expect this api to be stable as long as
+ * pandora does not provide a stable api
+ * 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"
+
+typedef struct PianoUserInfo {
+ char *webAuthToken;
+ char *listenerId;
+ char *authToken;
+} PianoUserInfo_t;
+
+typedef struct PianoStation {
+ char isCreator;
+ char isQuickMix;
+ char useQuickMix; /* station will be included in quickmix */
+ char *name;
+ char *id;
+ struct PianoStation *next;
+} PianoStation_t;
+
+typedef enum {
+ PIANO_RATE_NONE = 0,
+ PIANO_RATE_LOVE = 1,
+ PIANO_RATE_BAN = 2
+} PianoSongRating_t;
+
+/* UNKNOWN should be 0, because memset sets audio format to 0 */
+typedef enum {
+ PIANO_AF_UNKNOWN = 0,
+ PIANO_AF_AACPLUS = 1,
+ PIANO_AF_MP3 = 2,
+ PIANO_AF_MP3_HI = 3
+} PianoAudioFormat_t;
+
+typedef struct PianoSong {
+ char *artist;
+ char *artistMusicId;
+ char *stationId;
+ char *album;
+ char *userSeed;
+ char *audioUrl;
+ char *coverArt;
+ char *musicId;
+ char *title;
+ float fileGain;
+ PianoSongRating_t rating;
+ PianoAudioFormat_t audioFormat;
+ int testStrategy;
+ unsigned int songType;
+ struct PianoSong *next;
+} PianoSong_t;
+
+/* currently only used for search results */
+typedef struct PianoArtist {
+ char *name;
+ char *musicId;
+ int score;
+ struct PianoArtist *next;
+} PianoArtist_t;
+
+typedef struct PianoGenre {
+ char *name;
+ char *musicId;
+ struct PianoGenre *next;
+} PianoGenre_t;
+
+typedef struct PianoGenreCategory {
+ char *name;
+ PianoGenre_t *genres;
+ struct PianoGenreCategory *next;
+} PianoGenreCategory_t;
+
+typedef struct PianoHandle {
+ char routeId[9];
+ PianoUserInfo_t user;
+ /* linked lists */
+ PianoStation_t *stations;
+ PianoGenreCategory_t *genreStations;
+ int timeOffset;
+} PianoHandle_t;
+
+typedef struct PianoSearchResult {
+ PianoSong_t *songs;
+ PianoArtist_t *artists;
+} PianoSearchResult_t;
+
+typedef enum {
+ /* 0 is reserved: memset (x, 0, sizeof (x)) */
+ PIANO_REQUEST_LOGIN = 1,
+ PIANO_REQUEST_GET_STATIONS = 2,
+ PIANO_REQUEST_GET_PLAYLIST = 3,
+ PIANO_REQUEST_RATE_SONG = 4,
+ PIANO_REQUEST_ADD_FEEDBACK = 5,
+ PIANO_REQUEST_MOVE_SONG = 6,
+ PIANO_REQUEST_RENAME_STATION = 7,
+ PIANO_REQUEST_DELETE_STATION = 8,
+ PIANO_REQUEST_SEARCH = 9,
+ PIANO_REQUEST_CREATE_STATION = 10,
+ PIANO_REQUEST_ADD_SEED = 11,
+ PIANO_REQUEST_ADD_TIRED_SONG = 12,
+ PIANO_REQUEST_SET_QUICKMIX = 13,
+ PIANO_REQUEST_GET_GENRE_STATIONS = 14,
+ PIANO_REQUEST_TRANSFORM_STATION = 15,
+ PIANO_REQUEST_EXPLAIN = 16,
+ PIANO_REQUEST_GET_SEED_SUGGESTIONS = 17,
+ PIANO_REQUEST_BOOKMARK_SONG = 18,
+ PIANO_REQUEST_BOOKMARK_ARTIST = 19,
+} PianoRequestType_t;
+
+typedef struct PianoRequest {
+ PianoRequestType_t type;
+ void *data;
+ char urlPath[1024];
+ char *postData;
+ char *responseData;
+} PianoRequest_t;
+
+/* request data structures */
+typedef struct {
+ char *user;
+ char *password;
+ unsigned char step;
+} PianoRequestDataLogin_t;
+
+typedef struct {
+ PianoStation_t *station;
+ PianoAudioFormat_t format;
+ PianoSong_t *retPlaylist;
+} PianoRequestDataGetPlaylist_t;
+
+typedef struct {
+ PianoSong_t *song;
+ PianoSongRating_t rating;
+} PianoRequestDataRateSong_t;
+
+typedef struct {
+ char *stationId;
+ char *musicId;
+ char *userSeed;
+ PianoSongRating_t rating;
+ unsigned int testStrategy;
+ unsigned int songType;
+} PianoRequestDataAddFeedback_t;
+
+typedef struct {
+ PianoSong_t *song;
+ PianoStation_t *from;
+ PianoStation_t *to;
+ unsigned short step;
+} PianoRequestDataMoveSong_t;
+
+typedef struct {
+ PianoStation_t *station;
+ char *newName;
+} PianoRequestDataRenameStation_t;
+
+typedef struct {
+ char *searchStr;
+ PianoSearchResult_t searchResult;
+} PianoRequestDataSearch_t;
+
+typedef struct {
+ char *type;
+ char *id;
+} PianoRequestDataCreateStation_t;
+
+typedef struct {
+ PianoStation_t *station;
+ char *musicId;
+} PianoRequestDataAddSeed_t;
+
+typedef struct {
+ PianoSong_t *song;
+ char *retExplain;
+} PianoRequestDataExplain_t;
+
+typedef struct {
+ char *musicId;
+ unsigned short max;
+ PianoSearchResult_t searchResult;
+} PianoRequestDataGetSeedSuggestions_t;
+
+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,
+} PianoReturn_t;
+
+void PianoInit (PianoHandle_t *);
+void PianoDestroy (PianoHandle_t *);
+void PianoDestroyPlaylist (PianoSong_t *);
+void PianoDestroySearchResult (PianoSearchResult_t *);
+
+PianoReturn_t PianoRequest (PianoHandle_t *, PianoRequest_t *,
+ PianoRequestType_t);
+PianoReturn_t PianoResponse (PianoHandle_t *, PianoRequest_t *);
+void PianoDestroyRequest (PianoRequest_t *);
+
+PianoStation_t *PianoFindStationById (PianoStation_t *, const char *);
+const char *PianoErrorToStr (PianoReturn_t);
+
+#endif /* _PIANO_H */
diff --git a/src/libpiano/piano_private.h b/src/libpiano/piano_private.h
new file mode 100644
index 0000000..bb14c8f
--- /dev/null
+++ b/src/libpiano/piano_private.h
@@ -0,0 +1,31 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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 _MAIN_H
+#define _MAIN_H
+
+#include "piano.h"
+
+void PianoDestroyStation (PianoStation_t *station);
+
+#endif /* _MAIN_H */
diff --git a/src/libpiano/xml.c b/src/libpiano/xml.c
new file mode 100644
index 0000000..01294d8
--- /dev/null
+++ b/src/libpiano/xml.c
@@ -0,0 +1,826 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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.
+*/
+
+#define _BSD_SOURCE /* required by strdup() */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ezxml.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 {
+ *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) {
+ 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 ("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 ("testStrategy", key) == 0) {
+ song->testStrategy = atoi (valueStr);
+ } else if (strcmp ("songType", key) == 0) {
+ song->songType = atoi (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;
+}
+
+/* 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;
+
+ 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) {
+ PianoSong_t *tmpSong;
+
+ if ((tmpSong = calloc (1, sizeof (*tmpSong))) == NULL) {
+ ezxml_free (xmlDoc);
+ return PIANO_RET_OUT_OF_MEMORY;
+ }
+
+ PianoXmlStructParser (ezxml_child (dataNode, "struct"),
+ PianoXmlParsePlaylistCb, tmpSong);
+ /* begin linked list or append */
+ if (*retPlaylist == NULL) {
+ *retPlaylist = tmpSong;
+ } else {
+ PianoSong_t *curSong = *retPlaylist;
+ while (curSong->next != NULL) {
+ curSong = curSong->next;
+ }
+ curSong->next = tmpSong;
+ }
+ }
+
+ ezxml_free (xmlDoc);
+
+ return PIANO_RET_OK;
+}
+
+/* parse simple answers like this: <?xml version="1.0" encoding="UTF-8"?>
+ * <methodResponse><params><param><value>1</value></param></params>
+ * </methodResponse>
+ * @param xml string
+ * @return
+ */
+PianoReturn_t PianoXmlParseSimple (char *xml) {
+ 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);
+
+ if (strcmp (ezxml_txt (dataNode), "1") == 0) {
+ ret = PIANO_RET_OK;
+ } else {
+ ret = PIANO_RET_ERR;
+ }
+
+ 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) {
+ /* FIXME: copy & waste */
+ PianoSong_t *tmpSong;
+
+ if ((tmpSong = calloc (1, sizeof (*tmpSong))) == NULL) {
+ /* fail silently */
+ break;
+ }
+
+ PianoXmlStructParser (ezxml_child (curNode, "struct"),
+ PianoXmlParsePlaylistCb, tmpSong);
+ /* begin linked list or append */
+ if (searchResult->songs == NULL) {
+ searchResult->songs = tmpSong;
+ } else {
+ PianoSong_t *curSong = searchResult->songs;
+ while (curSong->next != NULL) {
+ curSong = curSong->next;
+ }
+ curSong->next = tmpSong;
+ }
+ }
+ }
+}
+
+/* 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) {
+ char *replacements[] = {"&&amp;", "'&apos;", "\"&quot;", "<&lt;",
+ ">&gt;", NULL};
+ char **r, *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;
+}
+
diff --git a/src/libpiano/xml.h b/src/libpiano/xml.h
new file mode 100644
index 0000000..dc778ca
--- /dev/null
+++ b/src/libpiano/xml.h
@@ -0,0 +1,48 @@
+/*
+Copyright (c) 2008-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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, const char *xml);
+PianoReturn_t PianoXmlParseStations (PianoHandle_t *ph, const char *xml);
+PianoReturn_t PianoXmlParsePlaylist (PianoHandle_t *ph, const char *xml,
+ PianoSong_t **);
+PianoReturn_t PianoXmlParseSearch (const char *searchXml,
+ PianoSearchResult_t *searchResult);
+PianoReturn_t PianoXmlParseSimple (const char *xml);
+PianoReturn_t PianoXmlParseCreateStation (PianoHandle_t *ph,
+ const char *xml);
+PianoReturn_t PianoXmlParseAddSeed (PianoHandle_t *ph, const char *xml,
+ PianoStation_t *station);
+PianoReturn_t PianoXmlParseGenreExplorer (PianoHandle_t *ph,
+ const char *xmlContent);
+PianoReturn_t PianoXmlParseTranformStation (const char *searchXml);
+PianoReturn_t PianoXmlParseNarrative (const char *xml, char **retNarrative);
+PianoReturn_t PianoXmlParseSeedSuggestions (char *, PianoSearchResult_t *);
+
+char *PianoXmlEncodeString (const char *s);
+
+#endif /* _XML_H */
diff --git a/src/libwaitress/config.h b/src/libwaitress/config.h
new file mode 100644
index 0000000..e13fe40
--- /dev/null
+++ b/src/libwaitress/config.h
@@ -0,0 +1 @@
+#define PACKAGE "libwaitress"
diff --git a/src/libwaitress/waitress.c b/src/libwaitress/waitress.c
new file mode 100644
index 0000000..27f7b3d
--- /dev/null
+++ b/src/libwaitress/waitress.c
@@ -0,0 +1,573 @@
+/*
+Copyright (c) 2009-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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.
+*/
+
+#define _POSIX_C_SOURCE 1 /* required by getaddrinfo() */
+#define _BSD_SOURCE /* snprintf() */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <errno.h>
+
+#include "config.h"
+#include "waitress.h"
+
+typedef struct {
+ char *buf;
+ size_t pos;
+} WaitressFetchBufCbBuffer_t;
+
+inline void WaitressInit (WaitressHandle_t *waith) {
+ memset (waith, 0, sizeof (*waith));
+ waith->socktimeout = 30000;
+}
+
+inline void WaitressFree (WaitressHandle_t *waith) {
+ memset (waith, 0, sizeof (*waith));
+}
+
+/* Proxy set up?
+ * @param Waitress handle
+ * @return true|false
+ */
+static inline char WaitressProxyEnabled (const WaitressHandle_t *waith) {
+ return *waith->proxyHost != '\0' && *waith->proxyPort != '\0';
+}
+
+/* Set http proxy
+ * @param waitress handle
+ * @param host
+ * @param port
+ */
+inline void WaitressSetProxy (WaitressHandle_t *waith, const char *host,
+ const char *port) {
+ strncpy (waith->proxyHost, host, sizeof (waith->proxyHost)-1);
+ strncpy (waith->proxyPort, port, sizeof (waith->proxyPort)-1);
+}
+
+/* urlencode post-data
+ * @param encode this
+ * @return malloc'ed encoded string, don't forget to free it
+ */
+char *WaitressUrlEncode (const char *in) {
+ size_t inLen = strlen (in);
+ /* worst case: encode all characters */
+ char *out = calloc (inLen * 3 + 1, sizeof (*in));
+ const char *inPos = in;
+ char *outPos = out;
+
+ while (inPos - in < inLen) {
+ if (!isalnum (*inPos) && *inPos != '_' && *inPos != '-' && *inPos != '.') {
+ *outPos++ = '%';
+ snprintf (outPos, 3, "%02x", *inPos & 0xff);
+ outPos += 2;
+ } else {
+ /* copy character */
+ *outPos++ = *inPos;
+ }
+ ++inPos;
+ }
+
+ return out;
+}
+
+/* Split http url into host, port and path
+ * @param url
+ * @param return buffer: host
+ * @param host buffer size
+ * @param return buffer: port, defaults to 80
+ * @param port buffer size
+ * @param return buffer: path
+ * @param path buffer size
+ * @param 1 = ok, 0 = not a http url; if your buffers are too small horrible
+ * things will happen... But 1 is returned anyway.
+ */
+char WaitressSplitUrl (const char *url, char *retHost, size_t retHostSize,
+ char *retPort, size_t retPortSize, char *retPath, size_t retPathSize) {
+ size_t urlSize = strlen (url);
+ const char *urlPos = url, *lastPos;
+
+ if (urlSize > sizeof ("http://")-1 &&
+ memcmp (url, "http://", sizeof ("http://")-1) == 0) {
+ memset (retHost, 0, retHostSize);
+ memset (retPort, 0, retPortSize);
+ strncpy (retPort, "80", retPortSize-1);
+ memset (retPath, 0, retPathSize);
+
+ urlPos += sizeof ("http://")-1;
+ lastPos = urlPos;
+
+ /* find host */
+ while (*urlPos != '\0' && *urlPos != ':' && *urlPos != '/' &&
+ urlPos - lastPos < retHostSize-1) {
+ *retHost++ = *urlPos++;
+ }
+ lastPos = urlPos;
+
+ /* port, if available */
+ if (*urlPos == ':') {
+ /* skip : */
+ ++urlPos;
+ ++lastPos;
+ while (*urlPos != '\0' && *urlPos != '/' &&
+ urlPos - lastPos < retPortSize-1) {
+ *retPort++ = *urlPos++;
+ }
+ }
+ lastPos = urlPos;
+
+ /* path */
+ while (*urlPos != '\0' && *urlPos != '#' &&
+ urlPos - lastPos < retPathSize-1) {
+ *retPath++ = *urlPos++;
+ }
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+/* Parse url and set host, port, path
+ * @param Waitress handle
+ * @param url: protocol://host:port/path
+ */
+inline char WaitressSetUrl (WaitressHandle_t *waith, const char *url) {
+ return WaitressSplitUrl (url, waith->host, sizeof (waith->host),
+ waith->port, sizeof (waith->port), waith->path, sizeof (waith->path));
+}
+
+/* Set host, port, path
+ * @param Waitress handle
+ * @param host
+ * @param port (getaddrinfo () needs a string...)
+ * @param path, including leading /
+ */
+inline void WaitressSetHPP (WaitressHandle_t *waith, const char *host,
+ const char *port, const char *path) {
+ strncpy (waith->host, host, sizeof (waith->host)-1);
+ strncpy (waith->port, port, sizeof (waith->port)-1);
+ strncpy (waith->path, path, sizeof (waith->path)-1);
+}
+
+/* Callback for WaitressFetchBuf, appends received data to NULL-terminated buffer
+ * @param received data
+ * @param data size
+ * @param buffer structure
+ */
+static WaitressCbReturn_t WaitressFetchBufCb (void *recvData, size_t recvDataSize,
+ void *extraData) {
+ char *recvBytes = recvData;
+ WaitressFetchBufCbBuffer_t *buffer = extraData;
+
+ if (buffer->buf == NULL) {
+ if ((buffer->buf = malloc (sizeof (*buffer->buf) *
+ (recvDataSize + 1))) == NULL) {
+ return WAITRESS_CB_RET_ERR;
+ }
+ } else {
+ char *newbuf;
+ if ((newbuf = realloc (buffer->buf,
+ sizeof (*buffer->buf) *
+ (buffer->pos + recvDataSize + 1))) == NULL) {
+ free (buffer->buf);
+ return WAITRESS_CB_RET_ERR;
+ }
+ buffer->buf = newbuf;
+ }
+ memcpy (buffer->buf + buffer->pos, recvBytes, recvDataSize);
+ buffer->pos += recvDataSize;
+ *(buffer->buf+buffer->pos) = '\0';
+
+ return WAITRESS_CB_RET_OK;
+}
+
+/* Fetch string. Beware! This overwrites your waith->data pointer
+ * @param waitress handle
+ * @param result buffer, malloced (don't forget to free it yourself)
+ */
+WaitressReturn_t WaitressFetchBuf (WaitressHandle_t *waith, char **buf) {
+ WaitressFetchBufCbBuffer_t buffer;
+ WaitressReturn_t wRet;
+
+ memset (&buffer, 0, sizeof (buffer));
+
+ waith->data = &buffer;
+ waith->callback = WaitressFetchBufCb;
+
+ wRet = WaitressFetchCall (waith);
+ *buf = buffer.buf;
+ return wRet;
+}
+
+/* poll wrapper that retries after signal interrupts, required for socksify
+ * wrapper
+ */
+static int WaitressPollLoop (struct pollfd *fds, nfds_t nfds, int timeout) {
+ int pollres = -1;
+ int pollerr = 0;
+
+ do {
+ pollres = poll (fds, nfds, timeout);
+ pollerr = errno;
+ errno = 0;
+ } while (pollerr == EINTR || pollerr == EINPROGRESS || pollerr == EAGAIN);
+
+ return pollres;
+}
+
+/* write () wrapper with poll () timeout
+ * @param socket fd
+ * @param write buffer
+ * @param write count bytes
+ * @param reuse existing pollfd structure
+ * @param timeout (microseconds)
+ * @return WAITRESS_RET_OK, WAITRESS_RET_TIMEOUT or WAITRESS_RET_ERR
+ */
+static WaitressReturn_t WaitressPollWrite (int sockfd, const char *buf, size_t count,
+ struct pollfd *sockpoll, int timeout) {
+ int pollres = -1;
+
+ sockpoll->events = POLLOUT;
+ pollres = WaitressPollLoop (sockpoll, 1, timeout);
+ if (pollres == 0) {
+ return WAITRESS_RET_TIMEOUT;
+ } else if (pollres == -1) {
+ return WAITRESS_RET_ERR;
+ }
+ if (write (sockfd, buf, count) == -1) {
+ return WAITRESS_RET_ERR;
+ }
+ return WAITRESS_RET_OK;
+}
+
+/* read () wrapper with poll () timeout
+ * @param socket fd
+ * @param write to this buf, not NULL terminated
+ * @param buffer size
+ * @param reuse existing pollfd struct
+ * @param timeout (in microseconds)
+ * @param read () return value/written bytes
+ * @return WAITRESS_RET_OK, WAITRESS_RET_TIMEOUT, WAITRESS_RET_ERR
+ */
+static WaitressReturn_t WaitressPollRead (int sockfd, char *buf, size_t count,
+ struct pollfd *sockpoll, int timeout, ssize_t *retSize) {
+ int pollres = -1;
+
+ sockpoll->events = POLLIN;
+ pollres = WaitressPollLoop (sockpoll, 1, timeout);
+ if (pollres == 0) {
+ return WAITRESS_RET_TIMEOUT;
+ } else if (pollres == -1) {
+ return WAITRESS_RET_ERR;
+ }
+ if ((*retSize = read (sockfd, buf, count)) == -1) {
+ return WAITRESS_RET_READ_ERR;
+ }
+ return WAITRESS_RET_OK;
+}
+
+/* FIXME: compiler macros are ugly... */
+#define CLOSE_RET(ret) close (sockfd); return ret;
+#define WRITE_RET(buf, count) \
+ if ((wRet = WaitressPollWrite (sockfd, buf, count, \
+ &sockpoll, waith->socktimeout)) != WAITRESS_RET_OK) { \
+ CLOSE_RET (wRet); \
+ }
+#define READ_RET(buf, count, size) \
+ if ((wRet = WaitressPollRead (sockfd, buf, count, \
+ &sockpoll, waith->socktimeout, size)) != WAITRESS_RET_OK) { \
+ CLOSE_RET (wRet); \
+ }
+
+/* Receive data from host and call *callback ()
+ * @param waitress handle
+ * @return WaitressReturn_t
+ */
+WaitressReturn_t WaitressFetchCall (WaitressHandle_t *waith) {
+ struct addrinfo hints, *res;
+ int sockfd;
+ char recvBuf[WAITRESS_RECV_BUFFER];
+ char writeBuf[2*1024];
+ ssize_t recvSize = 0;
+ WaitressReturn_t wRet = WAITRESS_RET_OK;
+ struct pollfd sockpoll;
+ int pollres;
+ /* header parser vars */
+ char *nextLine = NULL, *thisLine = NULL;
+ enum {HDRM_HEAD, HDRM_LINES, HDRM_FINISHED} hdrParseMode = HDRM_HEAD;
+ char statusCode[3], val[256];
+ unsigned int bufFilled = 0;
+
+ /* initialize */
+ waith->contentLength = 0;
+ waith->contentReceived = 0;
+ memset (&hints, 0, sizeof hints);
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* Use proxy? */
+ if (WaitressProxyEnabled (waith)) {
+ if (getaddrinfo (waith->proxyHost, waith->proxyPort, &hints, &res) != 0) {
+ return WAITRESS_RET_GETADDR_ERR;
+ }
+ } else {
+ if (getaddrinfo (waith->host, waith->port, &hints, &res) != 0) {
+ return WAITRESS_RET_GETADDR_ERR;
+ }
+ }
+
+ if ((sockfd = socket (res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
+ freeaddrinfo (res);
+ return WAITRESS_RET_SOCK_ERR;
+ }
+ sockpoll.fd = sockfd;
+
+ /* we need shorter timeouts for connect() */
+ fcntl (sockfd, F_SETFL, O_NONBLOCK);
+
+ /* increase socket receive buffer */
+ const int sockopt = 256*1024;
+ setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof (sockopt));
+
+ /* non-blocking connect will return immediately */
+ connect (sockfd, res->ai_addr, res->ai_addrlen);
+
+ sockpoll.events = POLLOUT;
+ pollres = WaitressPollLoop (&sockpoll, 1, waith->socktimeout);
+ freeaddrinfo (res);
+ if (pollres == 0) {
+ return WAITRESS_RET_TIMEOUT;
+ } else if (pollres == -1) {
+ return WAITRESS_RET_ERR;
+ }
+ /* check connect () return value */
+ socklen_t pollresSize = sizeof (pollres);
+ getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &pollres, &pollresSize);
+ if (pollres != 0) {
+ return WAITRESS_RET_CONNECT_REFUSED;
+ }
+
+ /* send request */
+ if (WaitressProxyEnabled (waith)) {
+ snprintf (writeBuf, sizeof (writeBuf),
+ "%s http://%s:%s%s HTTP/1.0\r\n",
+ (waith->method == WAITRESS_METHOD_GET ? "GET" : "POST"),
+ waith->host, waith->port, waith->path);
+ } else {
+ snprintf (writeBuf, sizeof (writeBuf),
+ "%s %s HTTP/1.0\r\n",
+ (waith->method == WAITRESS_METHOD_GET ? "GET" : "POST"),
+ waith->path);
+ }
+ WRITE_RET (writeBuf, strlen (writeBuf));
+
+ snprintf (writeBuf, sizeof (writeBuf),
+ "Host: %s\r\nUser-Agent: " PACKAGE "\r\n", waith->host);
+ WRITE_RET (writeBuf, strlen (writeBuf));
+
+ if (waith->method == WAITRESS_METHOD_POST && waith->postData != NULL) {
+ snprintf (writeBuf, sizeof (writeBuf), "Content-Length: %zu\r\n",
+ strlen (waith->postData));
+ WRITE_RET (writeBuf, strlen (writeBuf));
+ }
+
+ if (waith->extraHeaders != NULL) {
+ WRITE_RET (waith->extraHeaders, strlen (waith->extraHeaders));
+ }
+
+ WRITE_RET ("\r\n", 2);
+
+ if (waith->method == WAITRESS_METHOD_POST && waith->postData != NULL) {
+ WRITE_RET (waith->postData, strlen (waith->postData));
+ }
+
+ /* receive answer */
+ nextLine = recvBuf;
+ while (hdrParseMode != HDRM_FINISHED) {
+ READ_RET (recvBuf+bufFilled, sizeof (recvBuf)-1 - bufFilled, &recvSize);
+ if (recvSize == 0) {
+ /* connection closed too early */
+ CLOSE_RET (WAITRESS_RET_CONNECTION_CLOSED);
+ }
+ bufFilled += recvSize;
+ memset (recvBuf+bufFilled, 0, sizeof (recvBuf) - bufFilled);
+ thisLine = recvBuf;
+
+ /* split */
+ while ((nextLine = strchr (thisLine, '\n')) != NULL &&
+ hdrParseMode != HDRM_FINISHED) {
+ /* make lines parseable by string routines */
+ *nextLine = '\0';
+ if (*(nextLine-1) == '\r') {
+ *(nextLine-1) = '\0';
+ }
+ /* skip \0 */
+ ++nextLine;
+
+ switch (hdrParseMode) {
+ /* Status code */
+ case HDRM_HEAD:
+ if (sscanf (thisLine, "HTTP/1.%*1[0-9] %3[0-9] ",
+ statusCode) == 1) {
+ if (memcmp (statusCode, "200", 3) == 0 ||
+ memcmp (statusCode, "206", 3) == 0) {
+ /* everything's fine... */
+ } else if (memcmp (statusCode, "403", 3) == 0) {
+ CLOSE_RET (WAITRESS_RET_FORBIDDEN);
+ } else if (memcmp (statusCode, "404", 3) == 0) {
+ CLOSE_RET (WAITRESS_RET_NOTFOUND);
+ } else {
+ CLOSE_RET (WAITRESS_RET_STATUS_UNKNOWN);
+ }
+ hdrParseMode = HDRM_LINES;
+ } /* endif */
+ break;
+
+ /* Everything else, except status code */
+ case HDRM_LINES:
+ /* empty line => content starts here */
+ if (*thisLine == '\0') {
+ hdrParseMode = HDRM_FINISHED;
+ } else {
+ memset (val, 0, sizeof (val));
+ if (sscanf (thisLine, "Content-Length: %255c", val) == 1) {
+ waith->contentLength = atol (val);
+ }
+ }
+ break;
+
+ default:
+ break;
+ } /* end switch */
+ thisLine = nextLine;
+ } /* end while strchr */
+ memmove (recvBuf, thisLine, thisLine-recvBuf);
+ bufFilled -= (thisLine-recvBuf);
+ } /* end while hdrParseMode */
+
+ /* push remaining bytes */
+ if (bufFilled > 0) {
+ waith->contentReceived += bufFilled;
+ if (waith->callback (thisLine, bufFilled, waith->data) ==
+ WAITRESS_CB_RET_ERR) {
+ CLOSE_RET (WAITRESS_RET_CB_ABORT);
+ }
+ }
+
+ /* receive content */
+ do {
+ READ_RET (recvBuf, sizeof (recvBuf), &recvSize);
+ if (recvSize > 0) {
+ waith->contentReceived += recvSize;
+ if (waith->callback (recvBuf, recvSize, waith->data) ==
+ WAITRESS_CB_RET_ERR) {
+ wRet = WAITRESS_RET_CB_ABORT;
+ break;
+ }
+ }
+ } while (recvSize > 0);
+
+ close (sockfd);
+
+ if (wRet == WAITRESS_RET_OK && waith->contentReceived < waith->contentLength) {
+ return WAITRESS_RET_PARTIAL_FILE;
+ }
+ return wRet;
+}
+
+#undef CLOSE_RET
+#undef WRITE_RET
+#undef READ_RET
+
+const char *WaitressErrorToStr (WaitressReturn_t wRet) {
+ switch (wRet) {
+ case WAITRESS_RET_OK:
+ return "Everything's fine :)";
+ break;
+
+ case WAITRESS_RET_ERR:
+ return "Unknown.";
+ break;
+
+ case WAITRESS_RET_STATUS_UNKNOWN:
+ return "Unknown HTTP status code.";
+ break;
+
+ case WAITRESS_RET_NOTFOUND:
+ return "File not found.";
+ break;
+
+ case WAITRESS_RET_FORBIDDEN:
+ return "Forbidden.";
+ break;
+
+ case WAITRESS_RET_CONNECT_REFUSED:
+ return "Connection refused.";
+ break;
+
+ case WAITRESS_RET_SOCK_ERR:
+ return "Socket error.";
+ break;
+
+ case WAITRESS_RET_GETADDR_ERR:
+ return "getaddr failed.";
+ break;
+
+ case WAITRESS_RET_CB_ABORT:
+ return "Callback aborted request.";
+ break;
+
+ case WAITRESS_RET_HDR_OVERFLOW:
+ return "HTTP header overflow.";
+ break;
+
+ case WAITRESS_RET_PARTIAL_FILE:
+ return "Partial file.";
+ break;
+
+ case WAITRESS_RET_TIMEOUT:
+ return "Timeout.";
+ break;
+
+ case WAITRESS_RET_READ_ERR:
+ return "Read error.";
+ break;
+
+ case WAITRESS_RET_CONNECTION_CLOSED:
+ return "Connection closed by remote host.";
+ break;
+
+ default:
+ return "No error message available.";
+ break;
+ }
+}
+
diff --git a/src/libwaitress/waitress.h b/src/libwaitress/waitress.h
new file mode 100644
index 0000000..04333fb
--- /dev/null
+++ b/src/libwaitress/waitress.h
@@ -0,0 +1,78 @@
+/*
+Copyright (c) 2009-2010
+ Lars-Dominik Braun <PromyLOPh@lavabit.com>
+
+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 _WAITRESS_H
+#define _WAITRESS_H
+
+#include <stdlib.h>
+
+#define WAITRESS_HOST_SIZE 100
+/* max: 65,535 */
+#define WAITRESS_PORT_SIZE 6
+#define WAITRESS_PATH_SIZE 1000
+#define WAITRESS_RECV_BUFFER 10*1024
+
+typedef enum {WAITRESS_METHOD_GET = 0, WAITRESS_METHOD_POST} WaitressMethod_t;
+
+typedef enum {WAITRESS_CB_RET_ERR, WAITRESS_CB_RET_OK} WaitressCbReturn_t;
+
+typedef struct {
+ char host[WAITRESS_HOST_SIZE];
+ char port[WAITRESS_PORT_SIZE];
+ char path[WAITRESS_PATH_SIZE];
+ WaitressMethod_t method;
+ const char *extraHeaders;
+ const char *postData;
+ size_t contentLength;
+ size_t contentReceived;
+ char proxyHost[WAITRESS_HOST_SIZE];
+ char proxyPort[WAITRESS_PORT_SIZE];
+ /* extra data handed over to callback function */
+ void *data;
+ WaitressCbReturn_t (*callback) (void *, size_t, void *);
+ int socktimeout;
+} WaitressHandle_t;
+
+typedef enum {WAITRESS_RET_ERR = 0, WAITRESS_RET_OK, WAITRESS_RET_STATUS_UNKNOWN,
+ WAITRESS_RET_NOTFOUND, WAITRESS_RET_FORBIDDEN, WAITRESS_RET_CONNECT_REFUSED,
+ WAITRESS_RET_SOCK_ERR, WAITRESS_RET_GETADDR_ERR,
+ WAITRESS_RET_CB_ABORT, WAITRESS_RET_HDR_OVERFLOW,
+ WAITRESS_RET_PARTIAL_FILE, WAITRESS_RET_TIMEOUT, WAITRESS_RET_READ_ERR,
+ WAITRESS_RET_CONNECTION_CLOSED
+} WaitressReturn_t;
+
+void WaitressInit (WaitressHandle_t *);
+void WaitressFree (WaitressHandle_t *);
+void WaitressSetProxy (WaitressHandle_t *, const char *, const char *);
+char *WaitressUrlEncode (const char *);
+char WaitressSplitUrl (const char *, char *, size_t, char *, size_t, char *,
+ size_t);
+char WaitressSetUrl (WaitressHandle_t *, const char *);
+void WaitressSetHPP (WaitressHandle_t *, const char *, const char *,
+ const char *);
+WaitressReturn_t WaitressFetchBuf (WaitressHandle_t *, char **);
+WaitressReturn_t WaitressFetchCall (WaitressHandle_t *);
+const char *WaitressErrorToStr (WaitressReturn_t);
+
+#endif /* _WAITRESS_H */
+
diff --git a/src/pianobar.1 b/src/pianobar.1
deleted file mode 100644
index 6a6bd33..0000000
--- a/src/pianobar.1
+++ /dev/null
@@ -1,227 +0,0 @@
-.TH pianobar 1
-
-.SH NAME
-pianobar \- console pandora.com music player
-
-.SH SYNOPSIS
-.B pianobar
-
-.SH DESCRIPTION
-.B pianobar
-is a lightweight console music player for the personalized online radio
-pandora.com.
-
-.SH FILES
-.I $XDG_CONFIG_HOME/pianobar/config
-or
-.I ~/.config/pianobar/config
-.RS
-Per-user configuration file. See
-.B CONFIGURATION.
-.RE
-
-.I /etc/libao.conf
-or
-.I ~/.libao
-.RS
-Global/per-user audio output configuration. See libao documentation at
-http://xiph.org/ao/doc/config.html
-.RE
-
-.SH CONFIGURATION
-The configuration file consists of simple
-.B key = value
-lines. Each terminated with a newline (\\n) character. Keys and values are both
-case sensitive. act_*-keys control
-.B pianobar's
-key-bindings.
-
-.TP
-.B act_help = ?
-Show keybindings.
-
-.TP
-.B act_songlove = +
-Love currently played song.
-
-.TP
-.B act_songban = -
-Ban current track. It will not be played again and can only removed using the
-pandora.com web interface.
-
-.TP
-.B act_stationaddmusic = a
-Add more music to current station. You will be asked for a search string. Just
-follow the instructions. If you're clueless try '?' (without quotes).
-
-.TP
-.B act_bookmark = b
-Bookmark current song or artist.
-
-.TP
-.B act_stationcreate = c
-Create new station. You have to enter a search string and select the song or
-artist of your choice.
-
-.TP
-.B act_stationdelete = d
-Delete current station.
-
-.TP
-.B act_songexplain = e
-Explain why this song is played.
-
-.TP
-.B act_stationaddbygenre = g
-Add genre station provided by pandora.
-
-.TP
-.B act_history = h
-Show history.
-
-.TP
-.B act_songinfo = i
-Print information about currently played song/station.
-
-.TP
-.B act_addshared = j
-Add shared station by id. id is a very long integer without "sh" at the
-beginning.
-
-.TP
-.B act_songmove = m
-Move current song to another station
-
-.TP
-.B act_songnext = n
-Skip current song.
-
-.TP
-.B act_songpause = p
-Pause/Continue
-
-.TP
-.B act_quit = q
-Quit
-.B pianobar.
-
-.TP
-.B act_stationrename = r
-Rename currently played station.
-
-.TP
-.B act_stationchange = s
-Select another station.
-
-.TP
-.B act_songtired = t
-Ban song for one month.
-
-.TP
-.B act_upcoming = u
-Show next songs in playlist.
-
-.TP
-.B act_stationselectquickmix = x
-Select quickmix stations.
-
-.TP
-.B audio_format = {aacplus,mp3,mp3-hifi}
-Select audio format. aacplus is default if both libraries (faad, mad) are
-available. mp3-hifi is available for Pandora One customers only.
-
-.TP
-.B autostart_station = stationid
-Play this station when starting up. You can get the
-.B stationid
-by pressing
-.B i
-or the key you defined in
-.B act_songinfo.
-
-.TP
-.B ban_icon = </3
-Icon for banned songs.
-
-.TP
-.B control_proxy = http://host:port/
-Non-american users need a proxy to use pandora.com. Only the xmlrpc interface
-will use this proxy. The music is streamed directly.
-
-.TP
-.B event_command = path
-File that is executed when event occurs. See section
-.B EVENTCMD
-
-.TP
-.B history = 5
-Keep a history of the last n songs (5, by default). You can rate these songs.
-
-.TP
-.B love_icon = <3
-Icon for loved songs.
-
-.TP
-.B password = plaintext_password
-Your pandora.com password. Plain-text.
-
-.TP
-.B proxy = http://host:port/
-Use a http proxy. Note that this setting overrides the http_proxy environment
-variable.
-
-.TP
-.B sort = {name_az, name_za, quickmix_01_name_az, quickmix_01_name_za, quickmix_10_name_az, quickmix_10_name_za}
-Sort station list by name or type (is quickmix) and name. name_az for example
-sorts by name from a to z, quickmix_01_name_za by type (quickmix at the
-bottom) and name from z to a.
-
-.TP
-.B user = your@user.name
-Your pandora.com username.
-
-.SH REMOTE CONTROL
-.B pianobar
-can be controlled through a fifo. You have to create it yourself by executing
-
- mkfifo ~/.config/pianobar/ctl
-
-Adjust the path if you set up a $XDG_CONFIG_HOME. Afterwards you can write
-commands directly into the fifo. Example (next song):
-
- echo -n 'n' > ~/.config/pianobar/ctl
-
-.B n
-is the keybinding for "next song". If you customized your keybindings you have to use these characters to control
-.B pianobar.
-.I This behaviour may change in the future!
-
-Another example:
-
- while true; do;
- nc -l -p 12345 -s localhost localhost > ~/.config/pianobar/ctl;
- sleep 1;
- done
-
- echo -ne 'n\\x1a' | nc -q 0 127.0.0.1 12345
-
-.SH EVENTCMD
-
-.B pianobar
-can report certain "events" to an external application (see
-.B CONFIGURATION
-). This application is started with the event name as it's first argument. More
-information (artist, title, album, stationName, error code, error description,
-song length in milliseconds, rating, album art url) is supplied through stdin.
-
-Currently supported events are: artistbookmark, songban, songbookmark,
-songexplain, songfinish, songlove, songmove, songshelf, songstart,
-stationaddmusic, stationaddshared, stationcreate, stationdelete,
-stationfetchplaylist, stationquickmixtoggle, stationrename
-
-An example script can be found in the contrib/ directory of
-.B pianobar's
-source distribution.
-
-.SH AUTHOR
-Lars-Dominik Braun <PromyLOPh@lavabit.com>