Il y a presque un an maintenant
j'avais testé Anywhere.FM, un site permettant d'uploader sa musique en ligne pour pouvoir ensuite la réécouter depuis n'importe quel poste connecté à Internet et disposant du plugin
Flash d'
Adobe.
C'est très pratique et agréable... quand cela fonctionne. Et malheureusement sous Linux, ça ne fonctionne pas souvent. Premièrement la fonction d'upload ne fonctionne pas sous Linux avec Flash9 (je n'ai pas testé avec la version béta 10), la faute à Adobe qui semble moins pressé quand il s'agit de systèmes libres. Deuxièmement la lecture des titres (et c'est quand même l'utilité principale du site) semble fonctionner uniquement quand ça lui chante... et de toute évidence depuis des mois le site a décidé qu'il ne voulait pas lire les musiques sous Linux. Ainsi l'animation Flash reste bloquée sur
"Loading..." mais ne charge rien du tout.
Bref dans mon raz-le-bol général j'ai décidé d'analyser le fonctionnement du site et de voir s'il est possible de développer un petit quelque chose qui me permettrait de pouvoir lire ma musique en ligne directement sur les serveurs sans avoir à passer par l'interface pompeuse existante
A l'heure actuelle il n'y a pas encore de code, seulement mon analyse qui dévoile la grosse partie du fonctionnement du site.
IdentificationSi vous rendez sur une des urls du site, vous serez redirigés automatiquement vers
http://www.anywhere.fm/player/. Cette page est chargée dynamiquement et en fonction de la page que vous avez demandé en premier, différentes variables seront passées à l'animation Flash chargée dans le navigateur (par exemple pour indiquer qu'il faut aller chercher la librairie de tel utilisateur).
Durant ce premier contact avec le site, un identifiant de session PHP (variable
PHPSESSID) vous sera attribué et vous n'en changerez normalement pas même si vous fermez/ouvrez une session sur le site.
Cette variable de session définie comme cookie est spécifique à l'hôte
www du domaine
anywhere.fm (d'autres hôtes entrent en jeu comme on le verra plus tard).
TrackingLa page chargée fait appel à différents scripts JavaScript dont un qui a sans doute un rôle de tracking (analyse pour étude commerciale etc) mais à l'heure de ces lignes je ne le jurerais pas.
Le script en question se situe à l'adresse
http://edge.quantserve.com/quant.js et il déclare une variable nommée
"dc" qui est générée aléatoirement par le serveur.
La fonction principale nommée
quantserve() récupère différentes informations sur la configuration de l'internaute et construit une url vers un
"pixel traqueur" qui est ensuite chargée par le client pour envoyer les données au serveur
pixel.quantserve.com.
La partie qui nous intéresse dans ce script (mais qui est peut-être optionnelle) est la modification des cookies.
La variable
dc qui était définie se retrouve dans les cookies sous le nom
"__qca".
Une autre variable, générée aléatoirement en JavaScript (code :
Math.round(Math.random()*2147483647) ) est aussi ajoutée au cookie et se nomme
"__qcb".
Contrairement à
PHPSESSID qui est propre à
www,
__qca et
__qcb sont propres à l'ensemble du domaine.
CrossdomainL'animation Flash étant chargée, elle a besoin de vérifier qu'elle a bien la permission de communiquer avec certains serveurs. Pour cela elle va demander pour chacun un fichier
crossdomain.xml qui définie les règles d'accès.
Parmis les serveurs on trouve
music,
upload-XX (XX étant un nombre multiple de 5) et
upload-full.
AuthentificationL'animation ayant déterminée qu'elle pouvait dialoguer avec
music.anywhere.fm, elle effectue une requête GET sur la page /account/get_current_user?_unique_request_id_=XXXXXXXXXXXXXX.
L'argument
_unique_request_id_ a pour valeur un nombre probablement aléatoire mais toujours très grand. Cet
"identifiant unique de requête" est incrémenté à chaque communication avec le serveur
music.
Le serveur
music donne ses réponses sous le format XML. En l'absence de la saisie de vos identifiants, les données renvoyées sont
celles de l'utilisateur par défaut (
user_id = 89,
login = demo,
fullname = Lux)
La page en profite pour définir trois nouvelles variables de cookie :
-
auth_token et auth_session_id ont pour restriction l'hôte music
-
_HotPot_session_id est défini sur tout le domaine anywhere.fm
Ces trois variables sont typiques des variables de session : 32 caractères sous l'alphabet hexadécimal.
Malgré leur nom différents,
auth_session_id et
_HotPot_session_id ont la même valeur.
L'opération suivante est un POST à l'adresse /account/create_login_tracker?_unique_request_id_=XXXXXXXXXXXXXX.
Les variables vues précédemment et concernant l'hôte
music ou tout le domaine sont bien sûr renvoyés à nouveau au serveur.
Les données passées dans le corps de la requêtes sont
session_id (correspondant à
auth_session_id de tout à l'heure) et
user_id (correspondant à celui renvoyé par la requête précédente soit 89 ici).
Cela renvoit une information très courte de la forme :
<tracker_id>XXXXXXXXXX</tracker_id>Introduction à AMFDébutent alors les communications
AMF avec le serveur
www.
Action Message Format est un format de données créé à l'origine par
Macromedia puis conservé par
Adobe.
Il est utilisé principalement par Flash pour des communications
RPC.
L'avantage par rapport à un formatage XML sont son format binaire (on n'est pas resteint à des caractères imprimables) et son gain de place. Il utilise notemment un système de références qui permet de ne pas avoir à redéfinir des données déjà envoyées (pour le moment je n'ai pas étudié en détails ce sujet).
On pourrait comparer ce format au
bencodage de BitTorrent mais en réalité il est bien plus complexe, voire carrément prise de tête (rien que
le codage d'entiers sur 29 bits donne un aperçu)
De plus deux versions existent : AMF0 et AMF3. Il peut possible d'intégrer des données AMF3 dans du AMF0 par le biais d'un type particulier (0x11)
Les requêtes HTTP transportant des données AMF ont l'entête
"Content-Type: application/x-amf"Ce format n'étant pas le sujet de l'article, je n'en dirais pas plus pour le moment.
ConnexionPassons pour l'instant sur les échanges
RPC qui ont été faits, et rentrons nos identifiants de connexion sur le site.
Cela génère une requête POST sur /account/login?_unique_request_id_=XXXXXXXXXXXXXX sur le serveur
music. L'identifiant de requête a (ne l'oublions pas) été incrémenté à plusieurs reprises et cela ne changera pas.
Les données envoyées sont :
- dans le header Cookie : __qca, __qcb, auth_token, auth_session_id et _HotPot_session_id
- dans le body : "login" et "password"
auth_session_id et
_HotPot_session_id ont toujours la même valeur.
La réponse du serveur nous informe d'une nouvelle valeur pour
auth_token (qui définie donc notre session utilisateur sur le serveur
music)
Sur le serveur
www, aucun changement n'est à prendre en compte, les données du cookies n'ont pas changées.
Les deux requêtes effectuées avant connexion sont répétées sauf que
get_current_user renverra notre
user_id et
create_login_tracker renverra une nouvelle valeur.
RPC et FlexLes échanges RPC sont effectués uniquement avec le serveur www et à destination de la page
/amfphp/gateway.php?_unique_request_id_=XXXXXXXXXXXXXXLes requêtes RPC sont toujours groupées par deux et ont le même
_unique_request_id_.
Anywhere.FM utilise les librairies
Flex dans ses échanges.
On trouve ainsi deux types de messages :
-
flex.messaging.messages.CommandMessage : permet de faire un "ping" du serveur. C'est toujours la première requête du groupe
-
flex.messaging.messages.RemotingMessage : permet d'appeler une fonction RPC sur le serveur. C'est toujours la seconde requête
Ces fonctions
Flex se basent toujours sur les mêmes arguments.
Concernant les fonctions RPC, l'argument
source sera toujours fixé à
"BlazingFast.DBQueries". L'argument
operation contient le nom de la fonction à appeler sur le serveur et
body est un tableau contenant les arguments à y passer.
FonctionsLe premier argument de chacune des fonctions existante correspont au
auth_session_id, permettant ainsi au serveur de vérifier les permissions d'accès aux données.
La première fonction qui nous intéresse est
get_complete_profiles. Elle prend deux argument : le premier est (bien entendu) l'identifiant de session et le second est un tableau contenant une liste d'identifiants utilisateur.
Le résultat retourné est un tableau contenant des informations diverses sur les utilisateurs correspondant, notemment les liste de titres dont ils disposent.
Trois données importantes permettent de définir une playlist :
playlist_type,
playlist_id et
playlist_name.
Le type d'une playlist aura la valeur 200 s'il s'agit de la collection entière de l'utilisateur et 300 s'il s'agit d'une librairie personnalisée.
La fonction
get_songs_for_user_playlists pourrait se définir de cette façon :
get_songs_for_user_playlists(session_id, user_id, bool, [playlist_id], [playlist_type], ['0','0'...])
Le rôle du booléen en troisième argument m'échappe pour le moment ainsi que le tableau de chaines de caractères définie à '0'.
Cette fonction retourne un tableau qui peut être imposant définissant chaque titre d'une liste de lecture. En dehors des données
"classiques" (name, artist, album), on trouve les données
"master_id" et
"song_ressource_id" qui permettent de récupérer une adresse valide pour un titre donné.
C'est la fonction
get_song_url qui nous donne le sésame. On pourrait la déclarer de cette façon :
get_song_url(session_id, user_id, master_id, song_ressource_id)
Elle retourne une chaine de caractères correspondant à une url HTTP vers le fichier MP3
Cette url dispose d'une clé d'accès ainsi qu'un temps de validation. Par conséquent il ne doit pas être possible d'utiliser deux fois la même url.
ConlusionIl reste de nombreux points à éclaircir qui doivent pouvoir être déterminés par expérimentation comme :
-
_unique_request_id_ est-il généré aléatoirement au début ?
- Les variables définies par quantserve() ont-elle une utilité autre que le tracking ?
- Quelle utilité a la valeur tracker_id ?
- A quoi sert le booléen et le tableau de chaines passé à get_songs_for_user_playlists ?
NetographieOpen Source Flash documentationPyAMF (les codes sources m'ont bien aidé)
Charles Web Debugging Proxy : Un proxy qui comprend l'AMF

Malheureusement en shareware
Ping FlexPour terminer un petit exemple de code utilisant l'API PyAMF qui réalisé un ping en Flex (merci aux développeurs de l'API pour leur aide ;-) ) :
import pyamf
from pyamf.remoting import client
from pyamf.flex import messaging
from pyamf import remoting
gw = client.RemotingService('http://127.0.0.1/',pyamf.AMF0,pyamf.ClientTypes.Flash9)
message = messaging.CommandMessage(body={},
timestamp=0,
destination='',
clientId=None,
headers={'DSId': u'nil'},
timeToLive=0,
messageId='7478D81C-65B4-EA4B-B52E-4689507A7241',
operation=5,correlationId='',
messageRefType='flex.messaging.messages.CommandMessage')
gw.addRequest('null', message)
gw.execute()