Commit 9b490cf7 authored by Matt Edman's avatar Matt Edman
Browse files

Add support for automatically downloading a list of bridges via HTTPS,
displaying the progress of the request, and adding any new bridge
addresses to the list in the Network settings page.


svn:r3729
parent 20cec3be
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
0.2.1   xx-xxx-2009
  o Add a "Find Bridges Now" button that will attempt to automatically
    download a set of bridge addresses and add them to the list of bridges
    in the Network settings page.
  o Tolerate bridge addresses that do not specify a port number, since Tor
    now defaults to using port 443 in such cases.
  o Renamed the 'make win32-installer' CMake target to 'make dist-win32'
+5 −0
Original line number Diff line number Diff line
@@ -72,6 +72,8 @@ set(vidalia_SRCS ${vidalia_SRCS}
  config/abstracttorsettings.cpp
  config/advancedpage.cpp
  config/appearancepage.cpp
  config/bridgedownloader.cpp
  config/bridgedownloaderprogressdialog.cpp
  config/bridgeusagedialog.cpp
  config/configdialog.cpp
  config/configpagestack.cpp
@@ -98,6 +100,8 @@ qt4_wrap_cpp(vidalia_SRCS
  config/abstracttorsettings.h
  config/advancedpage.h
  config/appearancepage.h
  config/bridgedownloader.h
  config/bridgedownloaderprogressdialog.h
  config/bridgeusagedialog.h
  config/configdialog.h
  config/configpage.h
@@ -245,6 +249,7 @@ qt4_wrap_ui(vidalia_SRCS
  bwgraph/bwgraph.ui
  config/advancedpage.ui
  config/appearancepage.ui
  config/bridgedownloaderprogressdialog.ui
  config/bridgeusagedialog.ui
  config/configdialog.ui
  config/generalpage.ui
+154 −0
Original line number Diff line number Diff line
/*
**  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 bridgedownloader.cpp
** \version $Id$
** \brief Downloads a list of new bridge addresses via HTTPS
*/

#include <QSslSocket>
#include <vidalia.h>
#include <bridgedownloader.h>

#define BRIDGEDB_HOST  "bridges.torproject.org"
#define BRIDGEDB_PORT  443


BridgeDownloader::BridgeDownloader(QObject *parent)
  : QObject(parent),
    _requestId(0)
{
  _https = new QHttp(BRIDGEDB_HOST,  QHttp::ConnectionModeHttps,
                     BRIDGEDB_PORT, this);

  connect(_https, SIGNAL(stateChanged(int)),
          this, SLOT(httpsStateChanged(int)));
  connect(_https, SIGNAL(requestFinished(int, bool)),
          this, SLOT(httpsRequestFinished(int, bool)));
  connect(_https, SIGNAL(dataReadProgress(int, int)),
          this, SIGNAL(downloadProgress(int, int)));
  connect(_https, SIGNAL(sslErrors(QList<QSslError>)),
          this, SLOT(sslErrors(QList<QSslError>)));  
}

bool
BridgeDownloader::downloadBridges(BridgeDownloadMethod method)
{
  if (! isMethodSupported(method))
    return false;

  switch (method) {
    case DownloadMethodHttps:
      startHttpsDownload();
      break;
 
    default:
      break;
  }
  return true;
}

bool
BridgeDownloader::isMethodSupported(BridgeDownloadMethod method)
{
  switch (method) {
    case DownloadMethodHttps:
      return QSslSocket::supportsSsl();

    default:
      break;
  }
  return false;
}

void
BridgeDownloader::startHttpsDownload()
{  
  emit statusChanged(tr("Starting HTTPS bridge request..."));
  emit downloadProgress(0, 0);

  _requestId = _https->get("/?format=plain");
  vInfo("Sending an HTTPS bridge request to %1:%2 (id %3).").arg(BRIDGEDB_HOST)
                                                            .arg(BRIDGEDB_PORT)
                                                            .arg(_requestId);
}

void
BridgeDownloader::cancelBridgeRequest()
{
  _https->abort();
}

void
BridgeDownloader::httpsStateChanged(int state)
{
  switch (state) {
    case QHttp::Connecting:
      emit statusChanged(tr("Connecting to %1:%2...").arg(BRIDGEDB_HOST)
                                                     .arg(BRIDGEDB_PORT));
      break;

    case QHttp::Sending:
      emit statusChanged(tr("Sending an HTTPS request for bridges..."));
      break;

    case QHttp::Reading:
      emit statusChanged(tr("Downloading a list of bridges..."));
      break;

    default:
      break;
  }
}

void
BridgeDownloader::httpsRequestFinished(int id, bool error)
{
  if (id != _requestId)
    return;

  if (error) {
    QString errorString = _https->errorString();
    vWarn("Bridge request failed (id %1): %2").arg(id).arg(errorString);
  
    emit bridgeRequestFailed(errorString);
  } else {
    QByteArray response = _https->readAll();
    vInfo("Bridge request complete (id %1): received %2 bytes.").arg(id)
                                                   .arg(response.size());

    QStringList bridges, lines = QString(response).split("\n");
    foreach (QString line, lines) {
      line = line.trimmed();
      if (line.startsWith("bridge ", Qt::CaseInsensitive))
        bridges << line;
    }
    emit bridgeRequestFinished(bridges);
  }
  _https->close();
}

void
BridgeDownloader::sslErrors(const QList<QSslError> &sslErrors)
{
  QString errorString;
  QStringList errorStrings;

  vWarn("%1 SSL error(s) when requesting bridge information (id %2):")
                                                      .arg(sslErrors.size())
                                                      .arg(_requestId);
  foreach (QSslError sslError, sslErrors) {
    errorString = sslError.errorString();
    errorStrings << errorString;
    vWarn("  SSL Error: %1").arg(errorString);
  }
}
+118 −0
Original line number Diff line number Diff line
/*
**  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 bridgedownloader.h
** \version $Id$
** \brief Downloads a list of new bridge addresses via HTTPS
*/

#ifndef _BRIDGEDOWNLOADER_H
#define _BRIDGEDOWNLOADER_H

#include <QHttp>
#include <QSslError>
#include <QStringList>


class BridgeDownloader : public QObject
{
  Q_OBJECT

public:
  /** Available bridge download methods. */
  enum BridgeDownloadMethod {
    DownloadMethodHttps, /** Download via an HTTPS connection. */
  };

  /** Default constructor.
   */
  BridgeDownloader(QObject *parent = 0);

  /** Initiates a request for a set of bridges using the specified
   * download <b>method</b>. Returns true if the request was initiated
   * successfully, or false on error.
   */
  bool downloadBridges(BridgeDownloadMethod method);

  /** Returns true if <b>method</b> is supported by the currently
   * available Qt libraries.
   */
  static bool isMethodSupported(BridgeDownloadMethod method);

public slots:
  /** Cancels any pending bridge download requests.
   */
  void cancelBridgeRequest();

signals:
  /** Emitted when the underlying QHttp object reads data from an HTTPS
   * response. <b>done</b> indicates how many bytes out of <b>total</b>
   * have been read so far. Note that <b>total</b> may be 0 if the expected
   * total size of the response is not known.
   */
  void downloadProgress(int done, int total);

  /** Emitted when the status of the bridge request changes. <b>status</b>
   * describes the new current state of the request.
   */
  void statusChanged(const QString &status);

  /** Emitted when the previous request for bridge addresses completes
   * successfully. The QStringList <b>bridges</b> contains a (possibly empty)
   * list of bridge addresses parsed from the received response.
   */
  void bridgeRequestFinished(const QStringList &bridges);

  /** Emitted when the previous request for bridge addresses fails. The
   * QString <b>error</b> is a human-readable string describing the error
   * encountered.
   */
  void bridgeRequestFailed(const QString &error);

private slots:
  /** Called when the state of the underlying QHttp object changes. A
   * statusChanged() signal is emitted with the appropriate text
   * describing the new state of the request.
   */
  void httpsStateChanged(int state); 

  /** Called when the underlying QHttp object used to make the bridge
   * request completes. <b>error</b> is set to false if the request was
   * successful, or true if the request failed. If <b>id</b> does not
   * match the request ID previously returned by QHttp::get(), then the
   * signal is ignored since it is the result of a close() or abort()
   * request.
   */
  void httpsRequestFinished(int id, bool error);

  /** Called when the HTTPS connection encounters one or more
   * <b>sslErrors</b>. Currently the errors are just logged and
   * bridgeRequestFailed() is <i>not</i> emitted, since QHttp will also
   * emit 
   */
  void sslErrors(const QList<QSslError> &sslErrors);

private:
  /** Initiates an HTTPS connection to bridges.torproject.org to start
   * downloading a set of bridges.
   */
  void startHttpsDownload();

  /** Used to connect to the bridge database, send an HTTPS request for
   * new bridge addresses and then read the response. */
  QHttp* _https;
  
  /** Unique numeric identifier of the current bridge request. */
  int _requestId;
};

#endif
+91 −0
Original line number Diff line number Diff line
/*
**  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 bridgedownloaderprogressdialog.cpp
** \version $Id$
** \brief Displays the progress of a request for bridge addresses
*/

#include <QTimer>

#include "bridgedownloaderprogressdialog.h"


BridgeDownloaderProgressDialog::BridgeDownloaderProgressDialog(QWidget *parent)
  : QDialog(parent)
{
  ui.setupUi(this);
 
  connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton *)),
          this, SLOT(buttonClicked(QAbstractButton *)));

  setModal(true);
}

