[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[vidalia-svn] r2940: Part 0 of multiple geoip options: move to a separate dir all (in vidalia/branches/exit-country/src/vidalia: . config geoip)



Author: cviecco
Date: 2008-08-05 09:18:56 -0400 (Tue, 05 Aug 2008)
New Revision: 2940

Added:
   vidalia/branches/exit-country/src/vidalia/geoip/
   vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.h
   vidalia/branches/exit-country/src/vidalia/geoip/geoip.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/geoip.h
   vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.h
   vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.h
   vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.h
   vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.h
   vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.cpp
   vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.h
Removed:
   vidalia/branches/exit-country/src/vidalia/config/filegeoipresolver.cpp
   vidalia/branches/exit-country/src/vidalia/config/filegeoipresolver.h
   vidalia/branches/exit-country/src/vidalia/config/geoip.cpp
   vidalia/branches/exit-country/src/vidalia/config/geoip.h
Modified:
   vidalia/branches/exit-country/src/vidalia/CMakeLists.txt
   vidalia/branches/exit-country/src/vidalia/config/networkoutpage.cpp
   vidalia/branches/exit-country/src/vidalia/config/networkoutpage.h
Log:
Part 0 of multiple geoip options: move to a separate dir all possible modules/options. Next step: unified API


Modified: vidalia/branches/exit-country/src/vidalia/CMakeLists.txt
===================================================================
--- vidalia/branches/exit-country/src/vidalia/CMakeLists.txt	2008-08-05 12:00:06 UTC (rev 2939)
+++ vidalia/branches/exit-country/src/vidalia/CMakeLists.txt	2008-08-05 13:18:56 UTC (rev 2940)
@@ -15,6 +15,7 @@
   ${CMAKE_CURRENT_BINARY_DIR}
   ${CMAKE_CURRENT_SOURCE_DIR}
   ${CMAKE_CURRENT_SOURCE_DIR}/config
+  ${CMAKE_CURRENT_SOURCE_DIR}/geoip
   ${CMAKE_CURRENT_SOURCE_DIR}/help/browser
 )
 configure_file(
@@ -69,8 +70,8 @@
   config/torsettings.cpp
   config/vidaliasettings.cpp
   config/vsettings.cpp
-  config/filegeoipresolver.cpp
-  config/geoip.cpp
+  #config/filegeoipresolver.cpp
+  #config/geoip.cpp
 )
 qt4_wrap_cpp(vidalia_SRCS
   config/abstracttorsettings.h
@@ -92,7 +93,7 @@
   config/torsettings.h
   config/vidaliasettings.h
   config/vsettings.h
-  config/filegeoipresolver.h
+  #config/filegeoipresolver.h
 )
 if (USE_MINIUPNPC)
   include_directories(${MINIUPNPC_INCLUDE_DIR})
@@ -114,6 +115,28 @@
   help/browser/helptextbrowser.h
 )
 
+## Geoip sources
+set(vidalia_SRCS ${vidalia_SRCS}
+  geoip/geoip.cpp
+  geoip/filegeoipresolver.cpp
+  #next are the web enabled ones!
+  geoip/geoipcacheitem.cpp
+  geoip/geoipcache.cpp
+  geoip/geoiprequest.cpp
+  geoip/geoipresponse.cpp
+  geoip/webgeoipresolver.cpp
+)
+qt4_wrap_cpp(vidalia_SRCS
+#  geoip/geoip.h
+  geoip/filegeoipresolver.h
+  geoip/geoipcacheitem.h
+  geoip/geoipcache.h
+  geoip/geoiprequest.h
+  geoip/geoipresponse.h
+  geoip/webgeoipresolver.h
+)
+
+
 ## Message log sources
 set(vidalia_SRCS ${vidalia_SRCS}
   log/logfile.cpp

Deleted: vidalia/branches/exit-country/src/vidalia/config/filegeoipresolver.cpp

Deleted: vidalia/branches/exit-country/src/vidalia/config/filegeoipresolver.h

Deleted: vidalia/branches/exit-country/src/vidalia/config/geoip.cpp

Deleted: vidalia/branches/exit-country/src/vidalia/config/geoip.h

Modified: vidalia/branches/exit-country/src/vidalia/config/networkoutpage.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/config/networkoutpage.cpp	2008-08-05 12:00:06 UTC (rev 2939)
+++ vidalia/branches/exit-country/src/vidalia/config/networkoutpage.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -44,6 +44,13 @@
   }
  */ 
 
+  /* Create the timer that will be used to update the internal ans displays once
+   every hour */
+  //_refreshTimer.setInterval(60*60*1000);
+   _refreshTimer.setInterval(60*2*1000);
+  connect(&_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));
+
+
   /*do connections!*/
   connect(_torControl, SIGNAL(authenticated()), this, SLOT(onAuthenticated()));
   connect(_torControl, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
@@ -70,6 +77,9 @@
 
   //file based geoip resolver
   _geoIpResolver=Vidalia::geoIpResolver();
+
+  
+
 }
 
 /** Destructor */
@@ -150,9 +160,21 @@
   load_longnames();
 }
 
+//void
+//NetworkoutPage::refresh(){
+//}
+
+
 void
 NetworkoutPage::onAuthenticated()
 {
+  refresh();
+   _refreshTimer.start();
+}
+
+void
+NetworkoutPage::refresh()
+{
   //_geoip.setSocksHost(_torControl->getSocksAddress(),
   //                    _torControl->getSocksPort());
   //refresh();
@@ -171,13 +193,12 @@
   //initialize the window.. just in case it has not been initialized yet.
   load();
   
-   
+  fprintf(stderr,"refresh!\n"); 
 
   /*Fill up the internal structure*/
   //by_country_exit_nodes.clear();
   by_country_exit_nodes2.clear();
   foreach( RouterStatus router, networkStatus){
-     //fprintf(stderr,"*");
 
      //Get the country!
      ip_addr=router.ipAddress().toString();
@@ -219,7 +240,6 @@
         by_country_exit_nodes2.insert(short_country_name,QList<RouterStatus>()<<router);
      }
   }
-  return;
   
 
   //* now fill the combo boxes!*/  
@@ -294,7 +314,6 @@
      copyExitCountryToText();
      applyTorSettings();
   }
-  fprintf(stderr,"networout on authenticated, done!\n");
 
 }
 
@@ -369,6 +388,7 @@
   //erase data structure
   by_country_exit_nodes2.clear();
   //remove data from the country combo box?
+   _refreshTimer.stop();
 
 }
 
