geocoder 1.2.7 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geocoder might be problematic. Click here for more details.

Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/README.md +31 -28
  4. data/examples/reverse_geocode_job.rb +40 -0
  5. data/lib/geocoder.rb +1 -0
  6. data/lib/geocoder/configuration.rb +5 -1
  7. data/lib/geocoder/exceptions.rb +3 -0
  8. data/lib/geocoder/ip_address.rb +2 -10
  9. data/lib/geocoder/logger.rb +55 -0
  10. data/lib/geocoder/lookup.rb +2 -1
  11. data/lib/geocoder/lookups/baidu.rb +5 -5
  12. data/lib/geocoder/lookups/baidu_ip.rb +5 -5
  13. data/lib/geocoder/lookups/base.rb +21 -16
  14. data/lib/geocoder/lookups/bing.rb +15 -2
  15. data/lib/geocoder/lookups/esri.rb +1 -1
  16. data/lib/geocoder/lookups/geocoder_ca.rb +1 -2
  17. data/lib/geocoder/lookups/geocodio.rb +3 -3
  18. data/lib/geocoder/lookups/google.rb +3 -3
  19. data/lib/geocoder/lookups/google_places_details.rb +3 -3
  20. data/lib/geocoder/lookups/mapquest.rb +3 -3
  21. data/lib/geocoder/lookups/maxmind.rb +1 -1
  22. data/lib/geocoder/lookups/maxmind_geoip2.rb +69 -0
  23. data/lib/geocoder/lookups/nominatim.rb +1 -1
  24. data/lib/geocoder/lookups/opencagedata.rb +4 -4
  25. data/lib/geocoder/lookups/pointpin.rb +4 -4
  26. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +3 -3
  27. data/lib/geocoder/lookups/yahoo.rb +3 -3
  28. data/lib/geocoder/lookups/yandex.rb +3 -3
  29. data/lib/geocoder/query.rb +1 -1
  30. data/lib/geocoder/request.rb +70 -12
  31. data/lib/geocoder/results/geoip2.rb +8 -14
  32. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  33. data/lib/geocoder/results/yandex.rb +5 -5
  34. data/lib/geocoder/version.rb +1 -1
  35. metadata +6 -147
  36. data/.gitignore +0 -6
  37. data/.travis.yml +0 -32
  38. data/Rakefile +0 -25
  39. data/examples/sidekiq_worker.rb +0 -16
  40. data/gemfiles/Gemfile.mongoid-2.4.x +0 -16
  41. data/test/fixtures/baidu_invalid_key +0 -1
  42. data/test/fixtures/baidu_ip_202_198_16_3 +0 -19
  43. data/test/fixtures/baidu_ip_invalid_key +0 -1
  44. data/test/fixtures/baidu_ip_no_results +0 -1
  45. data/test/fixtures/baidu_no_results +0 -1
  46. data/test/fixtures/baidu_reverse +0 -1
  47. data/test/fixtures/baidu_shanghai_pearl_tower +0 -12
  48. data/test/fixtures/bing_invalid_key +0 -1
  49. data/test/fixtures/bing_madison_square_garden +0 -40
  50. data/test/fixtures/bing_no_results +0 -16
  51. data/test/fixtures/bing_reverse +0 -42
  52. data/test/fixtures/cloudmade_invalid_key +0 -1
  53. data/test/fixtures/cloudmade_madison_square_garden +0 -1
  54. data/test/fixtures/cloudmade_no_results +0 -1
  55. data/test/fixtures/esri_madison_square_garden +0 -59
  56. data/test/fixtures/esri_no_results +0 -8
  57. data/test/fixtures/esri_reverse +0 -21
  58. data/test/fixtures/freegeoip_74_200_247_59 +0 -12
  59. data/test/fixtures/freegeoip_no_results +0 -1
  60. data/test/fixtures/geocoder_ca_madison_square_garden +0 -1
  61. data/test/fixtures/geocoder_ca_no_results +0 -1
  62. data/test/fixtures/geocoder_ca_reverse +0 -34
  63. data/test/fixtures/geocoder_us_madison_square_garden +0 -1
  64. data/test/fixtures/geocoder_us_no_results +0 -1
  65. data/test/fixtures/geocodio_1101_pennsylvania_ave +0 -1
  66. data/test/fixtures/geocodio_bad_api_key +0 -3
  67. data/test/fixtures/geocodio_invalid +0 -4
  68. data/test/fixtures/geocodio_no_results +0 -1
  69. data/test/fixtures/geocodio_over_query_limit +0 -4
  70. data/test/fixtures/google_garbage +0 -456
  71. data/test/fixtures/google_madison_square_garden +0 -57
  72. data/test/fixtures/google_no_city_data +0 -44
  73. data/test/fixtures/google_no_locality +0 -51
  74. data/test/fixtures/google_no_results +0 -4
  75. data/test/fixtures/google_over_limit +0 -4
  76. data/test/fixtures/google_places_details_invalid_request +0 -4
  77. data/test/fixtures/google_places_details_madison_square_garden +0 -120
  78. data/test/fixtures/google_places_details_no_results +0 -4
  79. data/test/fixtures/google_places_details_no_reviews +0 -60
  80. data/test/fixtures/google_places_details_no_types +0 -66
  81. data/test/fixtures/here_madison_square_garden +0 -72
  82. data/test/fixtures/here_no_results +0 -8
  83. data/test/fixtures/mapquest_error +0 -16
  84. data/test/fixtures/mapquest_invalid_api_key +0 -16
  85. data/test/fixtures/mapquest_invalid_request +0 -16
  86. data/test/fixtures/mapquest_madison_square_garden +0 -52
  87. data/test/fixtures/mapquest_no_results +0 -16
  88. data/test/fixtures/maxmind_24_24_24_21 +0 -1
  89. data/test/fixtures/maxmind_24_24_24_22 +0 -1
  90. data/test/fixtures/maxmind_24_24_24_23 +0 -1
  91. data/test/fixtures/maxmind_24_24_24_24 +0 -1
  92. data/test/fixtures/maxmind_74_200_247_59 +0 -1
  93. data/test/fixtures/maxmind_invalid_key +0 -1
  94. data/test/fixtures/maxmind_no_results +0 -1
  95. data/test/fixtures/nominatim_madison_square_garden +0 -150
  96. data/test/fixtures/nominatim_no_results +0 -1
  97. data/test/fixtures/nominatim_over_limit +0 -1
  98. data/test/fixtures/okf_kirstinmaki +0 -67
  99. data/test/fixtures/okf_no_results +0 -4
  100. data/test/fixtures/opencagedata_invalid_api_key +0 -25
  101. data/test/fixtures/opencagedata_invalid_request +0 -26
  102. data/test/fixtures/opencagedata_madison_square_garden +0 -73
  103. data/test/fixtures/opencagedata_no_results +0 -29
  104. data/test/fixtures/opencagedata_over_limit +0 -31
  105. data/test/fixtures/ovi_madison_square_garden +0 -72
  106. data/test/fixtures/ovi_no_results +0 -8
  107. data/test/fixtures/pointpin_10_10_10_10 +0 -1
  108. data/test/fixtures/pointpin_555_555_555_555 +0 -1
  109. data/test/fixtures/pointpin_80_111_555_555 +0 -1
  110. data/test/fixtures/pointpin_no_results +0 -1
  111. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_WR26NJ +0 -1
  112. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_generic_error +0 -1
  113. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_hampshire +0 -1
  114. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_key_limit_exceeded +0 -1
  115. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_no_results +0 -1
  116. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_romsey +0 -1
  117. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_unknown_key +0 -1
  118. data/test/fixtures/smarty_streets_11211 +0 -1
  119. data/test/fixtures/smarty_streets_madison_square_garden +0 -47
  120. data/test/fixtures/smarty_streets_no_results +0 -1
  121. data/test/fixtures/telize_10_10_10_10 +0 -1
  122. data/test/fixtures/telize_555_555_555_555 +0 -4
  123. data/test/fixtures/telize_74_200_247_59 +0 -1
  124. data/test/fixtures/telize_no_results +0 -1
  125. data/test/fixtures/yahoo_error +0 -1
  126. data/test/fixtures/yahoo_invalid_key +0 -2
  127. data/test/fixtures/yahoo_madison_square_garden +0 -52
  128. data/test/fixtures/yahoo_no_results +0 -10
  129. data/test/fixtures/yahoo_over_limit +0 -2
  130. data/test/fixtures/yandex_canada_rue_dupuis_14 +0 -446
  131. data/test/fixtures/yandex_invalid_key +0 -1
  132. data/test/fixtures/yandex_kremlin +0 -48
  133. data/test/fixtures/yandex_new_york +0 -1
  134. data/test/fixtures/yandex_no_administrative_area +0 -53
  135. data/test/fixtures/yandex_no_city_and_town +0 -112
  136. data/test/fixtures/yandex_no_results +0 -16
  137. data/test/integration/http_client_test.rb +0 -31
  138. data/test/mongoid_test_helper.rb +0 -43
  139. data/test/test_helper.rb +0 -423
  140. data/test/unit/active_record_test.rb +0 -16
  141. data/test/unit/cache_test.rb +0 -37
  142. data/test/unit/calculations_test.rb +0 -220
  143. data/test/unit/configuration_test.rb +0 -55
  144. data/test/unit/error_handling_test.rb +0 -79
  145. data/test/unit/geocoder_test.rb +0 -78
  146. data/test/unit/https_test.rb +0 -17
  147. data/test/unit/ip_address_test.rb +0 -27
  148. data/test/unit/lookup_test.rb +0 -153
  149. data/test/unit/lookups/bing_test.rb +0 -68
  150. data/test/unit/lookups/dstk_test.rb +0 -26
  151. data/test/unit/lookups/esri_test.rb +0 -48
  152. data/test/unit/lookups/freegeoip_test.rb +0 -27
  153. data/test/unit/lookups/geocoder_ca_test.rb +0 -17
  154. data/test/unit/lookups/geocodio_test.rb +0 -56
  155. data/test/unit/lookups/geoip2_test.rb +0 -27
  156. data/test/unit/lookups/google_places_details_test.rb +0 -122
  157. data/test/unit/lookups/google_premier_test.rb +0 -22
  158. data/test/unit/lookups/google_test.rb +0 -84
  159. data/test/unit/lookups/mapquest_test.rb +0 -60
  160. data/test/unit/lookups/maxmind_local_test.rb +0 -28
  161. data/test/unit/lookups/maxmind_test.rb +0 -63
  162. data/test/unit/lookups/nominatim_test.rb +0 -31
  163. data/test/unit/lookups/okf_test.rb +0 -38
  164. data/test/unit/lookups/opencagedata_test.rb +0 -64
  165. data/test/unit/lookups/pointpin_test.rb +0 -30
  166. data/test/unit/lookups/postcode_anywhere_uk_test.rb +0 -70
  167. data/test/unit/lookups/smarty_streets_test.rb +0 -71
  168. data/test/unit/lookups/telize_test.rb +0 -36
  169. data/test/unit/lookups/yahoo_test.rb +0 -35
  170. data/test/unit/method_aliases_test.rb +0 -26
  171. data/test/unit/model_test.rb +0 -38
  172. data/test/unit/mongoid_test.rb +0 -47
  173. data/test/unit/near_test.rb +0 -87
  174. data/test/unit/oauth_util_test.rb +0 -31
  175. data/test/unit/proxy_test.rb +0 -37
  176. data/test/unit/query_test.rb +0 -52
  177. data/test/unit/rake_task_test.rb +0 -21
  178. data/test/unit/request_test.rb +0 -35
  179. data/test/unit/result_test.rb +0 -81
  180. data/test/unit/test_mode_test.rb +0 -70