void
BridgeDownloaderProgressDialog::setVisible(bool visible)
{
  if (visible) {
    ui.progressBar->setRange(0, 0);
    ui.buttonBox->setStandardButtons(QDialogButtonBox::Cancel);
  }
  QDialog::setVisible(visible);
}

void
BridgeDownloaderProgressDialog::setStatus(const QString &status)
{
  ui.lblStatus->setText(status);
}

void
BridgeDownloaderProgressDialog::setDownloadProgress(int done, int total)
{
  ui.progressBar->setRange(0, total);
  ui.progressBar->setValue(done);
}

void
BridgeDownloaderProgressDialog::bridgeRequestFinished(const QStringList &bridges)
{
  Q_UNUSED(bridges);
  
  accept();
}

void
BridgeDownloaderProgressDialog::bridgeRequestFailed(const QString &error)
{
  ui.lblStatus->setText(tr("Unable to download bridges: %1").arg(error));

  ui.progressBar->setRange(0, 1);
  ui.progressBar->setValue(1);

  ui.buttonBox->setStandardButtons(QDialogButtonBox::Cancel
                                     | QDialogButtonBox::Retry
                                     | QDialogButtonBox::Help);
}

void
BridgeDownloaderProgressDialog::buttonClicked(QAbstractButton *button)
{
  int standardButton = ui.buttonBox->standardButton(button);
  if (standardButton == QDialogButtonBox::Retry) {
    setStatus(tr("Retrying bridge request..."));
    setDownloadProgress(0, 0);
    ui.buttonBox->setStandardButtons(QDialogButtonBox::Cancel);
  
    QTimer::singleShot(1000, this, SIGNAL(retry()));
  } else {
    done(standardButton);
  }
}
Loading