@@ -416,7 +436,7 @@
   if((ui.chkNodePolicy->isChecked())){
     settings.setUseExitNodePolicy(ui.chkExitNodePolicy->isChecked());
     settings.setUseExcludeNodePolicy(ui.chkExcludeNodePolicy->isChecked());
-    fprintf(stderr,"node pol applyset, checked!\n");
+    vInfo("node pol applyset, checked!\n");
 
     /*Only save if we have enabled saving exit policies!*/
     if(ui.chkExitNodePolicy->isChecked()){
@@ -425,7 +445,7 @@
         /*iterate over listwidget?*/
         QStringList exitList;
         QString current=ui.cmboExitNodesbyCountry->currentText();
-        fprintf(stderr,"qlen=%d",current.length());
+        //vInfo("qlen=%d",current.length());
         QStringList countrylist=current.split("(");
         current=countrylist.at(0);
         current=getExitCountry(); 

Modified: vidalia/branches/exit-country/src/vidalia/config/networkoutpage.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/config/networkoutpage.h	2008-08-05 12:00:06 UTC (rev 2939)
+++ vidalia/branches/exit-country/src/vidalia/config/networkoutpage.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -60,6 +60,9 @@
   void onAuthenticated();  
   /** Handles when we get disconnected from Tor network */
   void onDisconnected();
+  
+  /*refresh the window values*/
+  void refresh();
 
   
 private:
@@ -91,6 +94,9 @@
 
   TorControl* _torControl;
   FileGeoIpResolver* _geoIpResolver;
+ 
+  /** Timer that fires once an hour to update the QMaps list. */
+  QTimer _refreshTimer;
 
 };
 

Added: vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,241 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipresolver.cpp
+** \version $Id: geoipresolver.cpp 2429 2008-03-21 02:27:36Z edmanm $
+** \brief Requests GeoIP information and caches the result
+*/
+
+//#include <torsocket.h>
+#include <vidalia.h>
+#include "filegeoipresolver.h"
+#include "config.h"
+
+#include <time.h>
+
+#define GEOIP_FILENAME  (Vidalia::dataDirectory() + "/geoip.data")
+
+inline
+uint32_t
+FileGeoIpResolver::string2intloc(const char *in){
+   //long internal;
+   uint32_t rval;
+   rval=atoi(in);
+   if(0==rval){
+      rval=0xFF000000 | (in[0]<<8) | (in[1]);
+   }
+   return rval;
+}
+
+
+/** Default constructor. */
+FileGeoIpResolver::FileGeoIpResolver()
+{
+  time_t start,end;
+  ready=false;
+  FileGeoIpResolverGeoLocation newlocation;
+  FileGeoIpResolverIpRange newrange;
+
+  start=time(NULL);
+  //start by attempting to read the user geoip file
+  FILE* file2;
+  file2=fopen(GEOIP_FILENAME.toAscii(),"r");
+  char tmpline[256];
+  char tmpline2[256];
+  char tmpline3[256];
+  int block_count=0;  
+  char *tok;
+
+
+  if(NULL!=file2){
+     //fprintf(stderr,"fopen, open!\n");
+     while (fgets(tmpline, 255, file2) != NULL) {
+          memcpy(tmpline2,tmpline,256);
+
+          if(3==sscanf(tmpline,"%u,%u,%s",&newrange.low_net,&newrange.high_net,tmpline3)){
+                if(0==block_count){
+                  ipRange.reserve((location.size()*location.size())/10);
+                }
+                block_count++;
+               //token = strtok(line, search);
+               //fprintf(stderr,"%d,%d,%s\n",newrange.low_net,newrange.high_net,tmpline2);   
+               //newrange.location=tmpline3;
+               QString newline(tmpline3);
+               //QString line2(tmpline2);
+               //QStringList list2(line2.split(","));
+                    //newrange.low_net=list2.at(0).toUInt();
+                    //newrange.high_net=list2.at(1).toUInt();
+                    //newrange.location=list2.at(2);
+                    newrange.location2=string2intloc(tmpline3);
+                    //newrange.location=newline;
+              
+ 
+               ipRange.append(newrange); 
+          }
+          else{
+                QString line2(tmpline2);
+                QStringList list2(line2.split(","));
+                switch(list2.size()){
+                      //beware of fallthrough
+                      default:
+                         newlocation.extra_info=list2.at(4);
+                      case 4: //is location;
+                         newlocation.country_short=list2.at(1);
+                         newlocation.latitude=list2.at(2).toFloat();
+                         newlocation.longitude=list2.at(3).toFloat();
+                         //fprintf(stderr,"%s",tmpline2);
+                         //location.insert(list2.at(0),newlocation);
+                         //memcpy(tmpline3,list2.at(0).toAscii(),2);
+                         //location.insert(string2intloc(tmpline3),newlocation);
+                         location.insert(string2intloc(list2.at(0).toAscii()),newlocation);
+                         break;
+                      case 0:
+                      case 1:
+                      case 2:
+                      case 3:
+                         break;
+
+                }
+          }
+          //QString line2(tmpline);
+          //QStringList list2(line2.split(','));
+
+      } //closes while
+      fclose(file2);
+  }
+  else{
+    //This branche when user geoip file not found or cannot be opened
+    //therefore, use the default, built in file!
+     QFile file(":/geoip/geoip.data");
+     QString line;
+     QStringList list;
+     QTextStream in(&file);
+     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)){
+       fprintf (stderr,"cannot open geoip data file!\n");
+       return;
+     }
+     //fprintf(stderr,"using default geoip file!\n");
+     while (!in.atEnd()) {
+         line = in.readLine();
+         list=line.split(",");
+         //list=in.readLine().split(',');
+
+         switch (list.size()){
+             case 0:
+             case 1:
+             case 2:
+                    //invalid options
+                    break;
+
+             case 3:  //is range
+                    newrange.low_net=list.at(0).toUInt();
+                    newrange.high_net=list.at(1).toUInt();
+                    //newrange.location=list.at(2);
+                    //memcpy(tmpline3,list.at(0).toAscii(),2);
+                    newrange.location2=string2intloc(list.at(2).toAscii());
+                    ipRange.append(newrange);
+                    break;
+
+             //now some magic, beware of fallthrough
+             default:
+                    newlocation.extra_info=list.at(4);
+             case 4: //is location;
+                    newlocation.country_short=list.at(1);
+                    newlocation.latitude=list.at(2).toFloat();
+                    newlocation.longitude=list.at(3).toFloat();
+                    //location.insert(list.at(0),newlocation);
+                    location.insert(string2intloc(list.at(0).toAscii()),newlocation);
+                    break;
+
+         }
+
+     }//closes while
+
+
+  }
+  
+ 
+  end=time(NULL);
+  ready=true;
+}
+
+
+//returns an country given an ip.
+QString 
+FileGeoIpResolver::get_country(QHostAddress ip){
+  QString unknown("??");
+  //return unknown;  
+  GeoIp resolv=search(ip);
+   
+  if (resolv.isEmpty() || resolv.isUnknown())
+    return unknown;
+  else 
+    return resolv.country();
+}
+
+
+//Search for a geoip address given a host ip
+GeoIp 
+FileGeoIpResolver::search(QHostAddress ip){
+  int i;
+  int found=-1;
+  FileGeoIpResolverGeoLocation iploc;
+  int low=0,mid; 
+  int high=ipRange.size();
+  GeoIp invalid;
+
+  
+  /*
+  //old code.. simple linear search..
+  for(i=0;i<ipRange.size();i++){
+     if(ipRange[i].low_net<=ip.toIPv4Address() && ipRange[i].high_net>=ip.toIPv4Address() ){
+        found=i;
+        break;
+     }
+  }
+  */
+
+  //new code.. combo binary search + linear (linear on near found)
+  low=0;
+  high=ipRange.size(); 
+  while(low+2<high){
+     //now compare with middle!
+     mid=(low+high)/2;
+     if(ipRange[mid].low_net>ip.toIPv4Address()){
+        high=mid;
+     }
+     else{
+        low=mid;
+     }
+  }
+  for(i=low;i<high+1 && i<ipRange.size();i++){
+     if(ipRange[i].low_net<=ip.toIPv4Address() && 
+        ipRange[i].high_net>=ip.toIPv4Address()   ){
+        found=i;
+        break;
+     }
+  }
+
+  if(-1==found){
+    //fprintf(stderr,"not found!\n");
+    return invalid;
+  }
+  
+
+  //now get location
+  //iploc=location[ipRange[found].location];
+  iploc=location[ipRange[found].location2];
+  //now convert
+  GeoIp rvalue(ip, iploc.latitude, iploc.longitude,
+        iploc.extra_info, "", iploc.country_short); 
+  return rvalue;
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/filegeoipresolver.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,120 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file filegeoipresolver.h
+** \version $Id: geoipresolver.h 2429 2008-03-21 02:27:36Z edmanm $
+** \brief Provides and API to resolve geoip queries. Queries are solved syncronously
+**        based on data in a local file. All operations must be thread safe.
+*/
+
+#ifndef _FILEGEOIPRESOLVER_H
+#define _FILEGEOIPRESOLVER_H
+
+#include <QObject>
+#include <QList>
+#include <QHash>
+#include <QString>
+#include <QHostAddress>
+#include <QFile>
+#include <QDir>
+#include <QTextStream>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QVector>
+#include <QVarLengthArray>
+#include <geoip.h>
+
+//#include "geoipcache.h"
+//#include "geoiprequest.h"
+//#include "geoipresponse.h"
+
+
+//Two classes for internal storage!
+class  FileGeoIpResolverGeoLocation
+{
+  public:
+   QString country_short;
+   float latitude;
+   float longitude;
+   QString extra_info;
+};
+
+class  FileGeoIpResolverIpRange
+{
+  public:
+   uint32_t low_net;
+   uint32_t high_net;
+   //QString location;
+   uint32_t location2;
+};
+
+
+//now the actual class
+
+class FileGeoIpResolver : public QObject
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  FileGeoIpResolver();
+  
+  /** Sets the address and port of Tor, through which GeoIP requests will be
+   * made. */
+  //void setSocksHost(QHostAddress addr, quint16 port);
+  /** Resolves a single IP to a geographic location. */
+  //int resolve(QHostAddress ip);
+  /** Resolves a list of IPs to a geographic location. */
+  //int resolve(QList<QHostAddress> ips);
+  
+  //QList<GeoIp> resolve_sync(QHostAddress ip);
+  //List<GeoIp> resolve_sync(QList<QHostAddress> ips);
+
+  /** Resolves <b>ip</b> to geographic information only if it is cached. */
+  //bool resolveFromCache(QHostAddress ip);
+  /** Resolves a list of IPs to a geographic location, but only those which
+   * are cached. Returns a list of which IPs were not cached. */
+  //QList<QHostAddress> resolveFromCache(QList<QHostAddress> ips);
+
+  QString get_country(QHostAddress ip);
+
+//signals:
+  /** Emitted when a list of IPs have been resolved to lat/long. */
+  //void resolved(int id, QList<GeoIp> geoips);
+  /** Emitted when a resolve has failed. */
+  //void resolveFailed(int id, QString errorString);
+
+//private slots:
+  /** Called when the socket has connected to the Geo IP host. */
+  //void connected();
+  /** Called when the socket has disconnected from the Geo IP host. */
+  //void disconnected();
+  /** Called when an error has occurred getting the Geo IP information. */
+  //void socketError(QString errorString);
+
+public:
+  GeoIp search(QHostAddress ip);
+
+private:
+  //provides locking during db access/updates to guarantee thread safety
+  // not used now since we do not update the DB on realtime
+  QMutex mutex;
+  bool ready;
+
+  //now the internal data structs
+  //QHash<QString,FileGeoIpResolverGeoLocation> location;
+  QHash<uint32_t,FileGeoIpResolverGeoLocation> location;
+  QVector<FileGeoIpResolverIpRange> ipRange;
+  uint32_t string2intloc(const char *);
+};
+
+#endif
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoip.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoip.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoip.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,139 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoip.cpp
+** \version $Id: geoip.cpp 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Associates an IP with a geographic location
+*/
+
+#include <QStringList>
+
+#include "geoip.h"
+
+/** Verifies a latitude is between -90.0 and 90.0 degrees. */
+#define IS_VALID_LATITUDE(x)    (((x) >= -90.0) && ((x) <= 90.0))
+/** Verifies a longitude is between -180.0 and 180.0 degrees. */
+#define IS_VALID_LONGITUDE(x)   (((x) >= -180.0) && ((x) <= 180.0))
+
+
+/** Constructor */
+GeoIp::GeoIp(QHostAddress ip)
+{
+  _ip = ip;
+  _latitude = _longitude = 0xFFFF;
+}
+
+/** Constructor. */
+GeoIp::GeoIp(QHostAddress ip, float latitude, float longitude, 
+             QString city, QString state, QString country)
+{
+  _ip        = ip;
+  _latitude  = latitude;
+  _longitude = longitude;
+  _city      = city;
+  _state     = state;
+  _country   = country;
+}
+
+/** Parses the GeoIp information from a comma-delimited string. The format of
+ * the string is as in the following example:
+ *
+ *      128.213.48.13,Troy,NY,US,42.7495,-73.5951,1138402852
+ */
+GeoIp
+GeoIp::fromString(QString geoip)
+{
+  /* Split comma-delimited data fields */
+  QStringList data = geoip.split(",");
+  
+  if (data.size() == 2 && data.at(1).toLower() == "unknown") {
+    return GeoIp(QHostAddress(data.at(0)));
+  } else if (data.size() < 6) {
+    return GeoIp();
+  }
+  
+  /* Parse the data from the string */
+  QHostAddress   ip(data.at(0));
+  QString city    = data.at(1);
+  QString state   = data.at(2);
+  QString country = data.at(3);
+  float latitude  = data.at(4).toFloat();
+  float longitude = data.at(5).toFloat();
+ 
+  /* Create a new GeoIp object with the parsed data. */
+  return GeoIp(ip, latitude, longitude, city, state, country);
+}
+
+/** Formats the GeoIp information as a comma-delimited string. */
+QString
+GeoIp::toString() const
+{
+  QString s;
+  /* Assemble and comma-delimit the data fields */
+  s.append(_ip.toString());
+  s.append("," + _city);
+  s.append("," + _state);
+  s.append("," + _country);
+  s.append("," + QString::number(_latitude,  'f', 4));
+  s.append("," + QString::number(_longitude, 'f', 4));
+  return s;
+}
+
+/** Returns true if the GeoIp object is invalid. */
+bool
+GeoIp::isEmpty() const
+{
+  return (_ip.isNull() && 
+          !IS_VALID_LATITUDE(_latitude) && 
+          !IS_VALID_LONGITUDE(_longitude));
+}
+
+/** Returns true if the GeoIp object is valid, but no location information
+ * is known for the associated IP address. */
+bool
+GeoIp::isUnknown() const
+{
+  return (!_ip.isNull() && 
+          !IS_VALID_LATITUDE(_latitude) && 
+          !IS_VALID_LONGITUDE(_longitude));
+}
+
+/** Returns a human-readable string of GeoIp location information. */
+QString
+GeoIp::toLocation() const
+{
+  QStringList location;
+  
+  /* Add the city name (if present) */
+  if (!_city.isEmpty()) {
+    location << _city;
+  }
+  /* Add the state or region name (if present) */
+  if (!_state.isEmpty()) {
+    /* Only display non-numeric region codes. */
+    bool valid = true;
+    for (int i = 0; i < _state.length(); i++) {
+      if (_state[i].isDigit()) {
+        valid = false;
+        break;
+      }
+    }
+    if (valid) {
+      location << _state;
+    }
+  }
+  /* Add the country code (if present) */
+  if (!_country.isEmpty()) {
+    location << _country;
+  }
+  return location.join(", ");
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoip.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoip.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoip.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,72 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoip.h
+** \version $Id: geoip.h 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Associates an IP with a geographic location
+*/
+
+#ifndef _GEOIP_H
+#define _GEOIP_H
+
+#include <QString>
+#include <QHostAddress>
+
+
+class GeoIp
+{
+public:
+  /** Default constructor */
+  GeoIp() : _latitude(0xFFFF), _longitude(0xFFFF) {}
+  /** Constructor. */
+  GeoIp(QHostAddress ip);
+
+  /** Constructor */
+  GeoIp(QHostAddress ip, float latitude, float longitude, 
+        QString city, QString state, QString country);
+  
+  /** Creates a GeoIp object from a string. */
+  static GeoIp fromString(QString geoip);
+  /** Builds a comma-delimited string of GeoIp fields. */
+  QString toString() const;
+
+  /** Returns the IP address for this object. */
+  QHostAddress ip() const { return _ip; }
+  /** Returns the latitude coordinate for this IP. */
+  float latitude() const { return _latitude; }
+  /** Returns the longitude coordinate for this IP. */
+  float longitude() const { return _longitude; }
+  /** Returns the city in which this IP lives. */
+  QString city() const { return _city; }
+  /** Returns the state or district in which this IP lives. */
+  QString state() const { return _state; }
+  /** Returns the country in which this IP lives. */
+  QString country() const { return _country; }
+  /** Returns a human-readable string of city, region(state), and country. */
+  QString toLocation() const;
+
+  /** Returns true if the GeoIp object is invalid. */
+  bool isEmpty() const;
+  /** Returns true if the GeoIp object is valid, but no location information
+   * is known for the associated IP address. */
+   bool isUnknown() const;
+
+private:
+  QHostAddress _ip; /**< IP address for this location. */
+  float _latitude;  /**< Latitudinal coordinate for this IP's location. */
+  float _longitude; /**< Longitudinal coordinate for this IP's location. */
+  QString _city;    /**< City in which this IP lives. */
+  QString _state;   /**< State or district in which this IP lives. */
+  QString _country; /**< Country in which this IP lives. */
+};
+
+#endif
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,144 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipcache.cpp
+** \version $Id: geoipcache.cpp 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Caches the results of previous GeoIP requests
+*/
+
+#include <QFile>
+#include <QDir>
+#include <QTextStream>
+#include <stringutil.h>
+#include <file.h>
+#include <vidalia.h>
+
+#include "geoipcache.h"
+
+/* Location of Vidalia's geoip cache file. Qt docs claims that QFile will
+ * translate the "/" correctly on Windows. Hopefully they didn't lie. */
+#define CACHE_FILENAME  (Vidalia::dataDirectory() + "/geoip-cache")
+
+
+/** Constructor. */
+GeoIpCache::GeoIpCache()
+{
+  loadFromDisk();
+}
+
+/** Returns the location currently used for the cache file. */
+QString
+GeoIpCache::cacheFilename()
+{
+  return CACHE_FILENAME;
+}
+
+/** Writes the current cache to disk. */
+bool
+GeoIpCache::saveToDisk(QString *errmsg)
+{
+  /* Make sure we have a data directory. */
+  if (!create_path(Vidalia::dataDirectory())) {
+    return false;
+  }
+  
+  /* Try to open a temporary cache file for writing */
+  QFile tmpCacheFile(CACHE_FILENAME + ".tmp");
+  if (!tmpCacheFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+    return err(errmsg, tmpCacheFile.errorString());
+  }
+  
+  /* Write the cache entries to the file. */
+  QTextStream cache(&tmpCacheFile);
+  foreach (GeoIpCacheItem cacheItem, _cache.values()) {
+    /* Save the cache item if it's not too old. */
+    if (!cacheItem.isExpired()) {
+      cache << cacheItem.toString() << endl;
+    }
+  }
+  
+  QFile cacheFile(CACHE_FILENAME);
+  /* Check if an previous cache file exists. */
+  if (cacheFile.exists()) {
+    /* A previous cache file exists, so try to remove it */
+    if (!cacheFile.remove()) {
+      return err(errmsg, cacheFile.errorString());
+    }
+  }
+  /* Rename the temporary file into place. */
+  if (tmpCacheFile.rename(cacheFile.fileName())) {
+    return err(errmsg, tmpCacheFile.errorString());
+  }
+  return true;
+}
+
+/** Reads the cache contents in from disk. This function returns true if no
+ * cache file exists, since it's possible nothing has been cached yet. */
+bool
+GeoIpCache::loadFromDisk(QString *errmsg)
+{
+  QFile cacheFile(CACHE_FILENAME);
+  
+  if (cacheFile.exists()) {
+    /* Try to open the cache file */
+    if (!cacheFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+      return err(errmsg, cacheFile.errorString());
+    }
+    
+    /* Read the cached items from the cache file */
+    QTextStream cache(&cacheFile);
+    QString line = cache.readLine();
+    while (!line.isNull()) {
+      /* Create a GeoIpCacheItem from the line and save it */
+      GeoIpCacheItem item = GeoIpCacheItem::fromString(line);
+      if (!item.isEmpty() && !item.isExpired()) {
+        /* Only load non-stale cache items. */
+        _cache.insert(item.ip().toIPv4Address(), item);
+      }
+      line = cache.readLine();
+    }
+  }
+  return true;
+}
+
+/** Caches the given IP and geographic information to disk. Call saveToDisk()
+ * when you want to write the cache to disk. */
+void
+GeoIpCache::cache(GeoIp geoip)
+{
+  /* Store the entry in our in-memory cache */
+  _cache.insert(geoip.ip().toIPv4Address(), 
+                GeoIpCacheItem(geoip,QDateTime::currentDateTime()));
+}
+
+/** Returns a GeoIp object for the given IP from cache. */
+GeoIp
+GeoIpCache::geoip(QHostAddress ip)
+{
+  if (this->contains(ip)) {
+    return _cache.value(ip.toIPv4Address()).geoip();
+  }
+  return GeoIp();
+}
+
+/** Returns true if the given IP address is cached and the cached information
+ * is not stale. */
+bool
+GeoIpCache::contains(QHostAddress ip)
+{
+  quint32 ipv4 = ip.toIPv4Address();
+  if (_cache.contains(ipv4)) {
+    GeoIpCacheItem cacheItem = _cache.value(ipv4);
+    return !cacheItem.isExpired();
+  }
+  return false;
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoipcache.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,52 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipcache.h
+** \version $Id: geoipcache.h 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Caches the results of previous GeoIP requests
+*/
+
+#ifndef _GEOIPCACHE_H
+#define _GEOIPCACHE_H
+
+#include <QString>
+#include <QHash>
+#include <QHostAddress>
+
+#include "geoipcacheitem.h"
+
+
+class GeoIpCache
+{
+public:
+  /** Default constructor. */
+  GeoIpCache();
+  
+  /** Writes the current cache to disk. */
+  bool saveToDisk(QString *errmsg = 0);
+  /** Reads the cache in from disk. */
+  bool loadFromDisk(QString *errmsg = 0);
+  
+  /** Returns the location currently used for the cache file. */
+  QString cacheFilename();
+  /** Caches the given IP and geographic information to disk. */
+  void cache(GeoIp geoip);
+  /** Returns a GeoIp object for the given IP from cache. */
+  GeoIp geoip(QHostAddress ip);
+  /** Returns true if the given IP address is cached. */
+  bool contains(QHostAddress ip);
+  
+private:
+  QHash<quint32, GeoIpCacheItem> _cache;  /**< List of cached GeoIp objects. */  
+};
+
+#endif
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,84 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipcacheitem.cpp
+** \version $Id: geoipcacheitem.cpp 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Cached result of a single IP-to-geolocation result
+*/
+
+#include <QStringList>
+
+#include "geoipcacheitem.h"
+
+
+/** Constructor */
+GeoIpCacheItem::GeoIpCacheItem(GeoIp geoip, QDateTime timestamp)
+{
+  _geoip = geoip;
+  _timestamp = timestamp;
+}
+
+/** Returns true if this cache item is empty and invalid. A valid cache item
+ * must have a valid GeoIp object and timestamp. */
+bool
+GeoIpCacheItem::isEmpty() const
+{
+  return (_geoip.isEmpty() || _timestamp.isNull());
+}
+
+/** Returns a string representing the contents of this cache item, suitable
+ * for writing to disk. The format is as in the following example:
+ *                     <Geo IP Data>:<Timestamp>
+ */
+QString
+GeoIpCacheItem::toString() const
+{
+  return _geoip.toString() + ":" + QString::number(_timestamp.toTime_t());
+}
+
+/** Returns a GeoIpCacheItem from a string as read from the cache that was
+ * written to disk. The format is:
+ *                     <Geo IP Data>[:<Timestamp>]
+ *
+ * If no value for Timestamp is given, the current date and time will be used.
+ * If the string cannot be parsed for valid cached GeoIP data, then an empty
+ * GeoIpCacheItem object is returned. The calling method should call isEmpty()
+ * on the returned GeoIpCacheItem object to ensure it got a valid object.
+ */
+GeoIpCacheItem
+GeoIpCacheItem::fromString(QString cacheString)
+{
+  QDateTime timestamp;
+  QStringList cacheData = cacheString.split(":");
+
+  if (cacheData.size() >= 1) {
+    GeoIp geoip = GeoIp::fromString(cacheData.at(0));
+    if (cacheData.size() >= 2)
+      timestamp.setTime_t(cacheData.at(1).toUInt());
+    else
+      timestamp = QDateTime::currentDateTime();
+    return GeoIpCacheItem(geoip, timestamp);
+  }
+  return GeoIpCacheItem();
+}
+
+/** Returns true if the cache item is too old to be considered valid. Normal
+ * cached responses are valid for one month. Cached UNKNOWN responses are
+ * considered valid for one week. */
+bool
+GeoIpCacheItem::isExpired() const
+{
+  if (_geoip.isUnknown()) {
+    return (_timestamp.addDays(7) < QDateTime::currentDateTime());
+  }
+  return (_timestamp.addMonths(1) < QDateTime::currentDateTime()); 
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoipcacheitem.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,55 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipcacheitem.h
+** \version $Id: geoipcacheitem.h 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Cached result of a single IP-to-geolocation result
+*/
+
+#ifndef _GEOIPCACHEITEM_H
+#define _GEOIPCACHEITEM_H
+
+#include <QDateTime>
+
+#include <geoip.h>
+
+
+class GeoIpCacheItem
+{
+public:
+  /** Default constructor */
+  GeoIpCacheItem() {};
+  /** Constructor. */
+  GeoIpCacheItem(GeoIp geoip, QDateTime timestamp);
+
+  /** Returns the IP of this cache item. */
+  QHostAddress ip() const { return _geoip.ip(); }
+  /** Returns the cached GeoIp object. */
+  GeoIp geoip() const { return _geoip; }
+  /** Returns true if this cache item is expired. */
+  bool isExpired() const;
+  /** Returns true if this cache item is empty and invalid. */
+  bool isEmpty() const;
+
+  /** Returns a string representing the contents of this cache item, suitable
+   * for writing to disk. */
+  QString toString() const;
+  /** Returns a GeoIpCacheItem from a string as read from the cache that was
+   * written to disk. */
+  static GeoIpCacheItem fromString(QString cacheString);
+
+private:
+  GeoIp     _geoip;      /**< Cached GeoIp item. */
+  QDateTime _timestamp;  /**< Time this item was cached. */
+};
+
+#endif
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,77 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoiprequest.cpp
+** \version $Id: geoiprequest.cpp 2362 2008-02-29 04:30:11Z edmanm $
+** \brief A formatted request for GeoIP information for one or more IPs
+*/
+
+#include <zlibbytearray.h>
+
+#include "geoiprequest.h"
+
+
+/** Creates an HTTP POST header for this request, based on the 
+ * Host, Page, and content-length values. */
+QHttpRequestHeader
+GeoIpRequest::createHeader() const
+{
+  QHttpRequestHeader header("POST", _page, 1, 1);
+  
+  if (!_host.isEmpty())
+    header.setValue("Host", _host);
+  header.setContentType("application/x-www-form-urlencoded");
+  header.setContentLength(_request.length());
+  header.setValue("Connection", "close");
+
+  if (ZlibByteArray::isZlibAvailable()) {
+    QString acceptEncodings = "deflate, x-deflate";
+    if (ZlibByteArray::isGzipSupported())
+      acceptEncodings += ", gzip, x-gzip";
+    header.setValue("Accept-Encoding", acceptEncodings);
+  }
+
+  return header;
+}
+
+/** Sets the list of IPs whose geo information we want to request. */
+void
+GeoIpRequest::setRequest(const QList<QHostAddress> &ips)
+{
+  _request = "ip=";
+  int ipcount = ips.size();
+
+  /* Add each IP to a comma-delimited list. */
+  for (int i = 0; i < ipcount; i++) {
+    _request.append(ips.at(i).toString());
+    if (i < ipcount-1) {
+      _request.append(",");
+    }
+  }
+  _ips = ips;
+}
+
+/** Formats the request as an HTTP POST request. */
+QByteArray
+GeoIpRequest::request() const
+{
+  /* Create the header and append the request content. */
+  QString request = createHeader().toString() + _request;
+  return request.toAscii();
+}
+
+/** Returns true if this request contains <b>ip</b>. */
+bool
+GeoIpRequest::contains(const QHostAddress &ip) const
+{
+  return _ips.contains(ip);
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoiprequest.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,61 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoiprequest.h
+** \version $Id: geoiprequest.h 2362 2008-02-29 04:30:11Z edmanm $
+** \brief A formatted request for GeoIP information for one or more IPs
+*/
+
+#ifndef _GEOIPREQUEST_H
+#define _GEOIPREQUEST_H
+
+#include <QList>
+#include <QString>
+#include <QByteArray>
+#include <QHostAddress>
+#include <QHttpRequestHeader>
+
+
+class GeoIpRequest
+{
+public:
+  /** Constructor */
+  GeoIpRequest(int id) : _id(id) {}
+  
+  /** Sets the Host: field in this request's header. */
+  void setHost(const QString &host) { _host = host; }
+  /** Sets the page path in this request's header. */
+  void setPage(const QString &page) { _page = page; }
+  /** Sets the list of IPs whose geo information we want to request. */
+  void setRequest(const QList<QHostAddress> &ips);
+  /** Returns true if this request contains <b>ip</b>. */
+  bool contains(const QHostAddress &ip) const;
+
+  /** Returns the request's identifier. */
+  int id() const { return _id; }
+  /** Returns the number of IP addresses contained in this request. */
+  int size() const { return _ips.size(); }
+  /** Formats the request as an HTTP POST request */
+  QByteArray request() const;
+  
+private:
+  /** Creates an HTTP header for this request. */
+  QHttpRequestHeader createHeader() const;
+  
+  int _id;          /**< Request identifier */
+  QString _host;    /**< Host: field value. */
+  QString _page;    /**< Page giving us the geo ip information. */
+  QString _request; /**< Formatted Geo IP request string. */
+  QList<QHostAddress> _ips; /**< List of IP addresses in this request. */
+};
+
+#endif
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,126 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipresponse.cpp
+** \version $Id: geoipresponse.cpp 2533 2008-04-25 04:50:43Z edmanm $
+** \brief Parses a response to a previous GeoIP request
+*/
+
+#include <QStringList>
+#include <zlibbytearray.h>
+
+#include "geoipresponse.h"
+
+/** Status code for a successful HTTP request. */
+#define STATUS_HTTP_OK                 200
+/** Status code for content encoding errors. */
+#define STATUS_CONTENT_ENCODING_ERR    601
+/** Status code for transfer encoding errors. */
+#define STATUS_TRANSFER_ENCODING_ERR   602
+
+
+/** Constructor. Parses the response data for an HTTP header and Geo IP
+ * information. */
+GeoIpResponse::GeoIpResponse(QByteArray response)
+{
+  QString errmsg;
+  
+  /* Parse out the header */
+  int headerPos = response.indexOf("\r\n\r\n");
+  _header = QHttpResponseHeader(QString(response.mid(0, headerPos)));
+
+  /* Parse out the Geo IP information, if any was included. */
+  if (headerPos > 0 && _header.statusCode() == STATUS_HTTP_OK) {
+    QByteArray content = response.mid(headerPos+4);
+ 
+    if (_header.hasKey("Transfer-Encoding")) {
+      QString encoding = _header.value("Transfer-Encoding");
+      if (encoding == "chunked") {
+        content = decodeChunked(content);
+        if (content.isEmpty()) {
+          _header.setStatusLine(STATUS_TRANSFER_ENCODING_ERR,
+            QString("Failed to decode chunked response"));
+          return;
+        }
+      } else {
+        _header.setStatusLine(STATUS_TRANSFER_ENCODING_ERR,
+          QString("Unknown transfer encoding '%1'").arg(encoding));
+        return;
+      }
+    }
+ 
+    if (_header.hasKey("Content-Encoding")) {
+      ZlibByteArray::CompressionMethod method;
+      QString encoding = _header.value("Content-Encoding");
+      if (encoding == "gzip" || encoding == "x-gzip") {
+        method = ZlibByteArray::Gzip;
+      } else if (encoding == "deflate" || encoding == "x-deflate") {
+        method = ZlibByteArray::Zlib;
+      } else if (encoding == "text/plain") {
+        method = ZlibByteArray::None;
+      } else {
+        _header.setStatusLine(STATUS_CONTENT_ENCODING_ERR,
+          QString("Unknown content encoding '%1'").arg(encoding));
+        return;
+      }
+ 
+      content = ZlibByteArray::uncompress(content, method, &errmsg);
+      if (content.isEmpty()) {
+        _header.setStatusLine(STATUS_CONTENT_ENCODING_ERR,
+          QString("Content decoding using method '%1' failed: %2")
+                                       .arg(encoding).arg(errmsg));
+        return;
+      }
+    }
+
+    /* Parse the Geo IP information in each line */
+    QStringList lines = QString(content).split("\n");
+    foreach (QString line, lines) {
+      GeoIp geoip = GeoIp::fromString(line);
+      if (!geoip.isEmpty())
+        _geoips << geoip;
+    }
+  }
+}
+
+/** Decodes a <b>chunked</b> transfer encoding. Returns the unchunked 
+ * result on success, or an empty QByteArray if decoding fails. */
+QByteArray
+GeoIpResponse::decodeChunked(QByteArray chunked)
+{
+  QByteArray unchunked;
+  QString sizeString;
+  int eol, chunkedlen, chunksize, offset = 0;
+  bool ok;
+ 
+  chunkedlen = chunked.length();
+  while (offset < chunkedlen) {
+    eol = chunked.indexOf("\r\n", offset);
+    if (eol < 0)
+      return QByteArray();
+    sizeString = QString::fromAscii(chunked.mid(offset, eol-offset));
+    offset = eol + 2; /* Skip past the CRLF */
+    
+    if (sizeString.indexOf(";") >= 0)
+      sizeString.truncate(sizeString.indexOf(";")); 
+    chunksize = sizeString.toInt(&ok, 16);
+    if (!ok || chunksize > chunkedlen - offset)
+      return QByteArray();
+    if (!chunksize)
+      break; /* Last chunk. Ignore the trailer. */
+    
+    unchunked.append(chunked.mid(offset, chunksize));
+    offset += chunksize;
+    offset += 2; /* CRLF after each chunk */
+  }
+  return unchunked;
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/geoipresponse.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,51 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipresponse.h
+** \version $Id: geoipresponse.h 2362 2008-02-29 04:30:11Z edmanm $
+** \brief Parses a response to a previous GeoIP request
+*/
+
+#ifndef _GEOIPRESPONSE_H
+#define _GEOIPRESPONSE_H
+
+#include <QList>
+#include <QByteArray>
+#include <QHttpResponseHeader>
+
+#include "geoip.h"
+
+
+class GeoIpResponse
+{
+public:
+  /** Constructor. Parses the response data for an HTTP header and Geo IP
+   * information.  */
+  GeoIpResponse(QByteArray response);
+
+  /** Returns the HTTP status code for this response. */
+  int statusCode() { return _header.statusCode(); }
+  /** Returns the HTTP status message for this response. */
+  QString statusMessage() { return _header.reasonPhrase(); }
+  /** Returns the Geo IP information contained in this response. */
+  QList<GeoIp> geoIps() { return _geoips; }
+  
+private:
+  /** Decodes a <b>chunked</b> transfer encoding. Returns the unchunked 
+   * result on success, or an empty QByteArray if decoding fails. */
+  QByteArray decodeChunked(QByteArray chunked);
+  
+  QHttpResponseHeader _header; /**< HTTP response header. */
+  QList<GeoIp> _geoips;        /**< Geo IP information in this response. */
+};
+
+#endif
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.cpp
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.cpp	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.cpp	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,263 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipresolver.cpp
+** \version $Id: geoipresolver.cpp 2429 2008-03-21 02:27:36Z edmanm $
+** \brief Requests GeoIP information and caches the result
+*/
+
+#include <torsocket.h>
+#include <vidalia.h>
+#include "webgeoipresolver.h"
+#include "config.h"
+
+#if defined(USE_QSSLSOCKET)
+#include <torsslsocket.h>
+#endif
+
+/** Host for the geo ip information. */ 
+#define GEOIP_HOST    "geoip.vidalia-project.net"
+/** The non-encrypted GeoIP service lives on port 80. */
+#define GEOIP_PORT      80
+/** The SSL GeoIP service runs on port 1443 (443 was taken). */
+#define GEOIP_SSL_PORT  1443
+/** Page that we request the geo ip information from. */
+#define GEOIP_PAGE    "/cgi-bin/geoip"
+
+
+/** Default constructor. */
+WebGeoIpResolver::WebGeoIpResolver()
+{
+  _socksAddr = QHostAddress::LocalHost;
+  _socksPort = 9050;
+
+#if defined(USE_QSSLSOCKET)
+  if (! QSslSocket::addDefaultCaCertificates(":/geoip/cacert_root.crt"))
+    vWarn("Failed to add the GeoIP CA certificate to the default CA "
+          "certificate database.");
+#endif
+}
+
+/** Sets the address and port of Tor, through which GeoIP requests will be
+ * made. */
+void
+WebGeoIpResolver::setSocksHost(QHostAddress addr, quint16 port)
+{
+  _socksAddr = addr;
+  _socksPort = port;
+}
+
+/** Resolves <b>ip</b> to geographic information if it is cached. A resolved()
+ * signal will be emitted and true returned if we have cached geographic
+ * information for <b>ip</b>. Otherwise, this returns false. */
+bool
+WebGeoIpResolver::resolveFromCache(QHostAddress ip)
+{
+  if (_cache.contains(ip)) {
+    emit resolved(-1, QList<GeoIp>() << _cache.geoip(ip));
+    return true;
+  }
+  return false;
+}
+
+/** Resolves a list of IPs to a geographic location, but only those which are
+ * cached. Returns a list of IPs that were not in the cache. */
+QList<QHostAddress>
+WebGeoIpResolver::resolveFromCache(QList<QHostAddress> ips)
+{
+  QList<GeoIp> cached;
+
+  /* Build a list of which IPs have cached GeoIp information */
+  foreach (QHostAddress ip, ips) {
+    if (_cache.contains(ip)) {
+      ips.removeAt(ips.indexOf(ip));
+      cached << _cache.geoip(ip);
+    }
+  }
+
+  /* If any were cached, emit their results now */
+  if (cached.size() > 0) {
+    vInfo("Resolved %1 GeoIP entries from cache.").arg(ips.size());
+    emit resolved(-1, cached);
+  }
+  return ips;
+}
+
+/** Resolves a single IP to a geographic location. */
+int
+WebGeoIpResolver::resolve(QHostAddress ip)
+{
+  return resolve(QList<QHostAddress>() << ip);
+}
+
+/** Called when the socket has connected to the Geo IP host. */
+void
+WebGeoIpResolver::connected()
+{
+  /* Find the socket and request for whoever called this slot */ 
+  QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
+  if (!_requestList.contains(socket)) {
+    return;
+  }
+  GeoIpRequest *req = (GeoIpRequest *)_requestList.value(socket);
+  vInfo("Connected to the GeoIP host. Sending request for %1 uncached "
+        "GeoIP entries. (request id %2)").arg(req->size()).arg(req->id());
+
+  /* Send the request */
+  socket->write(req->request());
+}
+
+/** Called when the socket has disconnected from the Geo IP host. */
+void
+WebGeoIpResolver::disconnected()
+{
+  /* Find the socket and request for whoever called this slot */ 
+  QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
+  if (!_requestList.contains(socket)) {
+    return;
+  }
+  GeoIpRequest *request = (GeoIpRequest *)_requestList.take(socket);
+
+  /* Read and parse the response */
+  GeoIpResponse response = GeoIpResponse(socket->readAll());
+
+  /* Check the response code and see what we got */
+  if (response.statusCode() == 200) {
+    /* We got a 200 OK, so get the Geo IP information and cache the results */
+    int numCached = 0, i = 0;
+    QList<GeoIp> geoips = response.geoIps();
+    foreach (GeoIp geoip, geoips) {
+      QHostAddress ip = geoip.ip();
+      
+      if (request->contains(ip)) {
+        /* This is a requested geoip item, so if it wasn't cached, then 
+         * cache it now. */
+        if (!_cache.contains(ip)) {
+          _cache.cache(geoip);
+          numCached++;
+        }
+        i++;
+      } else {
+        /* This item wasn't requested, so remove it. According to the Qt docs,
+         * this is safe to do inside the foreach() loop because, "Qt
+         * automatically takes a copy of the container when it enters a
+         * foreach loop. If you modify the container as you are iterating,
+         * that won't affect the loop." */
+        vWarn("Received a GeoIP entry for IP address %1 that was not included "
+              "in the initial request. (request id %2)").arg(ip)
+                                                        .arg(request->id());
+        geoips.removeAt(i);
+      }
+    }
+    /* If new results were cached, save them to disk */
+    if (numCached > 0) {
+      _cache.saveToDisk();
+    }
+    /* Emit the results */
+    vInfo("Parsed %1 entries from the GeoIP response. (request id %2)")
+                                 .arg(geoips.size()).arg(request->id());
+    emit resolved(request->id(), geoips);
+  } else {
+    /* We failed to get the Geo IP information, so emit resolveFailed and
+     * include the HTTP status message. */
+    vWarn("GeoIP resolution failed (request id %1): %2").arg(request->id())
+                                             .arg(response.statusMessage());
+    emit resolveFailed(request->id(), response.statusMessage());
+  }
+  /* Close the socket and clean up */
+  socket->close();
+  delete socket;
+  delete request;
+}
+
+/** Called when an error has occurred requesting Geo IP information. */
+void
+WebGeoIpResolver::socketError(QString errorString)
+{
+  /* Find the socket and request for whoever called this slot */ 
+  QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
+  if (!_requestList.contains(socket)) {
+    return;
+  }
+  
+  /* We expect a remote host to close the socket, because that's how the HTTP
+   * server tells us he's done talkig to us. */
+  if (socket->error() != QAbstractSocket::RemoteHostClosedError) {
+    /* Emit the failure and clean up */
+    GeoIpRequest *request = (GeoIpRequest *)_requestList.take(socket);
+    emit resolveFailed(request->id(), errorString);
+    socket->abort();
+    vWarn("GeoIP request socket error (request id %1): %2").arg(request->id())
+                                                           .arg(errorString);
+    delete socket;
+    delete request;
+  }
+}
+
+/** Creates an HTTP request for Geo IP information. */
+GeoIpRequest*
+WebGeoIpResolver::createRequest(QList<QHostAddress> ips)
+{
+  static int id = -1;
+  GeoIpRequest *request = new GeoIpRequest(++id);
+  request->setHost(GEOIP_HOST);
+  request->setPage(GEOIP_PAGE);
+  request->setRequest(ips);
+  return request;
+}
+
+/** Resolves a list of IPs to a geographic location. */
+int
+WebGeoIpResolver::resolve(QList<QHostAddress> ips)
+{
+  /* Resolve the cached IPs and get a list of IPs that still need to be
+   * resolved to a lat and long. */
+  QList<QHostAddress> uncached = resolveFromCache(ips);
+  if (!uncached.size()) {
+    return -1;
+  }
+
+  /* Create a socket used to request the geo ip information. */
+#if defined(USE_QSSLSOCKET)
+  TorSslSocket *socket = new TorSslSocket(_socksAddr, _socksPort);
+#else
+  TorSocket *socket = new TorSocket(_socksAddr, _socksPort);
+#endif
+
+  connect(socket, SIGNAL(connectedToRemoteHost()), this, SLOT(connected()),
+          Qt::QueuedConnection);
+  connect(socket, SIGNAL(socketError(QString)), 
+          this,   SLOT(socketError(QString)),
+          Qt::QueuedConnection);
+  connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()),
+          Qt::QueuedConnection);
+  GeoIpRequest *request = createRequest(ips);
+  _requestList.insert(socket, request);
+  
+  /* Connect so we can send our request and return the request ID. */
+#if defined(USE_QSSLSOCKET)
+  if (TorSslSocket::supportsSsl()) {
+    vInfo("Opening an SSL connection to the GeoIP host at %1:%2 (request id %3)")
+                          .arg(GEOIP_HOST).arg(GEOIP_SSL_PORT).arg(request->id());
+    socket->connectToRemoteHost(GEOIP_HOST, GEOIP_SSL_PORT, true);
+  } else {
+    vInfo("Opening an unencrypted connection to the GeoIP host at %1:%2 "
+          "(request id %3)").arg(GEOIP_HOST).arg(GEOIP_PORT).arg(request->id());
+    socket->connectToRemoteHost(GEOIP_HOST, GEOIP_PORT, false);
+  }
+#else
+  vInfo("Opening an unencrypted connection to the GeoIP host at %1:%2 "
+        "(request id %3)").arg(GEOIP_HOST).arg(GEOIP_PORT).arg(request->id());
+  socket->connectToRemoteHost(GEOIP_HOST, GEOIP_PORT);
+#endif
+  return request->id();
+}
+

Added: vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.h
===================================================================
--- vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.h	                        (rev 0)
+++ vidalia/branches/exit-country/src/vidalia/geoip/webgeoipresolver.h	2008-08-05 13:18:56 UTC (rev 2940)
@@ -0,0 +1,82 @@
+/*
+**  This file is part of Vidalia, and is subject to the license terms in the
+**  LICENSE file, found in the top level directory of this distribution. If you
+**  did not receive the LICENSE file with this file, you may obtain it from the
+**  Vidalia source package distributed by the Vidalia Project at
+**  http://www.vidalia-project.net/. No part of Vidalia, including this file,
+**  may be copied, modified, propagated, or distributed except according to the
+**  terms described in the LICENSE file.
+*/
+
+/*
+** \file geoipresolver.h
+** \version $Id: geoipresolver.h 2429 2008-03-21 02:27:36Z edmanm $
+** \brief Requests GeoIP information and caches the result
+*/
+
+#ifndef _WEBGEOIPRESOLVER_H
+#define _WEBGEOIPRESOLVER_H
+
+#include <QObject>
+#include <QList>
+#include <QHash>
+#include <QString>
+#include <QHostAddress>
+
+#include <geoip.h>
+#include "geoipcache.h"
+#include "geoiprequest.h"
+#include "geoipresponse.h"
+
+
+class WebGeoIpResolver : public QObject
+{
+  Q_OBJECT
+
+public:
+  /** Default constructor. */
+  WebGeoIpResolver();
+  
+  /** Sets the address and port of Tor, through which GeoIP requests will be
+   * made. */
+  void setSocksHost(QHostAddress addr, quint16 port);
+  /** Resolves a single IP to a geographic location. */
+  int resolve(QHostAddress ip);
+  /** Resolves a list of IPs to a geographic location. */
+  int resolve(QList<QHostAddress> ips);
+  /** Resolves <b>ip</b> to geographic information only if it is cached. */
+  bool resolveFromCache(QHostAddress ip);
+  /** Resolves a list of IPs to a geographic location, but only those which
+   * are cached. Returns a list of which IPs were not cached. */
+  QList<QHostAddress> resolveFromCache(QList<QHostAddress> ips);
+
+signals:
+  /** Emitted when a list of IPs have been resolved to lat/long. */
+  void resolved(int id, QList<GeoIp> geoips);
+  /** Emitted when a resolve has failed. */
+  void resolveFailed(int id, QString errorString);
+
+private slots:
+  /** Called when the socket has connected to the Geo IP host. */
+  void connected();
+  /** Called when the socket has disconnected from the Geo IP host. */
+  void disconnected();
+  /** Called when an error has occurred getting the Geo IP information. */
+  void socketError(QString errorString);
+
+private:
+  /** Creates an HTTP request for Geo IP information. */
+  GeoIpRequest* createRequest(QList<QHostAddress> ips);
+
+  /**< Cached GeoIp objects. */
+  GeoIpCache  _cache;
+  /**< List of sockets used for requests. */
+  QHash<QAbstractSocket *,GeoIpRequest*> _requestList;
+  /** Tor's SocksListenAddress. */
+  QHostAddress _socksAddr;
+  /** Tor's SocksPort. */
+  quint16 _socksPort;
+};
+
+#endif
+