@@ -43,9 +43,9 @@ module Geocoder::Lookup
43
43
  if doc['statusCode'] == 200
44
44
  return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
45
45
  elsif doc['statusCode'] == 401 and doc["authenticationResultCode"] == "InvalidCredentials"
46
- raise_error(Geocoder::InvalidApiKey) || warn("Invalid Bing API key.")
46
+ raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid Bing API key.")
47
47
  else
48
- warn "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']})."
48
+ Geocoder.log(:warn, "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']}).")
49
49
  end
50
50
  return []
51
51
  end
@@ -55,5 +55,18 @@ module Geocoder::Lookup
55
55
  key: configuration.api_key
56
56
  }.merge(super)
57
57
  end
58
+
59
+ def check_response_for_errors!(response)
60
+ super
61
+ if response['x-ms-bm-ws-info'].to_i == 1
62
+ # Occasionally, the servers processing service requests can be overloaded,
63
+ # and you may receive some responses that contain no results for queries that
64
+ # you would normally receive a result. To identify this situation,
65
+ # check the HTTP headers of the response. If the HTTP header X-MS-BM-WS-INFO is set to 1,
66
+ # it is best to wait a few seconds and try again.
67
+ raise_error(Geocoder::ServiceUnavailable) ||
68
+ Geocoder.log(:warn, "Bing Geocoding API error: Service Unavailable")
69
+ end
70
+ end
58
71
  end
