Merge branch 'expand-geoip'

- Allow configurable TZ selector string, to allow for more variation in providers
This commit is contained in:
Adriaan de Groot 2018-04-16 07:53:25 -04:00
commit 6545d5d022
13 changed files with 151 additions and 42 deletions

View file

@ -6,7 +6,7 @@ endif()
include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui ) include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
set( geoip_src GeoIP.cpp GeoIPFreeGeoIP.cpp ) set( geoip_src GeoIP.cpp GeoIPJSON.cpp )
set( geoip_libs ) set( geoip_libs )
find_package(Qt5 COMPONENTS Xml) find_package(Qt5 COMPONENTS Xml)

View file

@ -20,13 +20,21 @@
#include "utils/Logger.h" #include "utils/Logger.h"
GeoIP::GeoIP(const QString& e)
: m_element( e )
{
}
GeoIP::~GeoIP() GeoIP::~GeoIP()
{ {
} }
GeoIP::RegionZonePair GeoIP::RegionZonePair
GeoIP::splitTZString( const QString& timezoneString ) GeoIP::splitTZString( const QString& tz )
{ {
QString timezoneString( tz );
timezoneString.remove( '\\' );
QStringList tzParts = timezoneString.split( '/', QString::SkipEmptyParts ); QStringList tzParts = timezoneString.split( '/', QString::SkipEmptyParts );
if ( tzParts.size() >= 2 ) if ( tzParts.size() >= 2 )
{ {

View file

@ -32,8 +32,9 @@ class QByteArray;
* and can handle the data returned from its interpretation of that * and can handle the data returned from its interpretation of that
* configured URL, returning a region and zone. * configured URL, returning a region and zone.
*/ */
struct GeoIP class GeoIP
{ {
public:
using RegionZonePair = QPair<QString, QString>; using RegionZonePair = QPair<QString, QString>;
virtual ~GeoIP(); virtual ~GeoIP();
@ -51,6 +52,11 @@ struct GeoIP
/** @brief Splits a region/zone string into a pair. */ /** @brief Splits a region/zone string into a pair. */
static RegionZonePair splitTZString( const QString& s ); static RegionZonePair splitTZString( const QString& s );
protected:
GeoIP( const QString& e = QString() );
QString m_element; // string for selecting from data
} ; } ;
#endif #endif

View file

@ -17,7 +17,7 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>. * along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "GeoIPFreeGeoIP.h" #include "GeoIPJSON.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/YamlUtils.h" #include "utils/YamlUtils.h"
@ -26,8 +26,13 @@
#include <yaml-cpp/yaml.h> #include <yaml-cpp/yaml.h>
GeoIPJSON::GeoIPJSON(const QString& attribute)
: GeoIP( attribute.isEmpty() ? QLatin1String( "time_zone" ) : attribute )
{
}
GeoIP::RegionZonePair GeoIP::RegionZonePair
FreeGeoIP::processReply( const QByteArray& data ) GeoIPJSON::processReply( const QByteArray& data )
{ {
try try
{ {
@ -39,12 +44,14 @@ FreeGeoIP::processReply( const QByteArray& data )
var.type() == QVariant::Map ) var.type() == QVariant::Map )
{ {
QVariantMap map = var.toMap(); QVariantMap map = var.toMap();
if ( map.contains( "time_zone" ) && if ( map.contains( m_element ) &&
!map.value( "time_zone" ).toString().isEmpty() ) !map.value( m_element ).toString().isEmpty() )
{ {
return splitTZString( map.value( "time_zone" ).toString() ); return splitTZString( map.value( m_element ).toString() );
} }
} }
else
cWarning() << "Invalid YAML data for GeoIPJSON";
} }
catch ( YAML::Exception& e ) catch ( YAML::Exception& e )
{ {

View file

@ -16,21 +16,28 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>. * along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef GEOIPFREEGEOIP_H #ifndef GEOIPJSON_H
#define GEOIPFREEGEOIP_H #define GEOIPJSON_H
#include "GeoIP.h" #include "GeoIP.h"
/** @brief GeoIP lookup via freegeoip.com /** @brief GeoIP lookup for services that return JSON.
* *
* This is the original implementation of GeoIP lookup, * This is the original implementation of GeoIP lookup,
* using the FreeGeoIP service, or similar which returns * (e.g. using the FreeGeoIP.net service), or similar.
* data in the same format.
* *
* The data is assumed to be in JSON format with a time_zone attribute. * The data is assumed to be in JSON format with a time_zone attribute.
*/ */
struct FreeGeoIP : public GeoIP class GeoIPJSON : public GeoIP
{ {
public:
/** @brief Configure the attribute name which is selected.
*
* If an empty string is passed in (not a valid attribute name),
* then "time_zone" is used.
*/
explicit GeoIPJSON( const QString& attribute = QString() );
virtual RegionZonePair processReply( const QByteArray& ); virtual RegionZonePair processReply( const QByteArray& );
} ; } ;

View file

@ -18,7 +18,7 @@
#include "GeoIPTests.h" #include "GeoIPTests.h"
#include "GeoIPFreeGeoIP.h" #include "GeoIPJSON.h"
#ifdef HAVE_XML #ifdef HAVE_XML
#include "GeoIPXML.h" #include "GeoIPXML.h"
#endif #endif
@ -40,14 +40,14 @@ GeoIPTests::initTestCase()
{ {
} }
static const char json_data_attribute[] =
"{\"time_zone\":\"Europe/Amsterdam\"}";
void void
GeoIPTests::testJSON() GeoIPTests::testJSON()
{ {
static const char data[] = GeoIPJSON handler;
"{\"time_zone\":\"Europe/Amsterdam\"}"; auto tz = handler.processReply( json_data_attribute );
FreeGeoIP handler;
auto tz = handler.processReply( data );
QCOMPARE( tz.first, QLatin1String( "Europe" ) ); QCOMPARE( tz.first, QLatin1String( "Europe" ) );
QCOMPARE( tz.second, QLatin1String( "Amsterdam" ) ); QCOMPARE( tz.second, QLatin1String( "Amsterdam" ) );
@ -60,12 +60,24 @@ GeoIPTests::testJSON()
QCOMPARE( tz.first, "America" ); QCOMPARE( tz.first, "America" );
} }
void GeoIPTests::testJSONalt()
{
GeoIPJSON handler( "zona_de_hora" );
auto tz = handler.processReply( json_data_attribute );
QCOMPARE( tz.first, QString() ); // Not found
tz = handler.processReply( "tarifa: 12\nzona_de_hora: Europe/Madrid" );
QCOMPARE( tz.first, QLatin1String( "Europe" ) );
QCOMPARE( tz.second, QLatin1String( "Madrid" ) );
}
void void
GeoIPTests::testJSONbad() GeoIPTests::testJSONbad()
{ {
static const char data[] = "time_zone: 1"; static const char data[] = "time_zone: 1";
FreeGeoIP handler; GeoIPJSON handler;
auto tz = handler.processReply( data ); auto tz = handler.processReply( data );
tz = handler.processReply( data ); tz = handler.processReply( data );
@ -76,13 +88,13 @@ GeoIPTests::testJSONbad()
tz = handler.processReply( "<html><body>404 Forbidden</body></html>" ); tz = handler.processReply( "<html><body>404 Forbidden</body></html>" );
QCOMPARE( tz.first, QString() ); QCOMPARE( tz.first, QString() );
tz = handler.processReply( "{ time zone = 'America/LosAngeles'}" );
QCOMPARE( tz.first, QString() );
} }
void static const char xml_data_ubiquity[] =
GeoIPTests::testXML()
{
static const char data[] =
R"(<Response> R"(<Response>
<Ip>85.150.1.1</Ip> <Ip>85.150.1.1</Ip>
<Status>OK</Status> <Status>OK</Status>
@ -99,9 +111,12 @@ GeoIPTests::testXML()
<TimeZone>Europe/Amsterdam</TimeZone> <TimeZone>Europe/Amsterdam</TimeZone>
</Response>)"; </Response>)";
void
GeoIPTests::testXML()
{
#ifdef HAVE_XML #ifdef HAVE_XML
XMLGeoIP handler; GeoIPXML handler;
auto tz = handler.processReply( data ); auto tz = handler.processReply( xml_data_ubiquity );
QCOMPARE( tz.first, QLatin1String( "Europe" ) ); QCOMPARE( tz.first, QLatin1String( "Europe" ) );
QCOMPARE( tz.second, QLatin1String( "Amsterdam" ) ); QCOMPARE( tz.second, QLatin1String( "Amsterdam" ) );
@ -115,7 +130,7 @@ GeoIPTests::testXML2()
"<Response><TimeZone>America/North Dakota/Beulah</TimeZone></Response>"; "<Response><TimeZone>America/North Dakota/Beulah</TimeZone></Response>";
#ifdef HAVE_XML #ifdef HAVE_XML
XMLGeoIP handler; GeoIPXML handler;
auto tz = handler.processReply( data ); auto tz = handler.processReply( data );
QCOMPARE( tz.first, QLatin1String( "America" ) ); QCOMPARE( tz.first, QLatin1String( "America" ) );
@ -123,11 +138,23 @@ GeoIPTests::testXML2()
#endif #endif
} }
void GeoIPTests::testXMLalt()
{
#ifdef HAvE_XML
GeoIPXML handler( "ZT" );
auto tz = handler.processReply( "<A><B/><C><ZT>Moon/Dark_side</ZT></C></A>" );
QCOMPARE( tz.first, QLatin1String( "Moon" ) );
QCOMPARE( tz.second, QLatin1String( "Dark_side" ) );
#endif
}
void void
GeoIPTests::testXMLbad() GeoIPTests::testXMLbad()
{ {
#ifdef HAVE_XML #ifdef HAVE_XML
XMLGeoIP handler; GeoIPXML handler;
auto tz = handler.processReply( "{time_zone: \"Europe/Paris\"}" ); auto tz = handler.processReply( "{time_zone: \"Europe/Paris\"}" );
QCOMPARE( tz.first, QString() ); QCOMPARE( tz.first, QString() );
@ -138,3 +165,27 @@ GeoIPTests::testXMLbad()
QCOMPARE( tz.first, QString() ); QCOMPARE( tz.first, QString() );
#endif #endif
} }
void GeoIPTests::testSplitTZ()
{
auto tz = GeoIP::splitTZString( QLatin1String("Moon/Dark_side") );
QCOMPARE( tz.first, QLatin1String("Moon") );
QCOMPARE( tz.second, QLatin1String("Dark_side") );
// Some providers return weirdly escaped data
tz = GeoIP::splitTZString( QLatin1String("America\\/NewYork") );
QCOMPARE( tz.first, QLatin1String("America") );
QCOMPARE( tz.second, QLatin1String("NewYork") ); // That's not actually the zone name
// Check that bogus data fails
tz = GeoIP::splitTZString( QString() );
QCOMPARE( tz.first, QString() );
tz = GeoIP::splitTZString( QLatin1String("America.NewYork") );
QCOMPARE( tz.first, QString() );
// Check that three-level is split properly
tz = GeoIP::splitTZString( QLatin1String("America/North Dakota/Beulah") );
QCOMPARE( tz.first, QLatin1String("America") );
QCOMPARE( tz.second, QLatin1String("North Dakota/Beulah") );
}

View file

@ -31,10 +31,13 @@ public:
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
void testJSON(); void testJSON();
void testJSONalt();
void testJSONbad(); void testJSONbad();
void testXML(); void testXML();
void testXML2(); void testXML2();
void testXMLalt();
void testXMLbad(); void testXMLbad();
void testSplitTZ();
}; };
#endif #endif

View file

@ -23,8 +23,13 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QtXml/QDomDocument> #include <QtXml/QDomDocument>
GeoIPXML::GeoIPXML( const QString& element )
: GeoIP( element.isEmpty() ? QLatin1String( "TimeZone" ) : element )
{
}
GeoIP::RegionZonePair GeoIP::RegionZonePair
XMLGeoIP::processReply( const QByteArray& data ) GeoIPXML::processReply( const QByteArray& data )
{ {
QString domError; QString domError;
int errorLine, errorColumn; int errorLine, errorColumn;
@ -32,7 +37,7 @@ XMLGeoIP::processReply( const QByteArray& data )
QDomDocument doc; QDomDocument doc;
if ( doc.setContent( data, false, &domError, &errorLine, &errorColumn ) ) if ( doc.setContent( data, false, &domError, &errorLine, &errorColumn ) )
{ {
const auto tzElements = doc.elementsByTagName( "TimeZone" ); const auto tzElements = doc.elementsByTagName( m_element );
cDebug() << "GeoIP found" << tzElements.length() << "elements"; cDebug() << "GeoIP found" << tzElements.length() << "elements";
for ( int it = 0; it < tzElements.length(); ++it ) for ( int it = 0; it < tzElements.length(); ++it )
{ {

View file

@ -28,8 +28,16 @@
* element, which contains the text (string) for the region/zone. This * element, which contains the text (string) for the region/zone. This
* format is expected by, e.g. the Ubiquity installer. * format is expected by, e.g. the Ubiquity installer.
*/ */
struct XMLGeoIP : public GeoIP class GeoIPXML : public GeoIP
{ {
public:
/** @brief Configure the element tag which is selected.
*
* If an empty string is passed in (not a valid element tag),
* then "TimeZone" is used.
*/
explicit GeoIPXML( const QString& element = QString() );
virtual RegionZonePair processReply( const QByteArray& ); virtual RegionZonePair processReply( const QByteArray& );
} ; } ;

View file

@ -20,7 +20,7 @@
#include "LocaleViewStep.h" #include "LocaleViewStep.h"
#include "GeoIP.h" #include "GeoIP.h"
#include "GeoIPFreeGeoIP.h" #include "GeoIPJSON.h"
#ifdef HAVE_XML #ifdef HAVE_XML
#include "GeoIPXML.h" #include "GeoIPXML.h"
#endif #endif
@ -124,16 +124,16 @@ LocaleViewStep::fetchGeoIpTimezone()
if ( m_geoipStyle.isEmpty() || m_geoipStyle == "legacy" ) if ( m_geoipStyle.isEmpty() || m_geoipStyle == "legacy" )
{ {
actualUrl.append( "/json/" ); actualUrl.append( "/json/" );
handler = new FreeGeoIP; handler = new GeoIPJSON( m_geoipSelector );
} }
else if ( m_geoipStyle == "json" ) else if ( m_geoipStyle == "json" )
{ {
handler = new FreeGeoIP; handler = new GeoIPJSON( m_geoipSelector );
} }
#if defined(HAVE_XML) #if defined(HAVE_XML)
else if ( m_geoipStyle == "xml" ) else if ( m_geoipStyle == "xml" )
{ {
handler = new XMLGeoIP; handler = new GeoIPXML( m_geoipSelector );
} }
#endif #endif
else else
@ -295,4 +295,5 @@ LocaleViewStep::setConfigurationMap( const QVariantMap& configurationMap )
// Optional // Optional
m_geoipUrl = CalamaresUtils::getString( configurationMap, "geoipUrl" ); m_geoipUrl = CalamaresUtils::getString( configurationMap, "geoipUrl" );
m_geoipStyle = CalamaresUtils::getString( configurationMap, "geoipStyle" ); m_geoipStyle = CalamaresUtils::getString( configurationMap, "geoipStyle" );
m_geoipSelector = CalamaresUtils::getString( configurationMap, "geoipSelector" );
} }

View file

@ -75,8 +75,10 @@ private:
QPair< QString, QString > m_startingTimezone; QPair< QString, QString > m_startingTimezone;
QString m_localeGenPath; QString m_localeGenPath;
QString m_geoipUrl;
QString m_geoipStyle; QString m_geoipUrl; // The URL, depening on style might be modified on lookup
QString m_geoipStyle; // String selecting which kind of geoip data to expect
QString m_geoipSelector; // String selecting data from the geoip lookup
QList< Calamares::job_ptr > m_jobs; QList< Calamares::job_ptr > m_jobs;
}; };

View file

@ -37,7 +37,9 @@ zone: "New_York"
# the URL may be modified before use. The request should return # the URL may be modified before use. The request should return
# valid data in a suitable format, depending on geoipStyle; # valid data in a suitable format, depending on geoipStyle;
# generally this includes a string value with the timezone # generally this includes a string value with the timezone
# in <region>/<zone> format. # in <region>/<zone> format. For services that return data which
# does not follow the conventions of "suitable data" described
# below, *geoIPSelector* may be used to pick different data.
# #
# Note that this example URL works, but the service is shutting # Note that this example URL works, but the service is shutting
# down in June 2018. # down in June 2018.
@ -68,3 +70,12 @@ zone: "New_York"
# shutting down in June 2018. There are other providers with the same # shutting down in June 2018. There are other providers with the same
# format. XML format is provided for Ubiquity. # format. XML format is provided for Ubiquity.
#geoipStyle: "legacy" #geoipStyle: "legacy"
# GeoIP selector. Leave commented out for the default selector
# (which depends on the style: JSON uses "time_zone" and XML
# uses TimeZone, for the FreeGeoIP-alike and the Ubiquity-alike
# respectively). If the service configured via *geoipUrl* uses
# a different attribute name (e.g. "timezone") in JSON or a
# different element tag (e.g. "<Time_Zone>") in XML, set this
# string to the name or tag to be used.
#geoipSelector: ""

View file

@ -22,7 +22,7 @@
#include <iostream> #include <iostream>
#include "GeoIPFreeGeoIP.h" #include "GeoIPJSON.h"
#ifdef HAVE_XML #ifdef HAVE_XML
#include "GeoIPXML.h" #include "GeoIPXML.h"
#endif #endif
@ -39,10 +39,10 @@ int main(int argc, char** argv)
GeoIP* handler = nullptr; GeoIP* handler = nullptr;
if ( QLatin1String( "json" ) == argv[1] ) if ( QLatin1String( "json" ) == argv[1] )
handler = new FreeGeoIP; handler = new GeoIPJSON;
#ifdef HAVE_XML #ifdef HAVE_XML
else if ( QLatin1String( "xml" ) == argv[1] ) else if ( QLatin1String( "xml" ) == argv[1] )
handler = new XMLGeoIP; handler = new GeoIPXML;
#endif #endif
if ( !handler ) if ( !handler )