Logo Search packages:      
Sourcecode: mas version File versions

freedb.c

/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * 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.
 *
 */

/* Some code taken from freedb.org's webpage. */
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "mas/mas_dpi.h"
#include "mas_cdrom_common.h"
#include "cdrom_int_device.h"



/* Formats the data for various reasons */
static void format_cddb_data(char *data)
{
      int i;


      if(!data) return;

      for(i = 0; i < strlen(data); i++)
      {
            /* Replace all '\n' '\r' with spaces */
            if(data[i] == '\n' || data[i] == '\r') data[i] = ' ';

            /* Replace all literal "\n" with ' \n' */
            if(data[i] == '\\' && data[i+1] == 'n')
            {
                  data[i] = ' ';
                  data[i+1] = '\n';
                  i++;
            }
      }
}

/* Find a tag and it's data from a returned cddb response. Returns
 * a pointer to a new malloc()ed string. */
static char *get_cddb_tag_data(char *tag, char *response)
{
      char        *current_location = response;
      char        *tag_start;
      char        *data_start;
      char        *data_end;
      char        *data = NULL;


      masc_entering_log_level("Getting tag data from server response: get_cddb_tag_data()");

      /* The return statements will end this loop. */
      while(1)
      {
            /* There should be at least one instance of this tag */
            if((tag_start = strstr(current_location, tag)) == NULL)
                  goto success;

            /* Find the beginning of the data */
            if((data_start = strchr(tag_start, '=')) == NULL)
                  goto success;

            data_start++;
      
            /* Find the end of the data */
            if((data_end = strchr(data_start, '\n')) == NULL)
                  goto success;
      
            /* Now put the string into the data buffer */
            if(data == NULL)
            {
                  if((data = malloc(data_end - data_start + 2)) == NULL)
                  {
                        masc_log_message(MAS_VERBLVL_ERROR, "malloc returned NULL");
                        goto failure;
                  }
                  data[0] = '\0';
            }
            else
            {
                  if((data = realloc(data, strlen(data) + data_end - data_start + 2)) == NULL)
                  {
                        masc_log_message(MAS_VERBLVL_ERROR, "realloc returned NULL");
                        goto failure;
                  }
            }
            strncat(data, data_start, data_end - data_start + 1);

            current_location = data_end;
      }
      masc_exiting_log_level();

      goto success;
failure:
      if(data) free(data);
      data = NULL;
success:
      if(data) format_cddb_data(data);
      masc_exiting_log_level();
      return data;
}


/* Takes a string and escapes any cgi characters */
static int escape_cgi_chars(char *old_string, char **new_string)
{
      int   old_loc, new_loc;
      int   special_chars = 0;
      char  escaped_char[3];


      masc_entering_log_level("Escaping special chars in CGI string: escape_cgi_chars()");

      /* Find how many special chars are in the string */
      for(old_loc=0; old_loc<strlen(old_string); old_loc++)
            if(old_string[old_loc] <= '+' || old_string[old_loc] >= '{')
                  special_chars++;

      /* Malloc a new string based on the number of characters to escape plus a null terminator
       * Escaping characters adds two more characters. '+' = %2B */
      if((*new_string = (char*)malloc(strlen(old_string) + special_chars * 2 + 1)) == NULL)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "malloc() failed for new_string");
            masc_exiting_log_level();
            return 0;
      }

      /* Now escape all the characters */
      new_loc = 0;
      for(old_loc=0; old_loc<strlen(old_string); old_loc++)
            if(old_string[old_loc] <= '+' || old_string[old_loc] >= '{')
            {
                  /* Replace the spaces since cddb servers don't handle them correctly. */
                  if(old_string[old_loc] == ' ')
                  {
                        (*new_string)[new_loc] = '_';
                        new_loc++;
                  }
                  else
                  {
                        (*new_string)[new_loc] = '%';
                        new_loc++;
                        sprintf(escaped_char, "%02x", (int)old_string[old_loc]);
                        strcpy((*new_string)+new_loc, escaped_char);
                        new_loc += 2;
                  }
            }
            else
            {
                  (*new_string)[new_loc] = old_string[old_loc];
                  new_loc++;
            }

      /* Put a null terminator on the end of the string */
      (*new_string)[new_loc] = '\0';


      masc_exiting_log_level();

      return 1;
}