59
72
  end
@@ -21,7 +21,7 @@ module Geocoder::Lookup
21
21
  return [] unless doc = fetch_data(query)
22
22
 
23
23
  if (!query.reverse_geocode?)
24
- return [] if doc['locations'].empty?
24
+ return [] if !doc['locations'] || doc['locations'].empty?
25
25
  end
26
26
 
27
27
  if (doc['error'].nil?)
@@ -21,7 +21,7 @@ module Geocoder::Lookup
21
21
  elsif doc['error']['code'] == "005"
22
22
  # "Postal Code is not in the proper Format" => no results, just shut up
23
23
  else
24
- warn "Geocoder.ca service error: #{doc['error']['code']} (#{doc['error']['description']})."
24
+ Geocoder.log(:warn, "Geocoder.ca service error: #{doc['error']['code']} (#{doc['error']['description']}).")
25
25
  end
26
26
  return []
27
27
  end
@@ -51,4 +51,3 @@ module Geocoder::Lookup
51
51
  end
52
52
  end
53
53
  end
54
-
@@ -19,13 +19,13 @@ module Geocoder::Lookup
19
19
 
20
20
  if doc['error'] == 'Invalid API key'
21
21
  raise_error(Geocoder::InvalidApiKey) ||
22
- warn("Geocodio service error: invalid API key.")
22
+ Geocoder.log(:warn, "Geocodio service error: invalid API key.")
23
23
  elsif doc['error'].match(/You have reached your daily maximum/)
