From d57bf656fd35eff82034dea5072014bffdc92f82 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Tue, 15 Dec 2009 20:44:40 +0100 Subject: Rename libraries' main.c files --- libpiano/src/CMakeLists.txt | 2 +- libpiano/src/crypt.c | 2 +- libpiano/src/http.c | 2 +- libpiano/src/main.c | 908 ----------------------------------------- libpiano/src/main.h | 32 -- libpiano/src/piano.c | 908 +++++++++++++++++++++++++++++++++++++++++ libpiano/src/piano_private.h | 32 ++ libpiano/src/xml.c | 2 +- libwaitress/src/CMakeLists.txt | 2 +- libwaitress/src/main.c | 481 ---------------------- libwaitress/src/waitress.c | 481 ++++++++++++++++++++++ libwardrobe/src/CMakeLists.txt | 2 +- libwardrobe/src/main.c | 261 ------------ libwardrobe/src/wardrobe.c | 261 ++++++++++++ src/main.c | 1 - 15 files changed, 1688 insertions(+), 1689 deletions(-) delete mode 100644 libpiano/src/main.c delete mode 100644 libpiano/src/main.h create mode 100644 libpiano/src/piano.c create mode 100644 libpiano/src/piano_private.h delete mode 100644 libwaitress/src/main.c create mode 100644 libwaitress/src/waitress.c delete mode 100644 libwardrobe/src/main.c create mode 100644 libwardrobe/src/wardrobe.c diff --git a/libpiano/src/CMakeLists.txt b/libpiano/src/CMakeLists.txt index 1344be2..8d1b77e 100644 --- a/libpiano/src/CMakeLists.txt +++ b/libpiano/src/CMakeLists.txt @@ -7,6 +7,6 @@ include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../libwaitress/src ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libezxml/src) -add_library (piano STATIC crypt.c http.c main.c xml.c) +add_library (piano STATIC crypt.c http.c piano.c xml.c) target_link_libraries (piano waitress ezxml) diff --git a/libpiano/src/crypt.c b/libpiano/src/crypt.c index 9b21516..9691aeb 100644 --- a/libpiano/src/crypt.c +++ b/libpiano/src/crypt.c @@ -28,7 +28,7 @@ THE SOFTWARE. #include "crypt_key_output.h" #include "crypt_key_input.h" -#include "main.h" +#include "piano_private.h" #define byteswap32(x) ((((x) >> 24) & 0x000000ff) | \ (((x) >> 8) & 0x0000ff00) | \ diff --git a/libpiano/src/http.c b/libpiano/src/http.c index 4af5ccc..10de2a5 100644 --- a/libpiano/src/http.c +++ b/libpiano/src/http.c @@ -27,7 +27,7 @@ THE SOFTWARE. #include -#include "main.h" +#include "piano_private.h" #include "crypt.h" #include "http.h" diff --git a/libpiano/src/main.c b/libpiano/src/main.c deleted file mode 100644 index 2bf8426..0000000 --- a/libpiano/src/main.c +++ /dev/null @@ -1,908 +0,0 @@ -/* -Copyright (c) 2008-2009 - Lars-Dominik Braun - -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 -#include -#include -#include - -#include "main.h" -#include "piano.h" -#include "http.h" -#include "xml.h" -#include "crypt.h" -#include "config.h" - -#define PIANO_PROTOCOL_VERSION "25" -#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 - -/* prototypes */ -static PianoReturn_t PianoAddFeedback (PianoHandle_t *, const char *, const char *, - const char *, const char *, const char *, PianoSongRating_t); -const char *PianoAudioFormatToString (PianoAudioFormat_t); - -/* more "secure" free version - * @param free this pointer - * @param zero n bytes; 0 disables zeroing (for strings with unknown size, - * e.g.) - */ -void PianoFree (void *ptr, size_t size) { - if (ptr != NULL) { - if (size > 0) { - /* avoid reuse of freed memory */ - memset ((char *) ptr, 0, size); - } - free (ptr); - } -} - -/* initialize piano handle - * @param piano handle - * @return nothing - */ -void PianoInit (PianoHandle_t *ph) { - memset (ph, 0, sizeof (*ph)); - - WaitressInit (&ph->waith); - strncpy (ph->waith.host, PIANO_RPC_HOST, sizeof (ph->waith.host)-1); - strncpy (ph->waith.port, PIANO_RPC_PORT, sizeof (ph->waith.port)-1); - - /* route-id seems to be random. we're using time anyway... */ - snprintf (ph->routeId, sizeof (ph->routeId), "%07liP", 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) { - PianoFree (curArtist->name, 0); - PianoFree (curArtist->musicId, 0); - lastArtist = curArtist; - curArtist = curArtist->next; - PianoFree (lastArtist, sizeof (*lastArtist)); - } - - curSong = searchResult->songs; - while (curSong != NULL) { - PianoFree (curSong->title, 0); - PianoFree (curSong->artist, 0); - PianoFree (curSong->musicId, 0); - lastSong = curSong; - curSong = curSong->next; - PianoFree (lastSong, sizeof (*lastSong)); - } -} - -/* free single station - * @param station - */ -void PianoDestroyStation (PianoStation_t *station) { - PianoFree (station->name, 0); - PianoFree (station->id, 0); - 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); - PianoFree (lastStation, sizeof (*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) { - PianoFree (curSong->audioUrl, 0); - PianoFree (curSong->artist, 0); - PianoFree (curSong->focusTraitId, 0); - PianoFree (curSong->matchingSeed, 0); - PianoFree (curSong->musicId, 0); - PianoFree (curSong->title, 0); - PianoFree (curSong->userSeed, 0); - PianoFree (curSong->identity, 0); - PianoFree (curSong->stationId, 0); - PianoFree (curSong->album, 0); - lastSong = curSong; - curSong = curSong->next; - PianoFree (lastSong, sizeof (*lastSong)); - } -} - -/* frees the whole piano handle structure - * @param piano handle - * @return nothing - */ -void PianoDestroy (PianoHandle_t *ph) { - WaitressFree (&ph->waith); - - PianoFree (ph->user.webAuthToken, 0); - PianoFree (ph->user.authToken, 0); - PianoFree (ph->user.listenerId, 0); - - PianoDestroyStations (ph->stations); - /* destroy genre stations */ - PianoGenreCategory_t *curGenreCat = ph->genreStations, *lastGenreCat; - while (curGenreCat != NULL) { - PianoDestroyStations (curGenreCat->stations); - PianoFree (curGenreCat->name, 0); - lastGenreCat = curGenreCat; - curGenreCat = curGenreCat->next; - PianoFree (lastGenreCat, sizeof (*lastGenreCat)); - } - memset (ph, 0, sizeof (*ph)); -} - -/* authenticates user - * @param piano handle - * @param username (utf-8 encoded) - * @param password (plaintext, utf-8 encoded) - * @param use ssl when logging in (1 = on, 0 = off), note that the password - * is not hashed and will be sent as plain-text! - */ -PianoReturn_t PianoConnect (PianoHandle_t *ph, const char *user, - const char *password) { - char *retStr, xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; - PianoReturn_t ret; - - /* sync and throw away result (it's an encrypted timestamp, decrypt with - * PianoDecryptString) */ - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "misc.sync" - ""); - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&method=sync", ph->routeId); - ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr); - PianoFree (retStr, 0); - - if (ret != PIANO_RET_OK) { - return ret; - } - - /* authenticate */ - snprintf (xmlSendBuf, sizeof (xmlSendBuf), - "" - "listener.authenticateListener" - "%li" - "%s" - "%s" - "", time (NULL), user, password); - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&method=authenticateListener", ph->routeId); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseUserinfo (ph, retStr); - PianoFree (retStr, 0); - } - - return ret; -} - -/* get all stations for authenticated user (so: PianoConnect needs to - * be run before) - * @param piano handle filled with some authentication data by PianoConnect - */ -PianoReturn_t PianoGetStations (PianoHandle_t *ph) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.getStations" - "%li" - "%s" - "", time (NULL), ph->user.authToken); - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=getStations", ph->routeId, - ph->user.listenerId); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseStations (ph, retStr); - PianoFree (retStr, 0); - } - - return ret; -} - -/* get next songs for station (usually four tracks) - * @param piano handle - * @param station id - * @param audio format - * @param return value: playlist - */ -PianoReturn_t PianoGetPlaylist (PianoHandle_t *ph, const char *stationId, - PianoAudioFormat_t format, PianoSong_t **retPlaylist) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - /* FIXME: remove static, "magic" numbers */ - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "playlist.getFragment" - "%li" - "%s" - "%s" - "0" - "" - "" - "%s" - "0" - "0" - "", time (NULL), ph->user.authToken, - stationId, PianoAudioFormatToString (format)); - snprintf (ph->waith.path, sizeof (ph->waith.path), 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, stationId, - PianoAudioFormatToString (format)); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParsePlaylist (ph, retStr, retPlaylist); - PianoFree (retStr, 0); - } - - return ret; -} - -/* love or ban track (you cannot remove your rating, so PIANO_RATE_NONE is - * not allowed) - * @public yes - * @param piano handle - * @param rate this track - * @param your rating - */ -PianoReturn_t PianoRateTrack (PianoHandle_t *ph, PianoSong_t *song, - PianoSongRating_t rating) { - PianoReturn_t ret; - - ret = PianoAddFeedback (ph, song->stationId, song->musicId, - song->matchingSeed, song->userSeed, song->focusTraitId, rating); - - if (ret == PIANO_RET_OK) { - song->rating = rating; - } - - return ret; -} - -/* move song to another station - * @param piano handle - * @param move from - * @param move here - * @param song to move - */ -PianoReturn_t PianoMoveSong (PianoHandle_t *ph, - const PianoStation_t *stationFrom, const PianoStation_t *stationTo, - const PianoSong_t *song) { - PianoReturn_t ret; - - /* ban from current station */ - if ((ret = PianoAddFeedback (ph, stationFrom->id, song->musicId, "", "", - "", PIANO_RATE_BAN)) == PIANO_RET_OK) { - /* love at new station */ - return PianoAddFeedback (ph, stationTo->id, song->musicId, "", - "", "", PIANO_RATE_LOVE); - } - return ret; -} - -/* add feedback - * @param piano handle - * @param station id - * @param song id - * @param song matching seed or NULL - * @param song user seed or NULL - * @param song focus trait id or NULL - * @param rating - */ -static PianoReturn_t PianoAddFeedback (PianoHandle_t *ph, const char *stationId, - const char *songMusicId, const char *songMatchingSeed, - const char *songUserSeed, const char *songFocusTraitId, - PianoSongRating_t rating) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret = PIANO_RET_ERR; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.addFeedback" - "%li" - "%s" - "%s" - "%s" - "%s" - "%s" - "%s" - "" - "%i" - "0" - "", time (NULL), ph->user.authToken, - stationId, songMusicId, - (songMatchingSeed == NULL) ? "" : songMatchingSeed, - (songUserSeed == NULL) ? "" : songUserSeed, - (songFocusTraitId == NULL) ? "" : songFocusTraitId, - (rating == PIANO_RATE_LOVE) ? 1 : 0); - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" - "&arg3=%s&arg4=%s&arg5=%s&arg6=&arg7=%s&arg8=false", ph->routeId, - ph->user.listenerId, stationId, songMusicId, - (songMatchingSeed == NULL) ? "" : songMatchingSeed, - (songUserSeed == NULL) ? "" : songUserSeed, - (songFocusTraitId == NULL) ? "" : songFocusTraitId, - (rating == PIANO_RATE_LOVE) ? "true" : "false"); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseSimple (retStr); - PianoFree (retStr, 0); - } - - return ret; -} - -/* rename station (on the server and local) - * @public yes - * @param piano handle - * @param change this stations name - * @param new name - * @return - */ -PianoReturn_t PianoRenameStation (PianoHandle_t *ph, PianoStation_t *station, - const char *newName) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - char *urlencodedNewName, *xmlencodedNewName; - PianoReturn_t ret = PIANO_RET_ERR; - - if ((xmlencodedNewName = PianoXmlEncodeString (newName)) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.setStationName" - "%li" - "%s" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - station->id, xmlencodedNewName); - - urlencodedNewName = WaitressUrlEncode (newName); - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", - ph->routeId, ph->user.listenerId, station->id, urlencodedNewName); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) { - PianoFree (station->name, 0); - station->name = strdup (newName); - } - PianoFree (retStr, 0); - } - - PianoFree (urlencodedNewName, 0); - PianoFree (xmlencodedNewName, 0); - - return ret; -} - -/* delete station - * @public yes - * @param piano handle - * @param station you want to delete - */ -PianoReturn_t PianoDeleteStation (PianoHandle_t *ph, PianoStation_t *station) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret = PIANO_RET_ERR; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.removeStation" - "%li" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - station->id); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, - ph->user.listenerId, station->id); - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) { - /* 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); - PianoFree (curStation, sizeof (*curStation)); - break; - } - lastStation = curStation; - curStation = curStation->next; - } - } - } - - return ret; -} - -/* search for music (artist or track), needed to create new station; don't - * forget to free the search result; beware! searchResult will be nulled - * by PianoXmlParseSearch - * @public yes - * @param piano handle - * @param utf-8 search string - * @param return search result - */ -PianoReturn_t PianoSearchMusic (PianoHandle_t *ph, - const char *searchStr, PianoSearchResult_t *searchResult) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - char *xmlencodedSearchStr, *urlencodedSearchStr; - PianoReturn_t ret; - - if ((xmlencodedSearchStr = PianoXmlEncodeString (searchStr)) == NULL) { - return PIANO_RET_OUT_OF_MEMORY; - } - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "music.search" - "%li" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - xmlencodedSearchStr); - - urlencodedSearchStr = WaitressUrlEncode (searchStr); - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, - ph->user.listenerId, urlencodedSearchStr); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseSearch (retStr, searchResult); - PianoFree (retStr, 0); - } - - PianoFree (urlencodedSearchStr, 0); - PianoFree (xmlencodedSearchStr, 0); - - return ret; -} - -/* create new station on server - * @public yes - * @param piano handle - * @param type: "mi" for music id (from music search) or "sh" for - * shared station - * @param id - */ -PianoReturn_t PianoCreateStation (PianoHandle_t *ph, const char *type, - const char *id) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.createStation" - "%li" - "%s" - "%s%s" - "", time (NULL), ph->user.authToken, - type, id); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId, - ph->user.listenerId, type, id); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseCreateStation (ph, retStr); - PianoFree (retStr, 0); - } - - return ret; -} - -/* FIXME: update station data instead of replacing them */ -/* add more music to existing station; multithreaded apps beware! this alters - * station data, don't forget to lock the station pointer you passed to this - * function - * @public yes - * @param piano handle - * @param add music to this station - * @param music id; can be obtained with PianoSearchMusic () - */ -PianoReturn_t PianoStationAddMusic (PianoHandle_t *ph, - PianoStation_t *station, const char *musicId) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.addSeed" - "%li" - "%s" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - station->id, musicId); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, - ph->user.listenerId, station->id, musicId); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseAddSeed (ph, retStr, station); - PianoFree (retStr, 0); - } - - return ret; -} - -/* ban a song temporary (for one month) - * @param piano handle - * @param song to be banned - * @return _OK or error - */ -PianoReturn_t PianoSongTired (PianoHandle_t *ph, const PianoSong_t *song) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "listener.addTiredSong" - "%li" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - song->identity); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=addTiredSong&arg1=%s", ph->routeId, - ph->user.listenerId, song->identity); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseSimple (retStr); - PianoFree (retStr, 0); - } - - return ret; -} - -/* set stations use by quickmix - * @param piano handle - * @return _OK or error - */ -PianoReturn_t PianoSetQuickmix (PianoHandle_t *ph) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], valueBuf[1000], urlArgBuf[1000], - *retStr; - PianoReturn_t ret; - PianoStation_t *curStation = ph->stations; - - memset (urlArgBuf, 0, sizeof (urlArgBuf)); - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.setQuickMix" - "%li" - "%s" - "RANDOM" - "", time (NULL), 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), - "%s", 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, - "", - sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s", - ph->routeId, ph->user.listenerId, urlArgBuf); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseSimple (retStr); - PianoFree (retStr, 0); - } - - 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; -} - -/* receive genre stations - * @param piano handle - * @return _OK or error - */ -PianoReturn_t PianoGetGenreStations (PianoHandle_t *ph) { - char *retStr; - PianoReturn_t ret; - - snprintf (ph->waith.path, sizeof (ph->waith.path), "/xml/genre?r=%li", - time (NULL)); - - if ((ret = PianoHttpGet (&ph->waith, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseGenreExplorer (ph, retStr); - PianoFree (retStr, 0); - } - - return ret; -} - -/* make shared stations private, needed to rate songs played on shared - * stations - * @param piano handle - * @param station to transform - * @return _OK or error - */ -PianoReturn_t PianoTransformShared (PianoHandle_t *ph, - PianoStation_t *station) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "station.transformShared" - "%li" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - station->id); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, - ph->user.listenerId, station->id); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseTranformStation (retStr); - /* though this call returns a bunch of "new" data only this one is - * changed and important (at the moment) */ - if (ret == PIANO_RET_OK) { - station->isCreator = 1; - } - PianoFree (retStr, 0); - } - - return ret; -} - -/* "why dit you play this song?" - * @param piano handle - * @param song (from playlist) - * @param return allocated string; you have to free it yourself - * @return _OK or error - */ -PianoReturn_t PianoExplain (PianoHandle_t *ph, const PianoSong_t *song, - char **retExplain) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "playlist.narrative" - "%li" - "%s" - "%s" - "%s" - "", time (NULL), ph->user.authToken, - song->stationId, song->musicId); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=method=narrative&arg1=%s&arg2=%s", - ph->routeId, ph->user.listenerId, song->stationId, song->musicId); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseNarrative (retStr, retExplain); - PianoFree (retStr, 0); - } - - return ret; -} - -/* Get seed suggestions by music id - * @param piano handle - * @param music id - * @param max results - * @param result buffer - */ -PianoReturn_t PianoSeedSuggestions (PianoHandle_t *ph, const char *musicId, - unsigned int max, PianoSearchResult_t *searchResult) { - char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; - PianoReturn_t ret; - - snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" - "music.getSeedSuggestions" - "%li" - "%s" - "%s" - "%u" - "", time (NULL), ph->user.authToken, - musicId, max); - - snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH - "rid=%s&lid=%s&method=method=getSeedSuggestions&arg1=%s&arg2=%u", - ph->routeId, ph->user.listenerId, musicId, max); - - if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == - PIANO_RET_OK) { - ret = PianoXmlParseSeedSuggestions (retStr, searchResult); - PianoFree (retStr, 0); - } - - return ret; -} - -/* 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_NET_ERROR: - return "Connection failed."; - 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; - - default: - return "No error message available."; - break; - } -} - -/* convert audio format id to string that can be used in xml requests - * @param format id - * @return constant string - */ -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; - } -} - diff --git a/libpiano/src/main.h b/libpiano/src/main.h deleted file mode 100644 index 6c8401e..0000000 --- a/libpiano/src/main.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright (c) 2008-2009 - Lars-Dominik Braun - -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 PianoFree (void *ptr, size_t size); -void PianoDestroyStation (PianoStation_t *station); - -#endif /* _MAIN_H */ diff --git a/libpiano/src/piano.c b/libpiano/src/piano.c new file mode 100644 index 0000000..43c675a --- /dev/null +++ b/libpiano/src/piano.c @@ -0,0 +1,908 @@ +/* +Copyright (c) 2008-2009 + Lars-Dominik Braun + +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 +#include +#include +#include + +#include "piano_private.h" +#include "piano.h" +#include "http.h" +#include "xml.h" +#include "crypt.h" +#include "config.h" + +#define PIANO_PROTOCOL_VERSION "25" +#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 + +/* prototypes */ +static PianoReturn_t PianoAddFeedback (PianoHandle_t *, const char *, const char *, + const char *, const char *, const char *, PianoSongRating_t); +const char *PianoAudioFormatToString (PianoAudioFormat_t); + +/* more "secure" free version + * @param free this pointer + * @param zero n bytes; 0 disables zeroing (for strings with unknown size, + * e.g.) + */ +void PianoFree (void *ptr, size_t size) { + if (ptr != NULL) { + if (size > 0) { + /* avoid reuse of freed memory */ + memset ((char *) ptr, 0, size); + } + free (ptr); + } +} + +/* initialize piano handle + * @param piano handle + * @return nothing + */ +void PianoInit (PianoHandle_t *ph) { + memset (ph, 0, sizeof (*ph)); + + WaitressInit (&ph->waith); + strncpy (ph->waith.host, PIANO_RPC_HOST, sizeof (ph->waith.host)-1); + strncpy (ph->waith.port, PIANO_RPC_PORT, sizeof (ph->waith.port)-1); + + /* route-id seems to be random. we're using time anyway... */ + snprintf (ph->routeId, sizeof (ph->routeId), "%07liP", 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) { + PianoFree (curArtist->name, 0); + PianoFree (curArtist->musicId, 0); + lastArtist = curArtist; + curArtist = curArtist->next; + PianoFree (lastArtist, sizeof (*lastArtist)); + } + + curSong = searchResult->songs; + while (curSong != NULL) { + PianoFree (curSong->title, 0); + PianoFree (curSong->artist, 0); + PianoFree (curSong->musicId, 0); + lastSong = curSong; + curSong = curSong->next; + PianoFree (lastSong, sizeof (*lastSong)); + } +} + +/* free single station + * @param station + */ +void PianoDestroyStation (PianoStation_t *station) { + PianoFree (station->name, 0); + PianoFree (station->id, 0); + 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); + PianoFree (lastStation, sizeof (*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) { + PianoFree (curSong->audioUrl, 0); + PianoFree (curSong->artist, 0); + PianoFree (curSong->focusTraitId, 0); + PianoFree (curSong->matchingSeed, 0); + PianoFree (curSong->musicId, 0); + PianoFree (curSong->title, 0); + PianoFree (curSong->userSeed, 0); + PianoFree (curSong->identity, 0); + PianoFree (curSong->stationId, 0); + PianoFree (curSong->album, 0); + lastSong = curSong; + curSong = curSong->next; + PianoFree (lastSong, sizeof (*lastSong)); + } +} + +/* frees the whole piano handle structure + * @param piano handle + * @return nothing + */ +void PianoDestroy (PianoHandle_t *ph) { + WaitressFree (&ph->waith); + + PianoFree (ph->user.webAuthToken, 0); + PianoFree (ph->user.authToken, 0); + PianoFree (ph->user.listenerId, 0); + + PianoDestroyStations (ph->stations); + /* destroy genre stations */ + PianoGenreCategory_t *curGenreCat = ph->genreStations, *lastGenreCat; + while (curGenreCat != NULL) { + PianoDestroyStations (curGenreCat->stations); + PianoFree (curGenreCat->name, 0); + lastGenreCat = curGenreCat; + curGenreCat = curGenreCat->next; + PianoFree (lastGenreCat, sizeof (*lastGenreCat)); + } + memset (ph, 0, sizeof (*ph)); +} + +/* authenticates user + * @param piano handle + * @param username (utf-8 encoded) + * @param password (plaintext, utf-8 encoded) + * @param use ssl when logging in (1 = on, 0 = off), note that the password + * is not hashed and will be sent as plain-text! + */ +PianoReturn_t PianoConnect (PianoHandle_t *ph, const char *user, + const char *password) { + char *retStr, xmlSendBuf[PIANO_SEND_BUFFER_SIZE]; + PianoReturn_t ret; + + /* sync and throw away result (it's an encrypted timestamp, decrypt with + * PianoDecryptString) */ + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "misc.sync" + ""); + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&method=sync", ph->routeId); + ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr); + PianoFree (retStr, 0); + + if (ret != PIANO_RET_OK) { + return ret; + } + + /* authenticate */ + snprintf (xmlSendBuf, sizeof (xmlSendBuf), + "" + "listener.authenticateListener" + "%li" + "%s" + "%s" + "", time (NULL), user, password); + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&method=authenticateListener", ph->routeId); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseUserinfo (ph, retStr); + PianoFree (retStr, 0); + } + + return ret; +} + +/* get all stations for authenticated user (so: PianoConnect needs to + * be run before) + * @param piano handle filled with some authentication data by PianoConnect + */ +PianoReturn_t PianoGetStations (PianoHandle_t *ph) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.getStations" + "%li" + "%s" + "", time (NULL), ph->user.authToken); + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=getStations", ph->routeId, + ph->user.listenerId); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseStations (ph, retStr); + PianoFree (retStr, 0); + } + + return ret; +} + +/* get next songs for station (usually four tracks) + * @param piano handle + * @param station id + * @param audio format + * @param return value: playlist + */ +PianoReturn_t PianoGetPlaylist (PianoHandle_t *ph, const char *stationId, + PianoAudioFormat_t format, PianoSong_t **retPlaylist) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + /* FIXME: remove static, "magic" numbers */ + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "playlist.getFragment" + "%li" + "%s" + "%s" + "0" + "" + "" + "%s" + "0" + "0" + "", time (NULL), ph->user.authToken, + stationId, PianoAudioFormatToString (format)); + snprintf (ph->waith.path, sizeof (ph->waith.path), 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, stationId, + PianoAudioFormatToString (format)); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParsePlaylist (ph, retStr, retPlaylist); + PianoFree (retStr, 0); + } + + return ret; +} + +/* love or ban track (you cannot remove your rating, so PIANO_RATE_NONE is + * not allowed) + * @public yes + * @param piano handle + * @param rate this track + * @param your rating + */ +PianoReturn_t PianoRateTrack (PianoHandle_t *ph, PianoSong_t *song, + PianoSongRating_t rating) { + PianoReturn_t ret; + + ret = PianoAddFeedback (ph, song->stationId, song->musicId, + song->matchingSeed, song->userSeed, song->focusTraitId, rating); + + if (ret == PIANO_RET_OK) { + song->rating = rating; + } + + return ret; +} + +/* move song to another station + * @param piano handle + * @param move from + * @param move here + * @param song to move + */ +PianoReturn_t PianoMoveSong (PianoHandle_t *ph, + const PianoStation_t *stationFrom, const PianoStation_t *stationTo, + const PianoSong_t *song) { + PianoReturn_t ret; + + /* ban from current station */ + if ((ret = PianoAddFeedback (ph, stationFrom->id, song->musicId, "", "", + "", PIANO_RATE_BAN)) == PIANO_RET_OK) { + /* love at new station */ + return PianoAddFeedback (ph, stationTo->id, song->musicId, "", + "", "", PIANO_RATE_LOVE); + } + return ret; +} + +/* add feedback + * @param piano handle + * @param station id + * @param song id + * @param song matching seed or NULL + * @param song user seed or NULL + * @param song focus trait id or NULL + * @param rating + */ +static PianoReturn_t PianoAddFeedback (PianoHandle_t *ph, const char *stationId, + const char *songMusicId, const char *songMatchingSeed, + const char *songUserSeed, const char *songFocusTraitId, + PianoSongRating_t rating) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret = PIANO_RET_ERR; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.addFeedback" + "%li" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "" + "%i" + "0" + "", time (NULL), ph->user.authToken, + stationId, songMusicId, + (songMatchingSeed == NULL) ? "" : songMatchingSeed, + (songUserSeed == NULL) ? "" : songUserSeed, + (songFocusTraitId == NULL) ? "" : songFocusTraitId, + (rating == PIANO_RATE_LOVE) ? 1 : 0); + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=addFeedback&arg1=%s&arg2=%s" + "&arg3=%s&arg4=%s&arg5=%s&arg6=&arg7=%s&arg8=false", ph->routeId, + ph->user.listenerId, stationId, songMusicId, + (songMatchingSeed == NULL) ? "" : songMatchingSeed, + (songUserSeed == NULL) ? "" : songUserSeed, + (songFocusTraitId == NULL) ? "" : songFocusTraitId, + (rating == PIANO_RATE_LOVE) ? "true" : "false"); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseSimple (retStr); + PianoFree (retStr, 0); + } + + return ret; +} + +/* rename station (on the server and local) + * @public yes + * @param piano handle + * @param change this stations name + * @param new name + * @return + */ +PianoReturn_t PianoRenameStation (PianoHandle_t *ph, PianoStation_t *station, + const char *newName) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + char *urlencodedNewName, *xmlencodedNewName; + PianoReturn_t ret = PIANO_RET_ERR; + + if ((xmlencodedNewName = PianoXmlEncodeString (newName)) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.setStationName" + "%li" + "%s" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + station->id, xmlencodedNewName); + + urlencodedNewName = WaitressUrlEncode (newName); + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=setStationName&arg1=%s&arg2=%s", + ph->routeId, ph->user.listenerId, station->id, urlencodedNewName); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) { + PianoFree (station->name, 0); + station->name = strdup (newName); + } + PianoFree (retStr, 0); + } + + PianoFree (urlencodedNewName, 0); + PianoFree (xmlencodedNewName, 0); + + return ret; +} + +/* delete station + * @public yes + * @param piano handle + * @param station you want to delete + */ +PianoReturn_t PianoDeleteStation (PianoHandle_t *ph, PianoStation_t *station) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret = PIANO_RET_ERR; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.removeStation" + "%li" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + station->id); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=removeStation&arg1=%s", ph->routeId, + ph->user.listenerId, station->id); + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + if ((ret = PianoXmlParseSimple (retStr)) == PIANO_RET_OK) { + /* 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); + PianoFree (curStation, sizeof (*curStation)); + break; + } + lastStation = curStation; + curStation = curStation->next; + } + } + } + + return ret; +} + +/* search for music (artist or track), needed to create new station; don't + * forget to free the search result; beware! searchResult will be nulled + * by PianoXmlParseSearch + * @public yes + * @param piano handle + * @param utf-8 search string + * @param return search result + */ +PianoReturn_t PianoSearchMusic (PianoHandle_t *ph, + const char *searchStr, PianoSearchResult_t *searchResult) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + char *xmlencodedSearchStr, *urlencodedSearchStr; + PianoReturn_t ret; + + if ((xmlencodedSearchStr = PianoXmlEncodeString (searchStr)) == NULL) { + return PIANO_RET_OUT_OF_MEMORY; + } + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "music.search" + "%li" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + xmlencodedSearchStr); + + urlencodedSearchStr = WaitressUrlEncode (searchStr); + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=search&arg1=%s", ph->routeId, + ph->user.listenerId, urlencodedSearchStr); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseSearch (retStr, searchResult); + PianoFree (retStr, 0); + } + + PianoFree (urlencodedSearchStr, 0); + PianoFree (xmlencodedSearchStr, 0); + + return ret; +} + +/* create new station on server + * @public yes + * @param piano handle + * @param type: "mi" for music id (from music search) or "sh" for + * shared station + * @param id + */ +PianoReturn_t PianoCreateStation (PianoHandle_t *ph, const char *type, + const char *id) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.createStation" + "%li" + "%s" + "%s%s" + "", time (NULL), ph->user.authToken, + type, id); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=createStation&arg1=%s%s", ph->routeId, + ph->user.listenerId, type, id); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseCreateStation (ph, retStr); + PianoFree (retStr, 0); + } + + return ret; +} + +/* FIXME: update station data instead of replacing them */ +/* add more music to existing station; multithreaded apps beware! this alters + * station data, don't forget to lock the station pointer you passed to this + * function + * @public yes + * @param piano handle + * @param add music to this station + * @param music id; can be obtained with PianoSearchMusic () + */ +PianoReturn_t PianoStationAddMusic (PianoHandle_t *ph, + PianoStation_t *station, const char *musicId) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.addSeed" + "%li" + "%s" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + station->id, musicId); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=addSeed&arg1=%s&arg2=%s", ph->routeId, + ph->user.listenerId, station->id, musicId); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseAddSeed (ph, retStr, station); + PianoFree (retStr, 0); + } + + return ret; +} + +/* ban a song temporary (for one month) + * @param piano handle + * @param song to be banned + * @return _OK or error + */ +PianoReturn_t PianoSongTired (PianoHandle_t *ph, const PianoSong_t *song) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "listener.addTiredSong" + "%li" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + song->identity); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=addTiredSong&arg1=%s", ph->routeId, + ph->user.listenerId, song->identity); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseSimple (retStr); + PianoFree (retStr, 0); + } + + return ret; +} + +/* set stations use by quickmix + * @param piano handle + * @return _OK or error + */ +PianoReturn_t PianoSetQuickmix (PianoHandle_t *ph) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], valueBuf[1000], urlArgBuf[1000], + *retStr; + PianoReturn_t ret; + PianoStation_t *curStation = ph->stations; + + memset (urlArgBuf, 0, sizeof (urlArgBuf)); + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.setQuickMix" + "%li" + "%s" + "RANDOM" + "", time (NULL), 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), + "%s", 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, + "", + sizeof (xmlSendBuf) - strlen (xmlSendBuf) - 1); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=setQuickMix&arg1=RANDOM&arg2=%s", + ph->routeId, ph->user.listenerId, urlArgBuf); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseSimple (retStr); + PianoFree (retStr, 0); + } + + 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; +} + +/* receive genre stations + * @param piano handle + * @return _OK or error + */ +PianoReturn_t PianoGetGenreStations (PianoHandle_t *ph) { + char *retStr; + PianoReturn_t ret; + + snprintf (ph->waith.path, sizeof (ph->waith.path), "/xml/genre?r=%li", + time (NULL)); + + if ((ret = PianoHttpGet (&ph->waith, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseGenreExplorer (ph, retStr); + PianoFree (retStr, 0); + } + + return ret; +} + +/* make shared stations private, needed to rate songs played on shared + * stations + * @param piano handle + * @param station to transform + * @return _OK or error + */ +PianoReturn_t PianoTransformShared (PianoHandle_t *ph, + PianoStation_t *station) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "station.transformShared" + "%li" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + station->id); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=transformShared&arg1=%s", ph->routeId, + ph->user.listenerId, station->id); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseTranformStation (retStr); + /* though this call returns a bunch of "new" data only this one is + * changed and important (at the moment) */ + if (ret == PIANO_RET_OK) { + station->isCreator = 1; + } + PianoFree (retStr, 0); + } + + return ret; +} + +/* "why dit you play this song?" + * @param piano handle + * @param song (from playlist) + * @param return allocated string; you have to free it yourself + * @return _OK or error + */ +PianoReturn_t PianoExplain (PianoHandle_t *ph, const PianoSong_t *song, + char **retExplain) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "playlist.narrative" + "%li" + "%s" + "%s" + "%s" + "", time (NULL), ph->user.authToken, + song->stationId, song->musicId); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=method=narrative&arg1=%s&arg2=%s", + ph->routeId, ph->user.listenerId, song->stationId, song->musicId); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseNarrative (retStr, retExplain); + PianoFree (retStr, 0); + } + + return ret; +} + +/* Get seed suggestions by music id + * @param piano handle + * @param music id + * @param max results + * @param result buffer + */ +PianoReturn_t PianoSeedSuggestions (PianoHandle_t *ph, const char *musicId, + unsigned int max, PianoSearchResult_t *searchResult) { + char xmlSendBuf[PIANO_SEND_BUFFER_SIZE], *retStr; + PianoReturn_t ret; + + snprintf (xmlSendBuf, sizeof (xmlSendBuf), "" + "music.getSeedSuggestions" + "%li" + "%s" + "%s" + "%u" + "", time (NULL), ph->user.authToken, + musicId, max); + + snprintf (ph->waith.path, sizeof (ph->waith.path), PIANO_RPC_PATH + "rid=%s&lid=%s&method=method=getSeedSuggestions&arg1=%s&arg2=%u", + ph->routeId, ph->user.listenerId, musicId, max); + + if ((ret = PianoHttpPost (&ph->waith, xmlSendBuf, &retStr)) == + PIANO_RET_OK) { + ret = PianoXmlParseSeedSuggestions (retStr, searchResult); + PianoFree (retStr, 0); + } + + return ret; +} + +/* 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_NET_ERROR: + return "Connection failed."; + 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; + + default: + return "No error message available."; + break; + } +} + +/* convert audio format id to string that can be used in xml requests + * @param format id + * @return constant string + */ +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; + } +} + diff --git a/libpiano/src/piano_private.h b/libpiano/src/piano_private.h new file mode 100644 index 0000000..6c8401e --- /dev/null +++ b/libpiano/src/piano_private.h @@ -0,0 +1,32 @@ +/* +Copyright (c) 2008-2009 + Lars-Dominik Braun + +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 PianoFree (void *ptr, size_t size); +void PianoDestroyStation (PianoStation_t *station); + +#endif /* _MAIN_H */ diff --git a/libpiano/src/xml.c b/libpiano/src/xml.c index ad0cf7d..35a8606 100644 --- a/libpiano/src/xml.c +++ b/libpiano/src/xml.c @@ -31,7 +31,7 @@ THE SOFTWARE. #include "piano.h" #include "crypt.h" #include "config.h" -#include "main.h" +#include "piano_private.h" static void PianoXmlStructParser (const ezxml_t, void (*callback) (const char *, const ezxml_t, void *), void *); diff --git a/libwaitress/src/CMakeLists.txt b/libwaitress/src/CMakeLists.txt index 8978a08..1985815 100644 --- a/libwaitress/src/CMakeLists.txt +++ b/libwaitress/src/CMakeLists.txt @@ -18,6 +18,6 @@ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) -add_library (waitress STATIC main.c) +add_library (waitress STATIC waitress.c) target_link_libraries (waitress ${EXTRA_LIBS}) diff --git a/libwaitress/src/main.c b/libwaitress/src/main.c deleted file mode 100644 index 545aa0a..0000000 --- a/libwaitress/src/main.c +++ /dev/null @@ -1,481 +0,0 @@ -/* -Copyright (c) 2009 - Lars-Dominik Braun - -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 char 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 0; - } - } else { - char *newbuf; - if ((newbuf = realloc (buffer->buf, - sizeof (*buffer->buf) * - (buffer->pos + recvDataSize + 1))) == NULL) { - free (buffer->buf); - return 0; - } - buffer->buf = newbuf; - } - memcpy (buffer->buf + buffer->pos, recvBytes, recvDataSize); - buffer->pos += recvDataSize; - *(buffer->buf+buffer->pos) = '\0'; - - return 1; -} - -/* 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; -} - -/* 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 = poll (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 = poll (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; - - fcntl (sockfd, F_SETFL, O_NONBLOCK); - - /* non-blocking connect will return immediately */ - connect (sockfd, res->ai_addr, res->ai_addrlen); - - sockpoll.events = POLLOUT; - pollres = poll (&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: %u\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); - 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)) { - 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)) { - 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 - diff --git a/libwaitress/src/waitress.c b/libwaitress/src/waitress.c new file mode 100644 index 0000000..545aa0a --- /dev/null +++ b/libwaitress/src/waitress.c @@ -0,0 +1,481 @@ +/* +Copyright (c) 2009 + Lars-Dominik Braun + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 char 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 0; + } + } else { + char *newbuf; + if ((newbuf = realloc (buffer->buf, + sizeof (*buffer->buf) * + (buffer->pos + recvDataSize + 1))) == NULL) { + free (buffer->buf); + return 0; + } + buffer->buf = newbuf; + } + memcpy (buffer->buf + buffer->pos, recvBytes, recvDataSize); + buffer->pos += recvDataSize; + *(buffer->buf+buffer->pos) = '\0'; + + return 1; +} + +/* 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; +} + +/* 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 = poll (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 = poll (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; + + fcntl (sockfd, F_SETFL, O_NONBLOCK); + + /* non-blocking connect will return immediately */ + connect (sockfd, res->ai_addr, res->ai_addrlen); + + sockpoll.events = POLLOUT; + pollres = poll (&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: %u\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); + 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)) { + 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)) { + 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 + diff --git a/libwardrobe/src/CMakeLists.txt b/libwardrobe/src/CMakeLists.txt index d6bed82..32fbe00 100644 --- a/libwardrobe/src/CMakeLists.txt +++ b/libwardrobe/src/CMakeLists.txt @@ -6,5 +6,5 @@ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../libwaitress/src ${CMAKE_CURRENT_BINARY_DIR} ) -add_library (wardrobe STATIC main.c md5.c) +add_library (wardrobe STATIC wardrobe.c md5.c) target_link_libraries (wardrobe waitress) diff --git a/libwardrobe/src/main.c b/libwardrobe/src/main.c deleted file mode 100644 index d822aa5..0000000 --- a/libwardrobe/src/main.c +++ /dev/null @@ -1,261 +0,0 @@ -/* -Copyright (c) 2008-2009 - Lars-Dominik Braun - -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 -#include -#include -#include -#include - -#include "wardrobe.h" -#include "md5.h" -#include "config.h" - -#define WARDROBE_HTTP_SEND_SIZE 10*1024 -#define WARDROBE_URL_SIZE 1024 - -/* Initialize song structure - * @param wardrobe sond - */ -inline void WardrobeSongInit (WardrobeSong_t *ws) { - memset (ws, 0, sizeof (*ws)); -} - -/* initialize wardrobe handle (setup waitress, e.g.) - * @param wardrobe handle - */ -inline void WardrobeInit (WardrobeHandle_t *wh) { - memset (wh, 0, sizeof (*wh)); - WaitressInit (&wh->waith); -} - -/* free () replacement that does some checks and zeros memory - * @param pointer - * @param size or 0 to disable zeroing - */ -void WardrobeFree (void *ptr, size_t size) { - if (ptr != NULL) { - if (size > 0) { - memset (ptr, 0, size); - } - free (ptr); - } -} - -/* cleanup song - * @param song - */ -void WardrobeSongDestroy (WardrobeSong_t *ws) { - WardrobeFree (ws->artist, 0); - WardrobeFree (ws->title, 0); - WardrobeFree (ws->album, 0); - memset (ws, 0, sizeof (*ws)); -} - -/* cleanup wardrobe handle - * @param initialized wardrobe handle - */ -void WardrobeDestroy (WardrobeHandle_t *wh) { - WardrobeFree (wh->user, 0); - WardrobeFree (wh->password, 0); - WaitressFree (&wh->waith); - memset (wh, 0, sizeof (*wh)); -} - -/* get session id from last.fm; you don't have to call this manually - * @param wardrobe handle - * @return _OK or error - */ -static WardrobeReturn_t WardrobeHandshake (WardrobeHandle_t *wh) { - /* md5 hash length + long integer max + NULL */ - char url[WARDROBE_URL_SIZE], tmp[32+55+1], *tmpDigest, *pwDigest, - *ret, postUrl[1024]; - WardrobeReturn_t fRet = WARDROBE_RET_ERR; - time_t currTStamp = time (NULL); - - tmpDigest = WardrobeMd5Calc (wh->password); - snprintf (tmp, sizeof (tmp), "%s%li", tmpDigest, currTStamp); - pwDigest = WardrobeMd5Calc (tmp); - snprintf (url, sizeof (url), "/?hs=true&p=1.2&c=tst&v=1.0&u=%s&t=%li&a=%s", - wh->user, currTStamp, pwDigest); - - WaitressSetHPP (&wh->waith, "post.audioscrobbler.com", "80", url); - wh->waith.method = WAITRESS_METHOD_GET; - wh->waith.postData = NULL; - wh->waith.extraHeaders = NULL; - if (WaitressFetchBuf (&wh->waith, &ret) != WAITRESS_RET_OK) { - return WARDROBE_RET_CONNECT_ERR; - } - - /* parse answer */ - if (memcmp (ret, "OK", 2) == 0) { - char *newlines[5]; - size_t i; - newlines[0] = ret; - /* split string */ - for (i = 1; i < sizeof (newlines) / sizeof (*newlines); i++) { - newlines[i] = strchr (newlines[i-1]+1, '\n'); - } - /* copy needed values (auth token and post url) */ - if (newlines[2] - newlines[1]-1 < sizeof (wh->authToken)) { - memcpy (wh->authToken, newlines[1]+1, newlines[2] - - newlines[1]-1); - } else { - printf ("buffer overflow!\n"); - } - if (newlines[4] - newlines[3]-1 < sizeof (postUrl)) { - memset (postUrl, 0, sizeof (postUrl)); - memcpy (postUrl, newlines[3]+1, newlines[4] - - newlines[3]-1); - WaitressSplitUrl (postUrl, wh->postHost, sizeof (wh->postHost), - wh->postPort, sizeof (wh->postPort), wh->postPath, - sizeof (wh->postPath)); - } else { - printf ("buffer overflow!\n"); - } - fRet = WARDROBE_RET_OK; - } else if (memcmp (ret, "BADAUTH", 7) == 0) { - fRet = WARDROBE_RET_BADAUTH; - } else if (memcmp (ret, "BADTIME", 7) == 0) { - fRet = WARDROBE_RET_BADTIME; - } else if (memcmp (ret, "BANNED", 6) == 0) { - fRet = WARDROBE_RET_CLIENT_BANNED; - } - - WardrobeFree (tmpDigest, WARDROBE_MD5_DIGEST_LEN); - WardrobeFree (pwDigest, WARDROBE_MD5_DIGEST_LEN); - WardrobeFree (ret, 0); - - return fRet; -} - -/* _really_ submit song to last.fm - * @param wardrobe handle - * @param song - * @return _OK or error - */ -static WardrobeReturn_t WardrobeSendSong (WardrobeHandle_t *wh, - const WardrobeSong_t *ws) { - char postContent[WARDROBE_HTTP_SEND_SIZE]; - char *urlencArtist, *urlencTitle, *urlencAlbum, *ret; - WardrobeReturn_t fRet = WARDROBE_RET_ERR; - - urlencArtist = WaitressUrlEncode (ws->artist); - urlencTitle = WaitressUrlEncode (ws->title); - urlencAlbum = WaitressUrlEncode (ws->album); - - snprintf (postContent, sizeof (postContent), "s=%s&a[0]=%s&t[0]=%s&" - "i[0]=%li&o[0]=E&r[0]=&l[0]=%li&b[0]=%s&n[0]=&m[0]=", - wh->authToken, urlencArtist, urlencTitle, ws->started, - ws->length, urlencAlbum); - - WaitressSetHPP (&wh->waith, wh->postHost, wh->postPort, wh->postPath); - wh->waith.method = WAITRESS_METHOD_POST; - wh->waith.postData = postContent; - wh->waith.extraHeaders = "Content-Type: application/x-www-form-urlencoded\r\n"; - if (WaitressFetchBuf (&wh->waith, &ret) != WAITRESS_RET_OK) { - return WARDROBE_RET_CONNECT_ERR; - } - - if (memcmp (ret, "OK", 2) == 0) { - fRet = WARDROBE_RET_OK; - } else if (memcmp (ret, "BADSESSION", 10) == 0) { - fRet = WARDROBE_RET_BADSESSION; - } - - WardrobeFree (urlencArtist, 0); - WardrobeFree (urlencTitle, 0); - WardrobeFree (urlencAlbum, 0); - WardrobeFree (ret, 0); - - return fRet; -} - -/* submit played track to last.fm - * @public yes - * @param wardrobe handle - * @param song data - * @return _OK or error - */ -WardrobeReturn_t WardrobeSubmit (WardrobeHandle_t *wh, - const WardrobeSong_t *ws) { - size_t i; - WardrobeReturn_t fRet = WARDROBE_RET_ERR; - - if (strlen (wh->authToken) <= 0 && (fRet = WardrobeHandshake (wh)) != - WARDROBE_RET_OK) { - return fRet; - } - for (i = 0; i < 2; i++) { - fRet = WardrobeSendSong (wh, ws); - if (fRet == WARDROBE_RET_OK) { - return WARDROBE_RET_OK; - } else if (fRet == WARDROBE_RET_BADSESSION && - (fRet = WardrobeHandshake (wh)) != WARDROBE_RET_OK) { - return fRet; - } - } - return fRet; -} - -/* error string - * @param error int - * @return human readable error string or NULL on error - */ -const char *WardrobeErrorToString (WardrobeReturn_t ret) { - switch (ret) { - case WARDROBE_RET_ERR: - return "Unknown error."; - break; - - case WARDROBE_RET_OK: - return "Everything's fine :)"; - break; - - case WARDROBE_RET_CLIENT_BANNED: - return "Client banned. Try to update your software."; - break; - - case WARDROBE_RET_BADAUTH: - return "Wrong username or password."; - break; - - case WARDROBE_RET_BADTIME: - return "System time wrong. Check your system time and " - "correct it."; - break; - - case WARDROBE_RET_BADSESSION: - return "Bad session. Try to login again."; - break; - - case WARDROBE_RET_CONNECT_ERR: - return "Connection failed."; - break; - - default: - return "Unknown Error."; - break; - } -} - diff --git a/libwardrobe/src/wardrobe.c b/libwardrobe/src/wardrobe.c new file mode 100644 index 0000000..d822aa5 --- /dev/null +++ b/libwardrobe/src/wardrobe.c @@ -0,0 +1,261 @@ +/* +Copyright (c) 2008-2009 + Lars-Dominik Braun + +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 +#include +#include +#include +#include + +#include "wardrobe.h" +#include "md5.h" +#include "config.h" + +#define WARDROBE_HTTP_SEND_SIZE 10*1024 +#define WARDROBE_URL_SIZE 1024 + +/* Initialize song structure + * @param wardrobe sond + */ +inline void WardrobeSongInit (WardrobeSong_t *ws) { + memset (ws, 0, sizeof (*ws)); +} + +/* initialize wardrobe handle (setup waitress, e.g.) + * @param wardrobe handle + */ +inline void WardrobeInit (WardrobeHandle_t *wh) { + memset (wh, 0, sizeof (*wh)); + WaitressInit (&wh->waith); +} + +/* free () replacement that does some checks and zeros memory + * @param pointer + * @param size or 0 to disable zeroing + */ +void WardrobeFree (void *ptr, size_t size) { + if (ptr != NULL) { + if (size > 0) { + memset (ptr, 0, size); + } + free (ptr); + } +} + +/* cleanup song + * @param song + */ +void WardrobeSongDestroy (WardrobeSong_t *ws) { + WardrobeFree (ws->artist, 0); + WardrobeFree (ws->title, 0); + WardrobeFree (ws->album, 0); + memset (ws, 0, sizeof (*ws)); +} + +/* cleanup wardrobe handle + * @param initialized wardrobe handle + */ +void WardrobeDestroy (WardrobeHandle_t *wh) { + WardrobeFree (wh->user, 0); + WardrobeFree (wh->password, 0); + WaitressFree (&wh->waith); + memset (wh, 0, sizeof (*wh)); +} + +/* get session id from last.fm; you don't have to call this manually + * @param wardrobe handle + * @return _OK or error + */ +static WardrobeReturn_t WardrobeHandshake (WardrobeHandle_t *wh) { + /* md5 hash length + long integer max + NULL */ + char url[WARDROBE_URL_SIZE], tmp[32+55+1], *tmpDigest, *pwDigest, + *ret, postUrl[1024]; + WardrobeReturn_t fRet = WARDROBE_RET_ERR; + time_t currTStamp = time (NULL); + + tmpDigest = WardrobeMd5Calc (wh->password); + snprintf (tmp, sizeof (tmp), "%s%li", tmpDigest, currTStamp); + pwDigest = WardrobeMd5Calc (tmp); + snprintf (url, sizeof (url), "/?hs=true&p=1.2&c=tst&v=1.0&u=%s&t=%li&a=%s", + wh->user, currTStamp, pwDigest); + + WaitressSetHPP (&wh->waith, "post.audioscrobbler.com", "80", url); + wh->waith.method = WAITRESS_METHOD_GET; + wh->waith.postData = NULL; + wh->waith.extraHeaders = NULL; + if (WaitressFetchBuf (&wh->waith, &ret) != WAITRESS_RET_OK) { + return WARDROBE_RET_CONNECT_ERR; + } + + /* parse answer */ + if (memcmp (ret, "OK", 2) == 0) { + char *newlines[5]; + size_t i; + newlines[0] = ret; + /* split string */ + for (i = 1; i < sizeof (newlines) / sizeof (*newlines); i++) { + newlines[i] = strchr (newlines[i-1]+1, '\n'); + } + /* copy needed values (auth token and post url) */ + if (newlines[2] - newlines[1]-1 < sizeof (wh->authToken)) { + memcpy (wh->authToken, newlines[1]+1, newlines[2] - + newlines[1]-1); + } else { + printf ("buffer overflow!\n"); + } + if (newlines[4] - newlines[3]-1 < sizeof (postUrl)) { + memset (postUrl, 0, sizeof (postUrl)); + memcpy (postUrl, newlines[3]+1, newlines[4] - + newlines[3]-1); + WaitressSplitUrl (postUrl, wh->postHost, sizeof (wh->postHost), + wh->postPort, sizeof (wh->postPort), wh->postPath, + sizeof (wh->postPath)); + } else { + printf ("buffer overflow!\n"); + } + fRet = WARDROBE_RET_OK; + } else if (memcmp (ret, "BADAUTH", 7) == 0) { + fRet = WARDROBE_RET_BADAUTH; + } else if (memcmp (ret, "BADTIME", 7) == 0) { + fRet = WARDROBE_RET_BADTIME; + } else if (memcmp (ret, "BANNED", 6) == 0) { + fRet = WARDROBE_RET_CLIENT_BANNED; + } + + WardrobeFree (tmpDigest, WARDROBE_MD5_DIGEST_LEN); + WardrobeFree (pwDigest, WARDROBE_MD5_DIGEST_LEN); + WardrobeFree (ret, 0); + + return fRet; +} + +/* _really_ submit song to last.fm + * @param wardrobe handle + * @param song + * @return _OK or error + */ +static WardrobeReturn_t WardrobeSendSong (WardrobeHandle_t *wh, + const WardrobeSong_t *ws) { + char postContent[WARDROBE_HTTP_SEND_SIZE]; + char *urlencArtist, *urlencTitle, *urlencAlbum, *ret; + WardrobeReturn_t fRet = WARDROBE_RET_ERR; + + urlencArtist = WaitressUrlEncode (ws->artist); + urlencTitle = WaitressUrlEncode (ws->title); + urlencAlbum = WaitressUrlEncode (ws->album); + + snprintf (postContent, sizeof (postContent), "s=%s&a[0]=%s&t[0]=%s&" + "i[0]=%li&o[0]=E&r[0]=&l[0]=%li&b[0]=%s&n[0]=&m[0]=", + wh->authToken, urlencArtist, urlencTitle, ws->started, + ws->length, urlencAlbum); + + WaitressSetHPP (&wh->waith, wh->postHost, wh->postPort, wh->postPath); + wh->waith.method = WAITRESS_METHOD_POST; + wh->waith.postData = postContent; + wh->waith.extraHeaders = "Content-Type: application/x-www-form-urlencoded\r\n"; + if (WaitressFetchBuf (&wh->waith, &ret) != WAITRESS_RET_OK) { + return WARDROBE_RET_CONNECT_ERR; + } + + if (memcmp (ret, "OK", 2) == 0) { + fRet = WARDROBE_RET_OK; + } else if (memcmp (ret, "BADSESSION", 10) == 0) { + fRet = WARDROBE_RET_BADSESSION; + } + + WardrobeFree (urlencArtist, 0); + WardrobeFree (urlencTitle, 0); + WardrobeFree (urlencAlbum, 0); + WardrobeFree (ret, 0); + + return fRet; +} + +/* submit played track to last.fm + * @public yes + * @param wardrobe handle + * @param song data + * @return _OK or error + */ +WardrobeReturn_t WardrobeSubmit (WardrobeHandle_t *wh, + const WardrobeSong_t *ws) { + size_t i; + WardrobeReturn_t fRet = WARDROBE_RET_ERR; + + if (strlen (wh->authToken) <= 0 && (fRet = WardrobeHandshake (wh)) != + WARDROBE_RET_OK) { + return fRet; + } + for (i = 0; i < 2; i++) { + fRet = WardrobeSendSong (wh, ws); + if (fRet == WARDROBE_RET_OK) { + return WARDROBE_RET_OK; + } else if (fRet == WARDROBE_RET_BADSESSION && + (fRet = WardrobeHandshake (wh)) != WARDROBE_RET_OK) { + return fRet; + } + } + return fRet; +} + +/* error string + * @param error int + * @return human readable error string or NULL on error + */ +const char *WardrobeErrorToString (WardrobeReturn_t ret) { + switch (ret) { + case WARDROBE_RET_ERR: + return "Unknown error."; + break; + + case WARDROBE_RET_OK: + return "Everything's fine :)"; + break; + + case WARDROBE_RET_CLIENT_BANNED: + return "Client banned. Try to update your software."; + break; + + case WARDROBE_RET_BADAUTH: + return "Wrong username or password."; + break; + + case WARDROBE_RET_BADTIME: + return "System time wrong. Check your system time and " + "correct it."; + break; + + case WARDROBE_RET_BADSESSION: + return "Bad session. Try to login again."; + break; + + case WARDROBE_RET_CONNECT_ERR: + return "Connection failed."; + break; + + default: + return "Unknown Error."; + break; + } +} + diff --git a/src/main.c b/src/main.c index bfe4965..426d566 100644 --- a/src/main.c +++ b/src/main.c @@ -49,7 +49,6 @@ THE SOFTWARE. #include "player.h" #include "settings.h" -#include "main.h" #include "terminal.h" #include "config.h" #include "ui.h" -- cgit v1.2.3