/* Generates the freedb id of the cd */
static int generate_cddb_id(struct cdrom_device *cd_dev)
{
      struct track_info *tracks = cd_dev->tracks;
      int               tot_trks = cd_dev->number_of_tracks;
        int             i, t = 0, n = 0;
      int               track_offset;


        /* For backward compatibility this algorithm must not change */
        for(i=0; i < tot_trks; i++)
      {
            track_offset = tracks[i].start_msf.minute * 60 + tracks[i].start_msf.second;
            
            while(track_offset > 0)
            {
                  n += track_offset % 10;
                  track_offset /= 10;
            }
      }
      

        t = ((tracks[tot_trks].start_msf.minute * 60) + tracks[tot_trks].start_msf.second) - ((tracks[0].start_msf.minute * 60) + tracks[0].start_msf.second);

        cd_dev->cddb_id = (n % 0xff) << 24 | t << 8 | tot_trks;


      return 1;
}

/* Sends a string to the cddb server and then mallocs a string for the
 * response. Be sure to free the string that is returned. Returns NULL
 * on error. */
static char *send_http_command(char *servername, char *command)
{
#define BUF_AMOUNT 1024
      struct sockaddr_in      cddb_server;
      struct hostent          *hostdata;
      int               fdSocket = -1;
      int               amount, recv_ret;
      char              *response = NULL;


      masc_entering_log_level("Sending HTTP request: send_http_command()");

      /* Get the server and connect to it. */
      if((hostdata = gethostbyname(servername)) == NULL)
      {
            masc_log_message(MAS_VERBLVL_WARNING, "gethostbyname failed for: %s", servername);
            goto failure;
      }

      memset(&cddb_server, 0, sizeof(cddb_server));
      cddb_server.sin_family = AF_INET;
      cddb_server.sin_addr.s_addr = ((struct in_addr *)hostdata->h_addr_list[0])->s_addr;
      cddb_server.sin_port = htons(80);

      if((fdSocket = socket(AF_INET, SOCK_STREAM, 0)) <= 0)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "socket creation failed: %s", strerror(errno));
            goto failure;
      }

      if(connect(fdSocket, (struct sockaddr *)&cddb_server, sizeof(struct sockaddr_in)) < 0)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "failed to connect socket: %s", strerror(errno));
            goto failure;
      }

      /* Send the string */
      if(send(fdSocket, command, strlen(command), 0) <= 0)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "failed to send data across socket: %s", strerror(errno));
            goto failure;
      }

      /* Malloc the buffer for the response */
      if((response = (char*)malloc(BUF_AMOUNT)) == NULL)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "malloc returned NULL");
            goto failure;
      }

      /* Get the response */
      amount = 0;
      do{
            recv_ret = recv(fdSocket, response+amount, BUF_AMOUNT, 0);

            if(recv_ret < 0)
            {
                  masc_log_message(MAS_VERBLVL_ERROR, "failed to recv data: %s", strerror(errno));
                  goto failure;
            }

            amount += recv_ret;
            /* if the maximum amount was recieved, realloc for more data */
            if(recv_ret == BUF_AMOUNT)
                  if((response = realloc(response, amount + BUF_AMOUNT)) == NULL)
                  {
                        masc_log_message(MAS_VERBLVL_ERROR, "realloc returned NULL");
                        goto failure;
                  }

      }while(recv_ret > 0);


      /* Make sure the end it null terminated. */
      response[amount] = '\0';
      goto success;
failure:
      if(response) free(response);
      response = NULL;
success:
      if(fdSocket != -1) close(fdSocket);
      masc_exiting_log_level();
      return response;
#undef BUF_AMOUNT
}


/* Get the cddb info from across the network
 * Use the HTTP protocol. */