24
24
  raise_error(Geocoder::OverQueryLimitError, doc['error']) ||
25
- warn("Geocodio service error: #{doc['error']}.")
25
+ Geocoder.log(:warn, "Geocodio service error: #{doc['error']}.")
26
26
  else
27
27
  raise_error(Geocoder::InvalidRequest, doc['error']) ||
28
- warn("Geocodio service error: #{doc['error']}.")
28
+ Geocoder.log(:warn, "Geocodio service error: #{doc['error']}.")
29
29
  end
30
30
  []
31
31
  end
@@ -30,13 +30,13 @@ module Geocoder::Lookup
30
30
  return doc['results']
31
31
  when "OVER_QUERY_LIMIT"
32
32
  raise_error(Geocoder::OverQueryLimitError) ||
33
- warn("Google Geocoding API error: over query limit.")
33
+ Geocoder.log(:warn, "Google Geocoding API error: over query limit.")
34
34
  when "REQUEST_DENIED"
35
35
  raise_error(Geocoder::RequestDenied) ||
36
- warn("Google Geocoding API error: request denied.")
36
+ Geocoder.log(:warn, "Google Geocoding API error: request denied.")
37
37
  when "INVALID_REQUEST"
38
38
  raise_error(Geocoder::InvalidRequest) ||
39
- warn("Google Geocoding API error: invalid request.")
39
+ Geocoder.log(:warn, "Google Geocoding API error: invalid request.")
40
40
  end
41
41
  return []
42
42
  end
@@ -29,11 +29,11 @@ module Geocoder
29
29
  when "OK"
30
30
  return [doc["result"]]
31
31
  when "OVER_QUERY_LIMIT"
32
- raise_error(Geocoder::OverQueryLimitError) || warn("Google Places Details API error: over query limit.")
32
+ raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Google Places Details API error: over query limit.")
33
33
  when "REQUEST_DENIED"
