Changeset 89:403d0d024752 in SMSSender


Ignore:
Timestamp:
May 29, 2010 8:21:02 PM (10 years ago)
Author:
Sämy Zehnder <saemy.zehnder@…>
Branch:
3.0
Convert:
svn:3639001d-8e34-449c-bb86-3782b86c4877/branches/3.0@92
Message:
  • Using SVersion instead of fixed strings; The version of the library and main app is now read from its pro-files.
  • Show account name instead of gateway name in the account selection list in the main form.
  • Fixes problem on windows, which did not find the deployed libraries.
  • Do not use the pkcs12 packaging for the public/private keys of the client certificate because this requires the openssl libraries to be deployed which results in a bigger install file. Read them from a .crt file instead. This can be done by the QSslCertificate and QSslKey classes which use the built in openssl version from qt.
  • Install the gorrión.-RootCA certificate in the SHttpHelper constructor and give access to the sslsocket of QHttp.
  • Removed version control of crypto++. Instead a file is lying in this folder which describes the download/build process.
Files:
6 added
374 deleted
18 edited
3 moved

Legend:

Unmodified
Added
Removed
  • gateways/SwisscomXtraZone/lib/SwisscomXtraZone.qrc

    r88 r89  
    22    <qresource prefix="/certs">
    33        <file>SwisscomRootCA1.crt</file>
    4         <file>GorrionRootCA.crt</file>
    5         <file>SMSSender_XtraZone.p12</file>
     4        <file>SMSSender_XtraZone.crt</file>
    65    </qresource>
    76    <qresource prefix="/images">
  • gateways/SwisscomXtraZone/src/business/BCAccount.cpp

    r88 r89  
    3333#include <snumber.h>
    3434#include <typeconvert.h>
    35 #include <thirdparty/SslCertificate.h>
    3635
    3736#include "BCGateway.h"
     
    9190
    9291
    93     // this replaces the internal QTcpSocket QHttp uses; unfortunately
    94     // we cannot reuse that one because Qt does not provide an accessor
    95     // for it
    96     QHttp* http = httpHelper->http();
    97     sslSocket_ = new QSslSocket(http);
    98 
    9992    /* Insert the swisscom-mobile.ch certificate to allow propper loading of the websites... */
    10093    QFile certFile(":/certs/SwisscomRootCA1.crt");
     
    10295    QSslCertificate cert(&certFile, QSsl::Pem);
    10396    Q_ASSERT(cert.isValid());
    104     sslSocket_->addCaCertificate(cert);
    105 
    106     http->setSocket(sslSocket_);
     97    httpHelper->httpSocket()->addCaCertificate(cert);
    10798
    10899    _httpHelpers_.insert(thread(), httpHelper);
     
    163154    return rx.cap(1);
    164155}
     156void BCAccount::_installAnticaptchaClientCertificate(SHttpHelper* http) {
     157    /* Set the client certificate */
     158    QFile clientCrtFile(":/certs/SMSSender_XtraZone.crt");
     159    clientCrtFile.open(QIODevice::ReadOnly);
     160    QSslCertificate clientCrt(&clientCrtFile, QSsl::Pem);
     161    Q_ASSERT(clientCrt.isValid());
     162    http->httpSocket()->setLocalCertificate(clientCrt);
     163
     164    http->httpSocket()->setPrivateKey(":/certs/SMSSender_XtraZone.crt");
     165    Q_ASSERT(!http->httpSocket()->privateKey().isNull());
     166}
    165167QString BCAccount::_doAnticaptcha(const QString& token) {
    166168    setStatus(BCAccount::Action::DoingAnticaptcha);
    167169
    168     SHttpHelper* http = new SHttpHelper(true);
    169 
    170     /* Set the client certificate */
    171     QFile certFile(":/certs/SMSSender_XtraZone.p12");
    172     certFile.open(QIODevice::ReadOnly);
    173     Q_ASSERT(certFile.isOpen());
    174 
    175     PKCS12Certificate pkcs12Cert(&certFile, "");
    176     Q_ASSERT(!pkcs12Cert.isNull());
    177 
    178     QSslSocket* socket = new QSslSocket(http);
    179     socket->setPrivateKey(pkcs12Cert.key());
    180     socket->setLocalCertificate(pkcs12Cert.certificate());
    181 
    182     /* Trust the gorrion root CA */
    183     QFile caFile(":/certs/GorrionRootCA.crt");
    184     caFile.open(QIODevice::ReadOnly);
    185     QSslCertificate caCert(&caFile, QSsl::Pem);
    186     Q_ASSERT(caCert.isValid());
    187     socket->addCaCertificate(caCert);
    188 
    189     http->http()->setSocket(socket);
    190 
    191 
    192     /* Do the anticaptcha stuff */
     170    SHttpHelper* http  = new SHttpHelper(true);
     171    _installAnticaptchaClientCertificate(http);
     172
     173    /* Do the anticaptcha request */
    193174    QString html = http->syncGet(ANTICAPTCHA_PAGE + "?mode=xtra&token=" + token);
    194175    delete http;
     
    358339    /* Tell valid captcha to the anticaptcha service */
    359340    SHttpHelper* http = new SHttpHelper(true);
     341    _installAnticaptchaClientCertificate(http);
    360342    connect(http, SIGNAL(done(bool)), http, SLOT(deleteLater()));
    361343    http->asyncGet(ANTICAPTCHA_PAGE + "?mode=xtra&validcaptcha=" + captcha);
  • gateways/SwisscomXtraZone/src/business/BCAccount.h

    r88 r89  
    1818#ifndef SWISSCOMXTRAZONE_BCACCOUNT_H_
    1919#define SWISSCOMXTRAZONE_BCACCOUNT_H_
    20 
    21 #include <QSslSocket>
    2220
    2321#include <abstract/abstractloginaccount.h>
     
    8179    const int    MAX_ANTICAPTCHA_TRY_COUNT;
    8280
    83     QSslSocket*  sslSocket_;
    8481    QMap<QThread*, SHttpHelper*> _httpHelpers_;
    8582    QString      _lastMainHtml_;
     
    9693
    9794    QString _getCaptchaToken();
     95    void    _installAnticaptchaClientCertificate(SHttpHelper* http);
    9896    QString _doAnticaptcha(const QString& token);
    9997
  • gateways/SwisscomXtraZone/src/main.cpp

    r87 r89  
    3434}
    3535SVersion Library::version() const {
    36     return SVersion(1, 0, 0, 0); // TODO: load this from build settings
     36    return SVersion(LIB_VERSION);
    3737}
    3838
  • gateways/SwisscomXtraZone/src/main.h

    r87 r89  
    3838
    3939public:
    40     Library(){}
     40    Library() {};
    4141    static ILibrary* instance();
    4242
  • gateways/SwisscomXtraZone/swisscomxtrazone.pro

    r88 r89  
    1 VERSION = 2.0.0
     1VERSION = 1.0.0.0
     2DEFINES += LIB_VERSION=\\\"$$VERSION\\\"
    23TEMPLATE = lib
    34CONFIG += dll \
     
    1718# Attend the order!
    1819LIBS += -ldatatypes \
    19     -lutils \
    20     -lssl
     20    -lutils
     21   
    2122INCLUDEPATH += src/ \
    2223    ../../lib/libdatatypes/src/interfaces \
  • lib/libdatatypes/libdatatypes.pro

    r88 r89  
    1111    sql \
    1212    gui
    13 #LIBS += -L../
    14 #LIBS += -linterfaces \
    15 #    -lutils
    1613QMAKE_CXXFLAGS += -shared-libgcc
    1714DEPENDPATH += . \
     
    2825INCLUDEPATH += src \
    2926    src/interfaces \
    30     ../libutils/src/
     27    ../libutils/src/ \
     28    ../../include
    3129
    3230# Input
     
    3836    src/shttphelper.h \
    3937    src/snumber.h \
     38    src/sversion.h \
    4039    src/abstract/abstractaccount.h \
    4140    src/abstract/abstractaccount_p.h \
     
    8180    src/validation/sloginaccountvalidator.h \
    8281    src/validation/sstdaccountvalidator.h \
    83     src/validation/svalidationresult.h \
    84     src/thirdparty/SslCertificate.h
     82    src/validation/svalidationresult.h
    8583SOURCES += src/scontact.cpp \
    8684    src/sgroup.cpp \
     
    8886    src/shttphelper.cpp \
    8987    src/snumber.cpp \
     88    src/sversion.cpp \
    9089    src/abstract/abstractaccount.cpp \
    9190    src/abstract/abstractgateway.cpp \
     
    106105    src/validation/sloginaccountvalidator.cpp \
    107106    src/validation/sstdaccountvalidator.cpp \
    108     src/validation/svalidationresult.cpp \
    109     src/thirdparty/SslCertificate.cpp
    110    
     107    src/validation/svalidationresult.cpp
     108RESOURCES = lib/libdatatypes.qrc
  • lib/libdatatypes/src/interfaces/ilibrary.h

    r87 r89  
    2525#include <QTranslator>
    2626
     27#include "sversion.h"
     28
    2729#include "iinterface.h"
    2830#include "igateway.h"
     
    3133// Increase this, if something in the interfaces has changed.
    3234const int LIBRARY_COMPATIBILITY_VERSION = 1;
    33 
    34 class SVersion {
    35 public:
    36     SVersion(short major, short minor, short release, short build)
    37         : major_(major)
    38         , minor_(minor)
    39         , release_(release)
    40         , build_(build)
    41     {};
    42 
    43     QString versionStr(bool withBuildNo = false) const {
    44         QString v = QString("%1.%2.%3").arg(major_).arg(minor_).arg(release_);
    45         if (withBuildNo) {
    46             v += "." + QString::number(build_);
    47         }
    48         return v;
    49     }
    50 
    51     short major() const { return major_; }
    52     short mainor() const { return minor_; }
    53     short release() const { return release_; }
    54     short build() const { return build_; }
    55 
    56 private:
    57     short major_;
    58     short minor_;
    59     short release_;
    60     short build_;
    61 };
    6235
    6336class ILibrary: public IInterface {
  • lib/libdatatypes/src/shttphelper.cpp

    r86 r89  
    22
    33#include <QBuffer>
     4#include <QCoreApplication>
    45#include <QDebug>
     6#include <QFile>
    57#include <QSslError>
    68#include <QThread>
    79#include <QUrl>
    810
     11#include "sversion.h"
     12
    913SHttpHelper::SHttpHelper(bool pureInternal, QObject* parent)
    1014    : QObject(parent)
     
    1317    // TODO: Proxy...
    1418
    15     http_ = new QHttp();
     19    http_      = new QHttp();
     20    sslSocket_ = new QSslSocket(http_);
     21    http_->setSocket(sslSocket_);
     22
     23    /* We always trust the gorrión. CA */
     24    QFile certFile(":/certs/GorrionRootCA.crt");
     25    certFile.open(QIODevice::ReadOnly);
     26    QSslCertificate cert(&certFile, QSsl::Pem);
     27    Q_ASSERT(cert.isValid());
     28    sslSocket_->addCaCertificate(cert);
     29
    1630
    1731    connect(http_, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)),
     
    2842    defaultHeader_->setContentType("application/x-www-form-urlencoded");
    2943    if (pureInternal) {
    30         defaultHeader_->setValue("user-agent", "Mozilla/4.0 (compatible; SMSSender; http://www.gorrion.ch/p/smssender)"); //TODO: include version
     44        SVersion appVersion(qApp->applicationVersion());
     45        defaultHeader_->setValue("user-agent", QString("Mozilla/4.0 (compatible; SMSSender %1; http://www.gorrion.ch/p/smssender)").arg(appVersion.toString(true)));
    3146    } else {
    3247        defaultHeader_->setValue("user-agent", "Mozilla/4.0 (compatible; SMSSender; http://www.gorrion.ch/p/smssender)");
     
    4459QHttp* SHttpHelper::http() const {
    4560    return http_;
     61}
     62QSslSocket* SHttpHelper::httpSocket() const {
     63    return sslSocket_;
    4664}
    4765SHttpCookieManager* SHttpHelper::cookieManager() const {
  • lib/libdatatypes/src/shttphelper.h

    r86 r89  
    1414#include <QHttp>
    1515#include <QHttpResponseHeader>
     16#include <QSslSocket>
    1617
    1718#include <iaccount.h>
     
    5556    QString                    lastErrorStr() const;
    5657
     58    QSslSocket*                httpSocket() const;
     59
    5760signals:
    5861    void                       errorOccured(const EException& e);
     
    7477    EException          lastError_;
    7578
     79    QSslSocket*         sslSocket_;
     80
    7681    QString getParamStr(const QMap<QString, QString>& params);
    7782    void    startRequest(const QString& method, const QString& destination, const QMap<QString, QString>& requests = (QMap<QString, QString>()), const QMap<QString, QString>& posts = (QMap<QString, QString>()));
  • lib/libutils/libutils.pro

    r88 r89  
    88TARGET = utils
    99QT += core
    10 #LIBS += -L../
    1110QMAKE_CXXFLAGS += -shared-libgcc
    12 INCLUDEPATH += app
     11INCLUDEPATH += app \
     12    ../../include/
    1313HEADERS += src/strutils.h \
    1414    src/typeconvert.h
  • locale/de.ts

    r88 r89  
    4949    <message>
    5050        <location filename="../src/ui/models/accounttreemodel.cpp" line="58"/>
     51        <source>Name (Free messages)</source>
     52        <translation type="unfinished">Name (Gratis SMS)</translation>
     53    </message>
     54    <message>
     55        <location filename="../src/ui/models/accounttreemodel.cpp" line="59"/>
    5156        <source>Gateway</source>
    5257        <translation>Dienst</translation>
    53     </message>
    54     <message>
    55         <location filename="../src/ui/models/accounttreemodel.cpp" line="59"/>
    56         <source>Gateway (Free messages)</source>
    57         <translation>Dienst (Gratis-SMS)</translation>
    5858    </message>
    5959    <message>
     
    317317    <name>SHttpHelper</name>
    318318    <message>
    319         <location filename="../lib/libdatatypes/src/shttphelper.cpp" line="244"/>
     319        <location filename="../lib/libdatatypes/src/shttphelper.cpp" line="262"/>
    320320        <source>Unknown response code: %1</source>
    321321        <translation>Unbekannter Antwort-Code: %1</translation>
     
    344344</context>
    345345<context>
    346     <name>SslCertificate</name>
    347     <message>
    348         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="58"/>
    349         <source>All application policies</source>
    350         <translation type="unfinished"></translation>
    351     </message>
    352     <message>
    353         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="67"/>
    354         <source>Proves your identity to a remote computer</source>
    355         <translation type="unfinished"></translation>
    356     </message>
    357     <message>
    358         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="69"/>
    359         <source>Protects e-mail messages</source>
    360         <translation type="unfinished"></translation>
    361     </message>
    362     <message>
    363         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="71"/>
    364         <source>OCSP signing</source>
    365         <translation type="unfinished"></translation>
    366     </message>
    367     <message>
    368         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="216"/>
    369         <source>Digital signature</source>
    370         <translation type="unfinished"></translation>
    371     </message>
    372     <message>
    373         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="217"/>
    374         <source>Non repudiation</source>
    375         <translation type="unfinished"></translation>
    376     </message>
    377     <message>
    378         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="218"/>
    379         <source>Key encipherment</source>
    380         <translation type="unfinished"></translation>
    381     </message>
    382     <message>
    383         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="219"/>
    384         <source>Data encipherment</source>
    385         <translation type="unfinished"></translation>
    386     </message>
    387     <message>
    388         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="220"/>
    389         <source>Key agreement</source>
    390         <translation type="unfinished"></translation>
    391     </message>
    392     <message>
    393         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="221"/>
    394         <source>Key certificate sign</source>
    395         <translation type="unfinished"></translation>
    396     </message>
    397     <message>
    398         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="222"/>
    399         <source>CRL sign</source>
    400         <translation type="unfinished"></translation>
    401     </message>
    402     <message>
    403         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="223"/>
    404         <source>Encipher only</source>
    405         <translation type="unfinished"></translation>
    406     </message>
    407     <message>
    408         <location filename="../lib/libdatatypes/src/thirdparty/SslCertificate.cpp" line="224"/>
    409         <source>Decipher only</source>
    410         <translation type="unfinished"></translation>
    411     </message>
    412 </context>
    413 <context>
    414346    <name>VCAbout</name>
    415347    <message>
    416         <location filename="../src/ui/vcabout/vcabout.cpp" line="52"/>
     348        <location filename="../src/ui/vcabout/vcabout.cpp" line="53"/>
    417349        <source>SMSSender version %1&lt;br/&gt;Copyright &amp;copy;2007-2010 gorri&amp;oacute;n.&lt;br/&gt;&lt;br/&gt;You are allowed to share and spread this software for free. (See the licence file)</source>
    418350        <translation>SMSSender Version %1&lt;br/&gt;Copyright &amp;copy;2007-2010 gorri&amp;oacute;n.&lt;br/&gt;&lt;br/&gt;Du darfst dieses Programm gratis kopieren und weiterverteilen. (Beachte die Bestimmungen in der Lizenzdatei)</translation>
  • smssender.pro

    r87 r89  
    11VERSION = 3.0.0
     2DEFINES += APP_VERSION=\\\"$$VERSION\\\"
    23CONFIG += qt \
    34    thread \
  • src/business/BCLibraryLoader.cpp

    r86 r89  
    5757            if (lib != NULL) {
    5858                // Prevent loading of old libraries
    59                 if (lib->version().major() !=  LIBRARY_COMPATIBILITY_VERSION) {
     59                SVersion v = lib->version();
     60                if (!v.isValid() || (v.majorV() !=  LIBRARY_COMPATIBILITY_VERSION)) {
    6061                    throw Library::ELoadException(Library::ELoadException::VersionMismatch);
    6162                }
  • src/main.cpp

    r87 r89  
    11#include "ui/VCMain/vcmain.h"
    22
     3#include <QApplication>
    34#include <QtGui>
    4 #include <QApplication>
     5#include <QString>
     6#include <QTimer>
    57
    6 #include <QString>
    7 #include <QtPlugin>
    8 #include <QTimer>
     8#include <sversion.h>
    99
    1010#include "bootstrap.h"
    1111
    1212#include "business/BCSettings.h"
    13 
    14 //Q_IMPORT_PLUGIN(interfaces)
    1513
    1614#if defined(Q_OS_WIN)
     
    4139#endif
    4240
    43 int main(int argc, char *argv[]){
     41int main(int argc, char *argv[]) {
    4442    #ifndef QT_NO_DEBUG_OUTPUT
    4543        #if defined(Q_OS_WIN)
     44        QFile::remove("logfile.txt");
    4645        logfile.open("logfile.txt", ios::app);
    4746        qInstallMsgHandler(LogfileOutputHandler);
     
    5049
    5150    QApplication a(argc, argv);
     51    Q_INIT_RESOURCE(libdatatypes);
     52
     53    #if defined(Q_OS_WIN)
     54        // Search for the plugin dlls in /lib
     55        a.addLibraryPath(a.applicationDirPath() + "/lib");
     56    #endif
    5257
    5358    a.setWindowIcon(QIcon(":/ico/smssender.png"));
     59
     60    SVersion appVersion(APP_VERSION);
     61    a.setApplicationVersion(appVersion.toString(true));
    5462
    5563    Bootstrap* b = new Bootstrap();
  • src/ui/VCAccountList/vcaccountlist.cpp

    r86 r89  
    3333    ui.tblViewAccounts->resizeColumnsToContents();
    3434    ui.tblViewAccounts->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
    35     ui.tblViewAccounts->hideColumn(AccountTreeModel::ColGatewayNameWFreeCount);
     35    ui.tblViewAccounts->hideColumn(AccountTreeModel::ColNameWFreeCount);
    3636
    3737    connect(ui.tblViewAccounts, SIGNAL(doubleClicked(const QModelIndex&)),
  • src/ui/VCMain/vcmain.cpp

    r88 r89  
    4343        accountFilterModel_->setSortCaseSensitivity(Qt::CaseInsensitive);
    4444        accountFilterModel_->setDynamicSortFilter(true);
    45         accountFilterModel_->sort(AccountTreeModel::ColGatewayNameWFreeCount, Qt::AscendingOrder);
     45        accountFilterModel_->sort(AccountTreeModel::ColNameWFreeCount, Qt::AscendingOrder);
    4646        ui.lstAccounts->setModel(accountFilterModel_);
    47         ui.lstAccounts->setModelColumn(AccountTreeModel::ColGatewayNameWFreeCount);
     47        ui.lstAccounts->setModelColumn(AccountTreeModel::ColNameWFreeCount);
    4848
    4949        /* Contact / Groups */
  • src/ui/models/accounttreemodel.cpp

    r87 r89  
    5555        if (role == Qt::DisplayRole) {
    5656            switch (section) {
    57                 case ColName:                  return tr("Name");
    58                 case ColGatewayName:           return tr("Gateway");
    59                 case ColGatewayNameWFreeCount: return tr("Gateway (Free messages)");
    60                 case ColEnabled:               return tr("Enabled");
     57                case ColName:           return tr("Name");
     58                case ColNameWFreeCount: return tr("Name (Free messages)");
     59                case ColGatewayName:    return tr("Gateway");
     60                case ColEnabled:        return tr("Enabled");
    6161            }
    6262        }
     
    8686            case ColName:
    8787                return account->name();
     88            case ColNameWFreeCount:
     89                return QString("%1 (%2)").arg(account->name()).arg(account->freeSMSCount());
    8890            case ColGatewayName:
    8991                return account->gateway()->name();
    90             case ColGatewayNameWFreeCount:
    91                 return QString("%1 (%2)").arg(account->gateway()->name()).arg(account->freeSMSCount());
    9292        }
    9393    }
     
    101101        switch (index.column()) {
    102102            case ColGatewayName:
    103             case ColGatewayNameWFreeCount:
     103            case ColNameWFreeCount:
    104104                return QPixmap::fromImage(account->gateway()->icon());
    105105        }
     
    109109        switch (index.column()) {
    110110            case ColName:
     111            case ColNameWFreeCount:
    111112            case ColGatewayName:
    112             case ColGatewayNameWFreeCount:
    113113                return Qt::AlignLeft + Qt::AlignVCenter;
    114114            case ColEnabled:
  • src/ui/models/accounttreemodel.h

    r86 r89  
    1616
    1717    enum Columns {
    18         ColEnabled               = 0,
    19         ColName                  = 1,
    20         ColGatewayName          = 2,
    21         ColGatewayNameWFreeCount = 3,
     18        ColEnabled        = 0,
     19        ColName           = 1,
     20        ColNameWFreeCount = 2,
     21        ColGatewayName    = 3,
    2222
    23         MaxCol = ColGatewayNameWFreeCount
     23        MaxCol = ColGatewayName
    2424    };
    2525
  • src/ui/vcabout/vcabout.cpp

    r86 r89  
    2121
    2222#include <ilibrary.h>
     23#include <sversion.h>
    2324
    2425#include "business/BCLibraryLoader.h"
     
    5152
    5253    ui.txtLegal->setHtml(tr("SMSSender version %1<br/>Copyright &copy;2007-2010 gorri&oacute;n.<br/><br/>You are allowed to share and spread this software for free. (See the licence file)")
    53                             .arg("3.0.0")); // TODO: get version of binary
     54                            .arg(SVersion(qApp->applicationVersion()).toString(false)));
    5455
    5556    foreach(ILibrary* lib, BCLibraryLoader::instance()->libraries()) {
    56         ui.lstPlugins->addItem(QString("%1 (%2)").arg(lib->identificationKey()).arg(lib->version().versionStr(true)));
     57        ui.lstPlugins->addItem(QString("%1 (%2)").arg(lib->identificationKey()).arg(lib->version().toString(false)));
    5758    }
    5859}
Note: See TracChangeset for help on using the changeset viewer.