static int get_cddb_dbinfo(struct cdrom_device *cd_dev, char *servername, char *username)
{
      char  hello[2048];
      char  command[2048];
      char  group[128];
      char  tag[64];
      char  *response, *sub_resp, *begin, *end;
      char  *cgi_username, *cgi_appname;
      int   i;
      

      masc_entering_log_level("Getting CDDB info: get_cddb_dbinfo()");

      /***************
       * Create the "hello" string.
       ***************/
      if(!escape_cgi_chars(username, &cgi_username))
      {
            masc_exiting_log_level();
            return 0;
      }
      if(!escape_cgi_chars(MAS_NAME, &cgi_appname))
      {
            masc_exiting_log_level();
            return 0;
      }
      sprintf(hello, "&hello=%s+%s+%s+%d.%d.%d&proto=4 HTTP/1.0\r\n\r\n",
                  cgi_username, "armstrong.shiman.com", cgi_appname,
                  MAS_VERSION_MAJOR, MAS_VERSION_MINOR, MAS_VERSION_TEENY);
      free(cgi_username);
      free(cgi_appname);


      /***************
       * Send the "query" command to the database
       ***************/
      sprintf(command, "GET /~cddb/cddb.cgi?cmd=cddb+query+%08x+%d",
                  cd_dev->cddb_id, cd_dev->number_of_tracks);
      /* Add the track offsets in frames */
      for(i=0; i<cd_dev->number_of_tracks; i++)
            sprintf(command+strlen(command), "+%d",
                              cd_dev->tracks[i].start_msf.minute * 60 * 75 + 
                              cd_dev->tracks[i].start_msf.second * 75 +
                              cd_dev->tracks[i].start_msf.frame);
      /* Add the leadout offset in seconds and the "hello" string. */
      sprintf(command+strlen(command), "+%d%s",
                              cd_dev->tracks[i].start_msf.minute * 60 +
                              cd_dev->tracks[i].start_msf.second, hello);

      /* Now send off the "query" command */
      if((response = send_http_command(servername, command)) == NULL)
      {
            masc_exiting_log_level();
            return 0;
      }
      
      /**************
       * Now create a "read" command. 
       **************/
      /* Get the group that the track was found in. */
      if((sub_resp = strstr(response, "\r\n\r\n")) == NULL)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "Can not process syntax of http response");
            masc_exiting_log_level();
            return 0;
      }
      sub_resp += 4;
      if((begin = strchr(sub_resp, ' ')) == NULL)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "Can not process syntax of http response");
            masc_exiting_log_level();
            return 0;
      }
      begin++;
      if((end = strchr(begin, ' ')) == NULL)
      {
            masc_log_message(MAS_VERBLVL_ERROR, "Can not process syntax of http response");
            masc_exiting_log_level();
            return 0;
      }
      strncpy(group, begin, end-begin);
      group[end-begin] = '\0';
      free(response);

      /* Build the command string */
      sprintf(command, "GET /~cddb/cddb.cgi?cmd=cddb+read+%s+%08x%s",
                  group, cd_dev->cddb_id, hello);
      if((response = send_http_command(servername, command)) == NULL)
      {
            masc_exiting_log_level();
            return 0;
      }


      /**************
       * Store the cd info in the device.
       **************/
      /* Get the title of the cd */
      cd_dev->cd_title = get_cddb_tag_data("DTITLE", response);

      /* Get the year of the cd */
      cd_dev->cd_year = get_cddb_tag_data("DYEAR", response);

      /* Get the genre of the cd */
      cd_dev->cd_genre = get_cddb_tag_data("DGENRE", response);

      /* Now fill in all the track titles */
      for(i = 0; i < cd_dev->number_of_tracks; i++)
      {
            sprintf(tag, "TTITLE%d", i);
            cd_dev->tracks[i].trackname = get_cddb_tag_data(tag, response);
      }

      /* Throw the rest of the data in misc in case the user wants it. */
      cd_dev->cd_misc_data = response;

      masc_exiting_log_level();

      return 1;
}


/* Gets the cddb info from a cddb database. */
int update_cddb_info(struct cdrom_device *cd_dev, char *username, char *servername)
{
      int               ret_val;
      int               i;



      masc_entering_log_level("Updating CDDB info: update_cddb_info()");

      /* First clear out all the existing cddb info */
      cd_dev->cddb_id = 0;    
      if(cd_dev->cd_genre) free(cd_dev->cd_genre);
      cd_dev->cd_genre = NULL;      
      if(cd_dev->cd_title) free(cd_dev->cd_title);
      cd_dev->cd_title = NULL;      
      if(cd_dev->cd_year) free(cd_dev->cd_year);
      cd_dev->cd_year = NULL; 
      if(cd_dev->cd_misc_data) free(cd_dev->cd_misc_data);
      cd_dev->cd_misc_data = NULL;  
      for(i=0; i<cd_dev->number_of_tracks; i++)
      {
            free(cd_dev->tracks[i].trackname);
            cd_dev->tracks[i].trackname = NULL;
      }
      cd_dev->number_of_tracks = 0;

      /* Make sure the cdinfo is updated */
      if(!mas_cdrom_update_status(cd_dev))
            goto failure;

      /* Grab the ID and store it */
      if(!generate_cddb_id(cd_dev))
            goto failure;

      /* Now get the cddb info from a cddb server if the server name is not NULL */
      if(!get_cddb_dbinfo(cd_dev, servername, username))
            goto failure;

      ret_val = 1;
      goto success;
failure:
      ret_val = 0;
success:
      masc_exiting_log_level();
      return ret_val;
}

Generated by  Doxygen 1.6.0   Back to index