34
- raise_error(Geocoder::RequestDenied) || warn("Google Places Details API error: request denied.")
34
+ raise_error(Geocoder::RequestDenied) || Geocoder.log(:warn, "Google Places Details API error: request denied.")
35
35
  when "INVALID_REQUEST"
36
- raise_error(Geocoder::InvalidRequest) || warn("Google Places Details API error: invalid request.")
36
+ raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, "Google Places Details API error: invalid request.")
37
37
  end
38
38
 
39
39
  []
@@ -45,13 +45,13 @@ module Geocoder::Lookup
45
45
  case doc['info']['statuscode']
46
46
  when 400 # Error with input
47
47
  raise_error(Geocoder::InvalidRequest, messages) ||
48
- warn("Mapquest Geocoding API error: #{messages}")
48
+ Geocoder.log(:warn, "Mapquest Geocoding API error: #{messages}")
49
49
  when 403 # Key related error
50
50
  raise_error(Geocoder::InvalidApiKey, messages) ||
51
- warn("Mapquest Geocoding API error: #{messages}")
51
+ Geocoder.log(:warn, "Mapquest Geocoding API error: #{messages}")
52
52
  when 500 # Unknown error
53
53
  raise_error(Geocoder::Error, messages) ||
54
- warn("Mapquest Geocoding API error: #{messages}")
54
+ Geocoder.log(:warn, "Mapquest Geocoding API error: #{messages}")
55
55
  end
56
56
  []
57
57
  end
@@ -64,7 +64,7 @@ module Geocoder::Lookup
64
64
  if !data_contains_error?(doc)
65
65
  return [doc]
66
66
  elsif doc.last == "INVALID_LICENSE_KEY"
67
- raise_error(Geocoder::InvalidApiKey) || warn("Invalid MaxMind API key.")
67
+ raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid MaxMind API key.")
68
68
  end
69
69
  end
70
70
  return []
@@ -0,0 +1,69 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/maxmind_geoip2'
3
+
4
+ module Geocoder::Lookup
5
+ class MaxmindGeoip2 < Base
6
+
7
+ def name
8
+ "MaxMind GeoIP2"
9
+ end
10
+
11
+ def use_ssl?
12
+ # Maxmind's GeoIP2 Precision Services only supports HTTPS,
13
+ # otherwise a `404 Not Found` HTTP response will be returned
14
+ true
15
+ end
16
+
17
+ def query_url(query)
18
+ "#{protocol}://geoip.maxmind.com/geoip/v2.1/#{configured_service!}/#{URI.escape(query.sanitized_text.strip)}"
19
+ end
20
+
21
+ private # ---------------------------------------------------------------
22
+
23
+ ##
24
+ # Return the name of the configured service, or raise an exception.
25
+ #
26
+ def configured_service!
27
+ if s = configuration[:service] and services.include?(s) and configuration[:basic_auth][:user] and configuration[:basic_auth][:password]
28
+ return s
29
+ else
30
+ raise(
31
+ Geocoder::ConfigurationError,
32
+ "When using MaxMind GeoIP2 you MUST specify a service name and basic_auth: " +
33
+ "Geocoder.configure(:maxmind_geoip2 => {:service => ...}, " +
34
+ ":basic_auth => {:user ..., :password => ...}), " +
35
+ "where service is one of: #{services.inspect}"
36
+ )
37
+ end
38
+ end
39
+
40
+ def data_contains_error?(doc)
41
+ (["code", "error"] - doc.keys).empty?
42
+ end
43
+
44
+ ##
45
+ # Service names used in URL.
46
+ #
47
+ def services
48
+ [
49
+ :country,
50
+ :city,
51
+ :insights,
52
+ ]
53
+ end
54
+
55
+ def results(query)
56
+ # don't look up a loopback address
57
+ return [] if query.loopback_ip_address?
58
+ doc = fetch_data(query)
59
+ if doc
60
+ if !data_contains_error?(doc)
61
+ return [doc]
62
+ else
63
+ Geocoder.log(:warn, "MaxMind GeoIP2 Geocoding API error: #{doc['code']} (#{doc['error']}).")
64
+ end
65
+ end
66
+ return []
67
+ end
68
+ end
69
+ end
@@ -27,7 +27,7 @@ module Geocoder::Lookup
27
27
 
28
28
  def parse_raw_data(raw_data)
29
29
  if raw_data.include?("Bandwidth limit exceeded")
30
- raise_error(Geocoder::OverQueryLimitError) || warn("Over API query limit.")
30
+ raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Over API query limit.")
31
31
  else
32
32
  super(raw_data)
33
33
  end
@@ -26,16 +26,16 @@ module Geocoder::Lookup
26
26
  case doc['status']['code']
27
27
  when 400 # Error with input
28
28
  raise_error(Geocoder::InvalidRequest, messages) ||
29
- warn("Opencagedata Geocoding API error: #{messages}")
29
+ Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
30
30
  when 403 # Key related error
31
31
  raise_error(Geocoder::InvalidApiKey, messages) ||
32
- warn("Opencagedata Geocoding API error: #{messages}")
32
+ Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
33
33
  when 402 # Quata Exceeded
34
34
  raise_error(Geocoder::OverQueryLimitError, messages) ||
35
- warn("Opencagedata Geocoding API error: #{messages}")
35
+ Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
36
36
  when 500 # Unknown error
37
37
  raise_error(Geocoder::Error, messages) ||
38
- warn("Opencagedata Geocoding API error: #{messages}")
38
+ Geocoder.log(:warn, "Opencagedata Geocoding API error: #{messages}")
39
39
  end
40
40
 
41
41
  return doc["results"]
@@ -28,14 +28,14 @@ module Geocoder::Lookup
28
28
  elsif doc['error']
29
29
  case doc['error']
30
30
  when "Invalid IP address"
31
- raise_error(Geocoder::InvalidRequest) || warn("Invalid Pointpin request.")
31
+ raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, "Invalid Pointpin request.")
32
32
  when "Invalid API key"
33
- raise_error(Geocoder::InvalidApiKey) || warn("Invalid Pointpin API key.")
33
+ raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid Pointpin API key.")
34
34
  when "Address not found"
35
- warn("Address not found.")
35
+ Geocoder.log(:warn, "Address not found.")
36
36
  end
37
37
  else
38
- raise_error(Geocoder::Error) || warn("Pointpin server error")
38
+ raise_error(Geocoder::Error) || Geocoder.log(:warn, "Pointpin server error")
39
39
  end
40
40
  end
41
41
 
@@ -33,11 +33,11 @@ module Geocoder::Lookup
33
33
  def raise_exception_for_response(response)
34
34
  case response['Error']
35
35
  when *DAILY_LIMIT_EXEEDED_ERROR_CODES
36
- raise_error(Geocoder::OverQueryLimitError, response['Cause']) || warn(response['Cause'])
36
+ raise_error(Geocoder::OverQueryLimitError, response['Cause']) || Geocoder.log(:warn, response['Cause'])
37
37
  when INVALID_API_KEY_ERROR_CODE
38
- raise_error(Geocoder::InvalidApiKey, response['Cause']) || warn(response['Cause'])
38
+ raise_error(Geocoder::InvalidApiKey, response['Cause']) || Geocoder.log(:warn, response['Cause'])
39
39
  else # anything else just raise general error with the api cause
40
- raise_error(Geocoder::Error, response['Cause']) || warn(response['Cause'])
40
+ raise_error(Geocoder::Error, response['Cause']) || Geocoder.log(:warn, response['Cause'])
41
41
  end
42
42
  end
43
43
 
@@ -37,7 +37,7 @@ module Geocoder::Lookup
37
37
  return []
38
38
  end
39
39
  else
40
- warn "Yahoo Geocoding API error: #{doc['responsecode']} (#{doc['reason']})."
40
+ Geocoder.log(:warn, "Yahoo Geocoding API error: #{doc['responsecode']} (#{doc['reason']}).")
41
41
  return []
42
42
  end
43
43
  end
@@ -52,9 +52,9 @@ module Geocoder::Lookup
52
52
  def parse_raw_data(raw_data)
53
53
  if raw_data.match(/^<\?xml/)
54
54
  if raw_data.include?("Rate Limit Exceeded")
55
- raise_error(Geocoder::OverQueryLimitError) || warn("Over API query limit.")
55
+ raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Over API query limit.")
56
56
  elsif raw_data =~ /<yahoo:description>(Please provide valid credentials.*)<\/yahoo:description>/i
57
- raise_error(Geocoder::InvalidApiKey) || warn("Invalid API key. Error response: #{$1}")
57
+ raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid API key. Error response: #{$1}")
58
58
  end
59
59
  else
60
60
  super(raw_data)
@@ -22,9 +22,9 @@ module Geocoder::Lookup
22
22
  return [] unless doc = fetch_data(query)
23
23
  if err = doc['error']
24
24
  if err["status"] == 401 and err["message"] == "invalid key"
25
- raise_error(Geocoder::InvalidApiKey) || warn("Invalid API key.")
25
+ raise_error(Geocoder::InvalidApiKey) || Geocoder.log(:warn, "Invalid API key.")
26
26
  else
27
- warn "Yandex Geocoding API error: #{err['status']} (#{err['message']})."
27
+ Geocoder.log(:warn, "Yandex Geocoding API error: #{err['status']} (#{err['message']}).")
28
28
  end
29
29
  return []
30
30
  end
@@ -32,7 +32,7 @@ module Geocoder::Lookup
32
32
  meta = doc['metaDataProperty']['GeocoderResponseMetaData']
33
33
  return meta['found'].to_i > 0 ? doc['featureMember'] : []
34
34
  else
35
- warn "Yandex Geocoding API error: unexpected response format."
35
+ Geocoder.log(:warn, "Yandex Geocoding API error: unexpected response format.")
36
36
  return []
37
37
  end
38
38
  end
@@ -32,7 +32,7 @@ module Geocoder
32
32
  # appropriate to the Query text.
33
33
  #
34
34
  def lookup
35
- if ip_address?
35
+ if !options[:street_address] and (options[:ip_address] or ip_address?)
36
36
  name = options[:ip_lookup] || Configuration.ip_lookup || Geocoder::Lookup.ip_services.first
37
37
  else
38
38
  name = options[:lookup] || Configuration.lookup || Geocoder::Lookup.street_services.first
@@ -1,21 +1,79 @@
1
1
  module Geocoder
2
2
  module Request
3
3
 
4
+ # The location() method is vulnerable to trivial IP spoofing.
5
+ # Don't use it in authorization/authentication code, or any
6
+ # other security-sensitive application. Use safe_location
7
+ # instead.
4
8
  def location
5
- @location ||= begin
6
- detected_ip = env['HTTP_X_REAL_IP'] || (
7
- env['HTTP_X_FORWARDED_FOR'] &&
8
- env['HTTP_X_FORWARDED_FOR'].split(",").first.strip
9
- )
10
- detected_ip = IpAddress.new(detected_ip.to_s)
11
- if detected_ip.valid? and !detected_ip.loopback?
12
- real_ip = detected_ip.to_s
13
- else
14
- real_ip = self.ip
9
+ @location ||= Geocoder.search(geocoder_spoofable_ip, ip_address: true).first
10
+ end
11
+
12
+ # This safe_location() protects you from trivial IP spoofing.
13
+ # For requests that go through a proxy that you haven't
14
+ # whitelisted as trusted in your Rack config, you will get the
15
+ # location for the IP of the last untrusted proxy in the chain,
16
+ # not the original client IP. You WILL NOT get the location
17
+ # corresponding to the original client IP for any request sent
18
+ # through a non-whitelisted proxy.
19
+ def safe_location
20
+ @location ||= Geocoder.search(ip, ip_address: true).first
21
+ end
22
+
23
+ # There's a whole zoo of nonstandard headers added by various
24
+ # proxy softwares to indicate original client IP.
25
+ # ANY of these can be trivially spoofed!
26
+ # (except REMOTE_ADDR, which should by set by your server,
27
+ # and is included at the end as a fallback.
28
+ # Order does matter: we're following the convention established in
29
+ # ActionDispatch::RemoteIp::GetIp::calculate_ip()
30
+ # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb
31
+ # where the forwarded_for headers, possibly containing lists,
32
+ # are arbitrarily preferred over headers expected to contain a
33
+ # single address.
34
+ GEOCODER_CANDIDATE_HEADERS = ['HTTP_X_FORWARDED_FOR',
35
+ 'HTTP_X_FORWARDED',
36
+ 'HTTP_FORWARDED_FOR',
37
+ 'HTTP_FORWARDED',
38
+ 'HTTP_X_CLIENT_IP',
39
+ 'HTTP_CLIENT_IP',
40
+ 'HTTP_X_REAL_IP',
41
+ 'HTTP_X_CLUSTER_CLIENT_IP',
42
+ 'REMOTE_ADDR']
43
+
44
+ def geocoder_spoofable_ip
45
+
46
+ # We could use a more sophisticated IP-guessing algorithm here,
47
+ # in which we'd try to resolve the use of different headers by
48
+ # different proxies. The idea is that by comparing IPs repeated
49
+ # in different headers, you can sometimes decide which header
50
+ # was used by a proxy further along in the chain, and thus
51
+ # prefer the headers used earlier. However, the gains might not
52
+ # be worth the performance tradeoff, since this method is likely
53
+ # to be called on every request in a lot of applications.
54
+ GEOCODER_CANDIDATE_HEADERS.each do |header|
55
+ if @env.has_key? header
56
+ addrs = geocoder_split_ip_addresses(@env[header])
57
+ addrs = geocoder_reject_trusted_ip_addresses(addrs)
58
+ return addrs.first if addrs.any?
15
59
  end
16
- Geocoder.search(real_ip).first
17
60
  end
18
- @location
61
+
62
+ @env['REMOTE_ADDR']
63
+ end
64
+
65
+ private
66
+
67
+ def geocoder_split_ip_addresses(ip_addresses)
68
+ ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
69
+ end
70
+
71
+ # use Rack's trusted_proxy?() method to filter out IPs that have
72
+ # been configured as trusted; includes private ranges by
73
+ # default. (we don't want every lookup to return the location
74
+ # of our own proxy/load balancer)
75
+ def geocoder_reject_trusted_ip_addresses(ip_addresses)
76
+ ip_addresses.reject { |ip| trusted_proxy?(ip) }
19
77
  end
20
78
  end
21
79
  end
@@ -13,41 +13,35 @@ module Geocoder
13
13
  end
14
14
 
15
15
  def latitude
16
- return 0.0 unless @data['location']
17
- @data['location']['latitude'].to_f
16
+ @data.fetch('location',{}).fetch('latitude',0.0)
18
17
  end
19
18
 
20
19
  def longitude
21
- return 0.0 unless @data['location']
22
- @data['location']['longitude'].to_f
20
+ @data.fetch('location',{}).fetch('longitude',0.0)
23
21
  end
24
22
 
25
23
  def city
26
- return '' unless @data['city']
27
- @data['city']['names']['en']
24
+ @data.fetch('city', {}).fetch('names', {}).fetch('en', '')
28
25
  end
29
26
 
30
27
  def state
31
- return '' unless @data['subdivisions']
32
- @data['subdivisions'][0]['names']['en']
28
+ @data.fetch('subdivisions',[]).fetch(0,{}).fetch('names',{}).fetch('en','')
33
29
  end
34
30
 
35
31
  def state_code
36
- return '' unless @data['subdivisions']
37
- @data['subdivisions'][0]['iso_code']
32
+ @data.fetch('subdivisions',[]).fetch(0,{}).fetch('iso_code','')
38
33
  end
39
34
 
40
35
  def country
41
- @data['country']['names']['en']
36
+ @data.fetch('country', {}).fetch('names',{}).fetch('en','')
42
37
  end
43
38
 
44
39
  def country_code
45
- @data['country']['iso_code']
40
+ @data.fetch('country',{}).fetch('iso_code','')
46
41
  end
47
42
 
48
43
  def postal_code
49
- return '' unless @data['postal']
50
- @data['postal']['code']
44
+ @data.fetch('postal',{}).fetch('code','')
51
45
  end
52
46
 
53
47
  def self.response_attributes