File Coverage

blib/lib/Geo/IPinfo.pm
Criterion Covered Total %
statement 141 216 65.2
branch 35 88 39.7
condition 3 10 30.0
subroutine 22 27 81.4
pod 6 7 85.7
total 207 348 59.4


line stmt bran cond sub pod time code
1             package Geo::IPinfo;
2              
3 2     2   451424 use 5.006;
  2         8  
4 2     2   12 use strict;
  2         4  
  2         102  
5 2     2   17 use warnings;
  2         40  
  2         168  
6 2     2   960 use Cache::LRU;
  2         1297  
  2         55  
7 2     2   1159 use LWP::UserAgent;
  2         101560  
  2         87  
8 2     2   19 use HTTP::Headers;
  2         4  
  2         48  
9 2     2   1414 use JSON;
  2         23003  
  2         16  
10 2     2   1269 use Geo::Details;
  2         7  
  2         83  
11 2     2   1052 use Net::CIDR;
  2         14014  
  2         143  
12 2     2   1084 use Net::CIDR::Set;
  2         51389  
  2         95  
13              
14             our $VERSION = '3.3.0';
15 2     2   17 use constant DEFAULT_CACHE_MAX_SIZE => 4096;
  2         3  
  2         98  
16 2     2   8 use constant DEFAULT_CACHE_TTL => 86_400;
  2         6  
  2         58  
17 2     2   11 use constant DEFAULT_TIMEOUT => 2;
  2         2  
  2         53  
18 2     2   6 use constant HTTP_TOO_MANY_REQUEST => 429;
  2         2  
  2         12597  
19              
20             my %valid_fields = (
21             ip => 1,
22             hostname => 1,
23             city => 1,
24             region => 1,
25             country => 1,
26             loc => 1,
27             org => 1,
28             postal => 1,
29             timezone => 1,
30             geo => 1,
31             asn => 1,
32             company => 1,
33             privacy => 1,
34             abuse => 1,
35             domains => 1,
36             );
37             my $base_url = 'https://ipinfo.io/';
38             my $base_url_ipv6 = 'https://v6.ipinfo.io/';
39             my $country_flag_url = 'https://cdn.ipinfo.io/static/images/countries-flags/';
40             my $cache_ttl = 0;
41             my $custom_cache = 0;
42             my %default_countries = (
43             "BD" => "Bangladesh",
44             "BE" => "Belgium",
45             "BF" => "Burkina Faso",
46             "BG" => "Bulgaria",
47             "BA" => "Bosnia and Herzegovina",
48             "BB" => "Barbados",
49             "WF" => "Wallis and Futuna",
50             "BL" => "Saint Barthelemy",
51             "BM" => "Bermuda",
52             "BN" => "Brunei",
53             "BO" => "Bolivia",
54             "BH" => "Bahrain",
55             "BI" => "Burundi",
56             "BJ" => "Benin",
57             "BT" => "Bhutan",
58             "JM" => "Jamaica",
59             "BV" => "Bouvet Island",
60             "BW" => "Botswana",
61             "WS" => "Samoa",
62             "BQ" => "Bonaire, Saint Eustatius and Saba ",
63             "BR" => "Brazil",
64             "BS" => "Bahamas",
65             "JE" => "Jersey",
66             "BY" => "Belarus",
67             "BZ" => "Belize",
68             "RU" => "Russia",
69             "RW" => "Rwanda",
70             "RS" => "Serbia",
71             "TL" => "East Timor",
72             "RE" => "Reunion",
73             "TM" => "Turkmenistan",
74             "TJ" => "Tajikistan",
75             "RO" => "Romania",
76             "TK" => "Tokelau",
77             "GW" => "Guinea-Bissau",
78             "GU" => "Guam",
79             "GT" => "Guatemala",
80             "GS" => "South Georgia and the South Sandwich Islands",
81             "GR" => "Greece",
82             "GQ" => "Equatorial Guinea",
83             "GP" => "Guadeloupe",
84             "JP" => "Japan",
85             "GY" => "Guyana",
86             "GG" => "Guernsey",
87             "GF" => "French Guiana",
88             "GE" => "Georgia",
89             "GD" => "Grenada",
90             "GB" => "United Kingdom",
91             "GA" => "Gabon",
92             "SV" => "El Salvador",
93             "GN" => "Guinea",
94             "GM" => "Gambia",
95             "GL" => "Greenland",
96             "GI" => "Gibraltar",
97             "GH" => "Ghana",
98             "OM" => "Oman",
99             "TN" => "Tunisia",
100             "JO" => "Jordan",
101             "HR" => "Croatia",
102             "HT" => "Haiti",
103             "HU" => "Hungary",
104             "HK" => "Hong Kong",
105             "HN" => "Honduras",
106             "HM" => "Heard Island and McDonald Islands",
107             "VE" => "Venezuela",
108             "PR" => "Puerto Rico",
109             "PS" => "Palestinian Territory",
110             "PW" => "Palau",
111             "PT" => "Portugal",
112             "SJ" => "Svalbard and Jan Mayen",
113             "PY" => "Paraguay",
114             "IQ" => "Iraq",
115             "PA" => "Panama",
116             "PF" => "French Polynesia",
117             "PG" => "Papua New Guinea",
118             "PE" => "Peru",
119             "PK" => "Pakistan",
120             "PH" => "Philippines",
121             "PN" => "Pitcairn",
122             "PL" => "Poland",
123             "PM" => "Saint Pierre and Miquelon",
124             "ZM" => "Zambia",
125             "EH" => "Western Sahara",
126             "EE" => "Estonia",
127             "EG" => "Egypt",
128             "ZA" => "South Africa",
129             "EC" => "Ecuador",
130             "IT" => "Italy",
131             "VN" => "Vietnam",
132             "SB" => "Solomon Islands",
133             "ET" => "Ethiopia",
134             "SO" => "Somalia",
135             "ZW" => "Zimbabwe",
136             "SA" => "Saudi Arabia",
137             "ES" => "Spain",
138             "ER" => "Eritrea",
139             "ME" => "Montenegro",
140             "MD" => "Moldova",
141             "MG" => "Madagascar",
142             "MF" => "Saint Martin",
143             "MA" => "Morocco",
144             "MC" => "Monaco",
145             "UZ" => "Uzbekistan",
146             "MM" => "Myanmar",
147             "ML" => "Mali",
148             "MO" => "Macao",
149             "MN" => "Mongolia",
150             "MH" => "Marshall Islands",
151             "MK" => "Macedonia",
152             "MU" => "Mauritius",
153             "MT" => "Malta",
154             "MW" => "Malawi",
155             "MV" => "Maldives",
156             "MQ" => "Martinique",
157             "MP" => "Northern Mariana Islands",
158             "MS" => "Montserrat",
159             "MR" => "Mauritania",
160             "IM" => "Isle of Man",
161             "UG" => "Uganda",
162             "TZ" => "Tanzania",
163             "MY" => "Malaysia",
164             "MX" => "Mexico",
165             "IL" => "Israel",
166             "FR" => "France",
167             "IO" => "British Indian Ocean Territory",
168             "SH" => "Saint Helena",
169             "FI" => "Finland",
170             "FJ" => "Fiji",
171             "FK" => "Falkland Islands",
172             "FM" => "Micronesia",
173             "FO" => "Faroe Islands",
174             "NI" => "Nicaragua",
175             "NL" => "Netherlands",
176             "NO" => "Norway",
177             "NA" => "Namibia",
178             "VU" => "Vanuatu",
179             "NC" => "New Caledonia",
180             "NE" => "Niger",
181             "NF" => "Norfolk Island",
182             "NG" => "Nigeria",
183             "NZ" => "New Zealand",
184             "NP" => "Nepal",
185             "NR" => "Nauru",
186             "NU" => "Niue",
187             "CK" => "Cook Islands",
188             "XK" => "Kosovo",
189             "CI" => "Ivory Coast",
190             "CH" => "Switzerland",
191             "CO" => "Colombia",
192             "CN" => "China",
193             "CM" => "Cameroon",
194             "CL" => "Chile",
195             "CC" => "Cocos Islands",
196             "CA" => "Canada",
197             "CG" => "Republic of the Congo",
198             "CF" => "Central African Republic",
199             "CD" => "Democratic Republic of the Congo",
200             "CZ" => "Czech Republic",
201             "CY" => "Cyprus",
202             "CX" => "Christmas Island",
203             "CR" => "Costa Rica",
204             "CW" => "Curacao",
205             "CV" => "Cape Verde",
206             "CU" => "Cuba",
207             "SZ" => "Swaziland",
208             "SY" => "Syria",
209             "SX" => "Sint Maarten",
210             "KG" => "Kyrgyzstan",
211             "KE" => "Kenya",
212             "SS" => "South Sudan",
213             "SR" => "Suriname",
214             "KI" => "Kiribati",
215             "KH" => "Cambodia",
216             "KN" => "Saint Kitts and Nevis",
217             "KM" => "Comoros",
218             "ST" => "Sao Tome and Principe",
219             "SK" => "Slovakia",
220             "KR" => "South Korea",
221             "SI" => "Slovenia",
222             "KP" => "North Korea",
223             "KW" => "Kuwait",
224             "SN" => "Senegal",
225             "SM" => "San Marino",
226             "SL" => "Sierra Leone",
227             "SC" => "Seychelles",
228             "KZ" => "Kazakhstan",
229             "KY" => "Cayman Islands",
230             "SG" => "Singapore",
231             "SE" => "Sweden",
232             "SD" => "Sudan",
233             "DO" => "Dominican Republic",
234             "DM" => "Dominica",
235             "DJ" => "Djibouti",
236             "DK" => "Denmark",
237             "VG" => "British Virgin Islands",
238             "DE" => "Germany",
239             "YE" => "Yemen",
240             "DZ" => "Algeria",
241             "US" => "United States",
242             "UY" => "Uruguay",
243             "YT" => "Mayotte",
244             "UM" => "United States Minor Outlying Islands",
245             "LB" => "Lebanon",
246             "LC" => "Saint Lucia",
247             "LA" => "Laos",
248             "TV" => "Tuvalu",
249             "TW" => "Taiwan",
250             "TT" => "Trinidad and Tobago",
251             "TR" => "Turkey",
252             "LK" => "Sri Lanka",
253             "LI" => "Liechtenstein",
254             "LV" => "Latvia",
255             "TO" => "Tonga",
256             "LT" => "Lithuania",
257             "LU" => "Luxembourg",
258             "LR" => "Liberia",
259             "LS" => "Lesotho",
260             "TH" => "Thailand",
261             "TF" => "French Southern Territories",
262             "TG" => "Togo",
263             "TD" => "Chad",
264             "TC" => "Turks and Caicos Islands",
265             "LY" => "Libya",
266             "VA" => "Vatican",
267             "VC" => "Saint Vincent and the Grenadines",
268             "AE" => "United Arab Emirates",
269             "AD" => "Andorra",
270             "AG" => "Antigua and Barbuda",
271             "AF" => "Afghanistan",
272             "AI" => "Anguilla",
273             "VI" => "U.S. Virgin Islands",
274             "IS" => "Iceland",
275             "IR" => "Iran",
276             "AM" => "Armenia",
277             "AL" => "Albania",
278             "AO" => "Angola",
279             "AQ" => "Antarctica",
280             "AS" => "American Samoa",
281             "AR" => "Argentina",
282             "AU" => "Australia",
283             "AT" => "Austria",
284             "AW" => "Aruba",
285             "IN" => "India",
286             "AX" => "Aland Islands",
287             "AZ" => "Azerbaijan",
288             "IE" => "Ireland",
289             "ID" => "Indonesia",
290             "UA" => "Ukraine",
291             "QA" => "Qatar",
292             "MZ" => "Mozambique"
293             );
294             my %default_countries_flags = (
295             'AD' => { 'emoji' => '🇦🇩', 'unicode' => 'U+1F1E6 U+1F1E9' },
296             'AE' => { 'emoji' => '🇦🇪', 'unicode' => 'U+1F1E6 U+1F1EA' },
297             'AF' => { 'emoji' => '🇦🇫', 'unicode' => 'U+1F1E6 U+1F1EB' },
298             'AG' => { 'emoji' => '🇦🇬', 'unicode' => 'U+1F1E6 U+1F1EC' },
299             'AI' => { 'emoji' => '🇦🇮', 'unicode' => 'U+1F1E6 U+1F1EE' },
300             'AL' => { 'emoji' => '🇦🇱', 'unicode' => 'U+1F1E6 U+1F1F1' },
301             'AM' => { 'emoji' => '🇦🇲', 'unicode' => 'U+1F1E6 U+1F1F2' },
302             'AO' => { 'emoji' => '🇦🇴', 'unicode' => 'U+1F1E6 U+1F1F4' },
303             'AQ' => { 'emoji' => '🇦🇶', 'unicode' => 'U+1F1E6 U+1F1F6' },
304             'AR' => { 'emoji' => '🇦🇷', 'unicode' => 'U+1F1E6 U+1F1F7' },
305             'AS' => { 'emoji' => '🇦🇸', 'unicode' => 'U+1F1E6 U+1F1F8' },
306             'AT' => { 'emoji' => '🇦🇹', 'unicode' => 'U+1F1E6 U+1F1F9' },
307             'AU' => { 'emoji' => '🇦🇺', 'unicode' => 'U+1F1E6 U+1F1FA' },
308             'AW' => { 'emoji' => '🇦🇼', 'unicode' => 'U+1F1E6 U+1F1FC' },
309             'AX' => { 'emoji' => '🇦🇽', 'unicode' => 'U+1F1E6 U+1F1FD' },
310             'AZ' => { 'emoji' => '🇦🇿', 'unicode' => 'U+1F1E6 U+1F1FF' },
311             'BA' => { 'emoji' => '🇧🇦', 'unicode' => 'U+1F1E7 U+1F1E6' },
312             'BB' => { 'emoji' => '🇧🇧', 'unicode' => 'U+1F1E7 U+1F1E7' },
313             'BD' => { 'emoji' => '🇧🇩', 'unicode' => 'U+1F1E7 U+1F1E9' },
314             'BE' => { 'emoji' => '🇧🇪', 'unicode' => 'U+1F1E7 U+1F1EA' },
315             'BF' => { 'emoji' => '🇧🇫', 'unicode' => 'U+1F1E7 U+1F1EB' },
316             'BG' => { 'emoji' => '🇧🇬', 'unicode' => 'U+1F1E7 U+1F1EC' },
317             'BH' => { 'emoji' => '🇧🇭', 'unicode' => 'U+1F1E7 U+1F1ED' },
318             'BI' => { 'emoji' => '🇧🇮', 'unicode' => 'U+1F1E7 U+1F1EE' },
319             'BJ' => { 'emoji' => '🇧🇯', 'unicode' => 'U+1F1E7 U+1F1EF' },
320             'BL' => { 'emoji' => '🇧🇱', 'unicode' => 'U+1F1E7 U+1F1F1' },
321             'BM' => { 'emoji' => '🇧🇲', 'unicode' => 'U+1F1E7 U+1F1F2' },
322             'BN' => { 'emoji' => '🇧🇳', 'unicode' => 'U+1F1E7 U+1F1F3' },
323             'BO' => { 'emoji' => '🇧🇴', 'unicode' => 'U+1F1E7 U+1F1F4' },
324             'BQ' => { 'emoji' => '🇧🇶', 'unicode' => 'U+1F1E7 U+1F1F6' },
325             'BR' => { 'emoji' => '🇧🇷', 'unicode' => 'U+1F1E7 U+1F1F7' },
326             'BS' => { 'emoji' => '🇧🇸', 'unicode' => 'U+1F1E7 U+1F1F8' },
327             'BT' => { 'emoji' => '🇧🇹', 'unicode' => 'U+1F1E7 U+1F1F9' },
328             'BV' => { 'emoji' => '🇧🇻', 'unicode' => 'U+1F1E7 U+1F1FB' },
329             'BW' => { 'emoji' => '🇧🇼', 'unicode' => 'U+1F1E7 U+1F1FC' },
330             'BY' => { 'emoji' => '🇧🇾', 'unicode' => 'U+1F1E7 U+1F1FE' },
331             'BZ' => { 'emoji' => '🇧🇿', 'unicode' => 'U+1F1E7 U+1F1FF' },
332             'CA' => { 'emoji' => '🇨🇦', 'unicode' => 'U+1F1E8 U+1F1E6' },
333             'CC' => { 'emoji' => '🇨🇨', 'unicode' => 'U+1F1E8 U+1F1E8' },
334             'CD' => { 'emoji' => '🇨🇩', 'unicode' => 'U+1F1E8 U+1F1E9' },
335             'CF' => { 'emoji' => '🇨🇫', 'unicode' => 'U+1F1E8 U+1F1EB' },
336             'CG' => { 'emoji' => '🇨🇬', 'unicode' => 'U+1F1E8 U+1F1EC' },
337             'CH' => { 'emoji' => '🇨🇭', 'unicode' => 'U+1F1E8 U+1F1ED' },
338             'CI' => { 'emoji' => '🇨🇮', 'unicode' => 'U+1F1E8 U+1F1EE' },
339             'CK' => { 'emoji' => '🇨🇰', 'unicode' => 'U+1F1E8 U+1F1F0' },
340             'CL' => { 'emoji' => '🇨🇱', 'unicode' => 'U+1F1E8 U+1F1F1' },
341             'CM' => { 'emoji' => '🇨🇲', 'unicode' => 'U+1F1E8 U+1F1F2' },
342             'CN' => { 'emoji' => '🇨🇳', 'unicode' => 'U+1F1E8 U+1F1F3' },
343             'CO' => { 'emoji' => '🇨🇴', 'unicode' => 'U+1F1E8 U+1F1F4' },
344             'CR' => { 'emoji' => '🇨🇷', 'unicode' => 'U+1F1E8 U+1F1F7' },
345             'CU' => { 'emoji' => '🇨🇺', 'unicode' => 'U+1F1E8 U+1F1FA' },
346             'CV' => { 'emoji' => '🇨🇻', 'unicode' => 'U+1F1E8 U+1F1FB' },
347             'CW' => { 'emoji' => '🇨🇼', 'unicode' => 'U+1F1E8 U+1F1FC' },
348             'CX' => { 'emoji' => '🇨🇽', 'unicode' => 'U+1F1E8 U+1F1FD' },
349             'CY' => { 'emoji' => '🇨🇾', 'unicode' => 'U+1F1E8 U+1F1FE' },
350             'CZ' => { 'emoji' => '🇨🇿', 'unicode' => 'U+1F1E8 U+1F1FF' },
351             'DE' => { 'emoji' => '🇩🇪', 'unicode' => 'U+1F1E9 U+1F1EA' },
352             'DJ' => { 'emoji' => '🇩🇯', 'unicode' => 'U+1F1E9 U+1F1EF' },
353             'DK' => { 'emoji' => '🇩🇰', 'unicode' => 'U+1F1E9 U+1F1F0' },
354             'DM' => { 'emoji' => '🇩🇲', 'unicode' => 'U+1F1E9 U+1F1F2' },
355             'DO' => { 'emoji' => '🇩🇴', 'unicode' => 'U+1F1E9 U+1F1F4' },
356             'DZ' => { 'emoji' => '🇩🇿', 'unicode' => 'U+1F1E9 U+1F1FF' },
357             'EC' => { 'emoji' => '🇪🇨', 'unicode' => 'U+1F1EA U+1F1E8' },
358             'EE' => { 'emoji' => '🇪🇪', 'unicode' => 'U+1F1EA U+1F1EA' },
359             'EG' => { 'emoji' => '🇪🇬', 'unicode' => 'U+1F1EA U+1F1EC' },
360             'EH' => { 'emoji' => '🇪🇭', 'unicode' => 'U+1F1EA U+1F1ED' },
361             'ER' => { 'emoji' => '🇪🇷', 'unicode' => 'U+1F1EA U+1F1F7' },
362             'ES' => { 'emoji' => '🇪🇸', 'unicode' => 'U+1F1EA U+1F1F8' },
363             'ET' => { 'emoji' => '🇪🇹', 'unicode' => 'U+1F1EA U+1F1F9' },
364             'FI' => { 'emoji' => '🇫🇮', 'unicode' => 'U+1F1EB U+1F1EE' },
365             'FJ' => { 'emoji' => '🇫🇯', 'unicode' => 'U+1F1EB U+1F1EF' },
366             'FK' => { 'emoji' => '🇫🇰', 'unicode' => 'U+1F1EB U+1F1F0' },
367             'FM' => { 'emoji' => '🇫🇲', 'unicode' => 'U+1F1EB U+1F1F2' },
368             'FO' => { 'emoji' => '🇫🇴', 'unicode' => 'U+1F1EB U+1F1F4' },
369             'FR' => { 'emoji' => '🇫🇷', 'unicode' => 'U+1F1EB U+1F1F7' },
370             'GA' => { 'emoji' => '🇬🇦', 'unicode' => 'U+1F1EC U+1F1E6' },
371             'GB' => { 'emoji' => '🇬🇧', 'unicode' => 'U+1F1EC U+1F1E7' },
372             'GD' => { 'emoji' => '🇬🇩', 'unicode' => 'U+1F1EC U+1F1E9' },
373             'GE' => { 'emoji' => '🇬🇪', 'unicode' => 'U+1F1EC U+1F1EA' },
374             'GF' => { 'emoji' => '🇬🇫', 'unicode' => 'U+1F1EC U+1F1EB' },
375             'GG' => { 'emoji' => '🇬🇬', 'unicode' => 'U+1F1EC U+1F1EC' },
376             'GH' => { 'emoji' => '🇬🇭', 'unicode' => 'U+1F1EC U+1F1ED' },
377             'GI' => { 'emoji' => '🇬🇮', 'unicode' => 'U+1F1EC U+1F1EE' },
378             'GL' => { 'emoji' => '🇬🇱', 'unicode' => 'U+1F1EC U+1F1F1' },
379             'GM' => { 'emoji' => '🇬🇲', 'unicode' => 'U+1F1EC U+1F1F2' },
380             'GN' => { 'emoji' => '🇬🇳', 'unicode' => 'U+1F1EC U+1F1F3' },
381             'GP' => { 'emoji' => '🇬🇵', 'unicode' => 'U+1F1EC U+1F1F5' },
382             'GQ' => { 'emoji' => '🇬🇶', 'unicode' => 'U+1F1EC U+1F1F6' },
383             'GR' => { 'emoji' => '🇬🇷', 'unicode' => 'U+1F1EC U+1F1F7' },
384             'GS' => { 'emoji' => '🇬🇸', 'unicode' => 'U+1F1EC U+1F1F8' },
385             'GT' => { 'emoji' => '🇬🇹', 'unicode' => 'U+1F1EC U+1F1F9' },
386             'GU' => { 'emoji' => '🇬🇺', 'unicode' => 'U+1F1EC U+1F1FA' },
387             'GW' => { 'emoji' => '🇬🇼', 'unicode' => 'U+1F1EC U+1F1FC' },
388             'GY' => { 'emoji' => '🇬🇾', 'unicode' => 'U+1F1EC U+1F1FE' },
389             'HK' => { 'emoji' => '🇭🇰', 'unicode' => 'U+1F1ED U+1F1F0' },
390             'HM' => { 'emoji' => '🇭🇲', 'unicode' => 'U+1F1ED U+1F1F2' },
391             'HN' => { 'emoji' => '🇭🇳', 'unicode' => 'U+1F1ED U+1F1F3' },
392             'HR' => { 'emoji' => '🇭🇷', 'unicode' => 'U+1F1ED U+1F1F7' },
393             'HT' => { 'emoji' => '🇭🇹', 'unicode' => 'U+1F1ED U+1F1F9' },
394             'HU' => { 'emoji' => '🇭🇺', 'unicode' => 'U+1F1ED U+1F1FA' },
395             'ID' => { 'emoji' => '🇮🇩', 'unicode' => 'U+1F1EE U+1F1E9' },
396             'IE' => { 'emoji' => '🇮🇪', 'unicode' => 'U+1F1EE U+1F1EA' },
397             'IL' => { 'emoji' => '🇮🇱', 'unicode' => 'U+1F1EE U+1F1F1' },
398             'IM' => { 'emoji' => '🇮🇲', 'unicode' => 'U+1F1EE U+1F1F2' },
399             'IN' => { 'emoji' => '🇮🇳', 'unicode' => 'U+1F1EE U+1F1F3' },
400             'IO' => { 'emoji' => '🇮🇴', 'unicode' => 'U+1F1EE U+1F1F4' },
401             'IQ' => { 'emoji' => '🇮🇶', 'unicode' => 'U+1F1EE U+1F1F6' },
402             'IR' => { 'emoji' => '🇮🇷', 'unicode' => 'U+1F1EE U+1F1F7' },
403             'IS' => { 'emoji' => '🇮🇸', 'unicode' => 'U+1F1EE U+1F1F8' },
404             'IT' => { 'emoji' => '🇮🇹', 'unicode' => 'U+1F1EE U+1F1F9' },
405             'JE' => { 'emoji' => '🇯🇪', 'unicode' => 'U+1F1EF U+1F1EA' },
406             'JM' => { 'emoji' => '🇯🇲', 'unicode' => 'U+1F1EF U+1F1F2' },
407             'JO' => { 'emoji' => '🇯🇴', 'unicode' => 'U+1F1EF U+1F1F4' },
408             'JP' => { 'emoji' => '🇯🇵', 'unicode' => 'U+1F1EF U+1F1F5' },
409             'KE' => { 'emoji' => '🇰🇪', 'unicode' => 'U+1F1F0 U+1F1EA' },
410             'KG' => { 'emoji' => '🇰🇬', 'unicode' => 'U+1F1F0 U+1F1EC' },
411             'KH' => { 'emoji' => '🇰🇭', 'unicode' => 'U+1F1F0 U+1F1ED' },
412             'KI' => { 'emoji' => '🇰🇮', 'unicode' => 'U+1F1F0 U+1F1EE' },
413             'KM' => { 'emoji' => '🇰🇲', 'unicode' => 'U+1F1F0 U+1F1F2' },
414             'KN' => { 'emoji' => '🇰🇳', 'unicode' => 'U+1F1F0 U+1F1F3' },
415             'KP' => { 'emoji' => '🇰🇵', 'unicode' => 'U+1F1F0 U+1F1F5' },
416             'KR' => { 'emoji' => '🇰🇷', 'unicode' => 'U+1F1F0 U+1F1F7' },
417             'KW' => { 'emoji' => '🇰🇼', 'unicode' => 'U+1F1F0 U+1F1FC' },
418             'KY' => { 'emoji' => '🇰🇾', 'unicode' => 'U+1F1F0 U+1F1FE' },
419             'KZ' => { 'emoji' => '🇰🇿', 'unicode' => 'U+1F1F0 U+1F1FF' },
420             'LA' => { 'emoji' => '🇱🇦', 'unicode' => 'U+1F1F1 U+1F1E6' },
421             'LB' => { 'emoji' => '🇱🇧', 'unicode' => 'U+1F1F1 U+1F1E7' },
422             'LC' => { 'emoji' => '🇱🇨', 'unicode' => 'U+1F1F1 U+1F1E8' },
423             'LI' => { 'emoji' => '🇱🇮', 'unicode' => 'U+1F1F1 U+1F1EE' },
424             'LK' => { 'emoji' => '🇱🇰', 'unicode' => 'U+1F1F1 U+1F1F0' },
425             'LR' => { 'emoji' => '🇱🇷', 'unicode' => 'U+1F1F1 U+1F1F7' },
426             'LS' => { 'emoji' => '🇱🇸', 'unicode' => 'U+1F1F1 U+1F1F8' },
427             'LT' => { 'emoji' => '🇱🇹', 'unicode' => 'U+1F1F1 U+1F1F9' },
428             'LU' => { 'emoji' => '🇱🇺', 'unicode' => 'U+1F1F1 U+1F1FA' },
429             'LV' => { 'emoji' => '🇱🇻', 'unicode' => 'U+1F1F1 U+1F1FB' },
430             'LY' => { 'emoji' => '🇱🇾', 'unicode' => 'U+1F1F1 U+1F1FE' },
431             'MA' => { 'emoji' => '🇲🇦', 'unicode' => 'U+1F1F2 U+1F1E6' },
432             'MC' => { 'emoji' => '🇲🇨', 'unicode' => 'U+1F1F2 U+1F1E8' },
433             'MD' => { 'emoji' => '🇲🇩', 'unicode' => 'U+1F1F2 U+1F1E9' },
434             'ME' => { 'emoji' => '🇲🇪', 'unicode' => 'U+1F1F2 U+1F1EA' },
435             'MF' => { 'emoji' => '🇲🇫', 'unicode' => 'U+1F1F2 U+1F1EB' },
436             'MG' => { 'emoji' => '🇲🇬', 'unicode' => 'U+1F1F2 U+1F1EC' },
437             'MH' => { 'emoji' => '🇲🇭', 'unicode' => 'U+1F1F2 U+1F1ED' },
438             'MK' => { 'emoji' => '🇲🇰', 'unicode' => 'U+1F1F2 U+1F1F0' },
439             'ML' => { 'emoji' => '🇲🇱', 'unicode' => 'U+1F1F2 U+1F1F1' },
440             'MM' => { 'emoji' => '🇲🇲', 'unicode' => 'U+1F1F2 U+1F1F2' },
441             'MN' => { 'emoji' => '🇲🇳', 'unicode' => 'U+1F1F2 U+1F1F3' },
442             'MO' => { 'emoji' => '🇲🇴', 'unicode' => 'U+1F1F2 U+1F1F4' },
443             'MP' => { 'emoji' => '🇲🇵', 'unicode' => 'U+1F1F2 U+1F1F5' },
444             'MQ' => { 'emoji' => '🇲🇶', 'unicode' => 'U+1F1F2 U+1F1F6' },
445             'MR' => { 'emoji' => '🇲🇷', 'unicode' => 'U+1F1F2 U+1F1F7' },
446             'MS' => { 'emoji' => '🇲🇸', 'unicode' => 'U+1F1F2 U+1F1F8' },
447             'MT' => { 'emoji' => '🇲🇹', 'unicode' => 'U+1F1F2 U+1F1F9' },
448             'MU' => { 'emoji' => '🇲🇺', 'unicode' => 'U+1F1F2 U+1F1FA' },
449             'MV' => { 'emoji' => '🇲🇻', 'unicode' => 'U+1F1F2 U+1F1FB' },
450             'MW' => { 'emoji' => '🇲🇼', 'unicode' => 'U+1F1F2 U+1F1FC' },
451             'MX' => { 'emoji' => '🇲🇽', 'unicode' => 'U+1F1F2 U+1F1FD' },
452             'MY' => { 'emoji' => '🇲🇾', 'unicode' => 'U+1F1F2 U+1F1FE' },
453             'MZ' => { 'emoji' => '🇲🇿', 'unicode' => 'U+1F1F2 U+1F1FF' },
454             'NA' => { 'emoji' => '🇳🇦', 'unicode' => 'U+1F1F3 U+1F1E6' },
455             'NC' => { 'emoji' => '🇳🇨', 'unicode' => 'U+1F1F3 U+1F1E8' },
456             'NE' => { 'emoji' => '🇳🇪', 'unicode' => 'U+1F1F3 U+1F1EA' },
457             'NF' => { 'emoji' => '🇳🇫', 'unicode' => 'U+1F1F3 U+1F1EB' },
458             'NG' => { 'emoji' => '🇳🇬', 'unicode' => 'U+1F1F3 U+1F1EC' },
459             'NI' => { 'emoji' => '🇳🇮', 'unicode' => 'U+1F1F3 U+1F1EE' },
460             'NL' => { 'emoji' => '🇳🇱', 'unicode' => 'U+1F1F3 U+1F1F1' },
461             'NO' => { 'emoji' => '🇳🇴', 'unicode' => 'U+1F1F3 U+1F1F4' },
462             'NP' => { 'emoji' => '🇳🇵', 'unicode' => 'U+1F1F3 U+1F1F5' },
463             'NR' => { 'emoji' => '🇳🇷', 'unicode' => 'U+1F1F3 U+1F1F7' },
464             'NU' => { 'emoji' => '🇳🇺', 'unicode' => 'U+1F1F3 U+1F1FA' },
465             'NZ' => { 'emoji' => '🇳🇿', 'unicode' => 'U+1F1F3 U+1F1FF' },
466             'OM' => { 'emoji' => '🇴🇲', 'unicode' => 'U+1F1F4 U+1F1F2' },
467             'PA' => { 'emoji' => '🇵🇦', 'unicode' => 'U+1F1F5 U+1F1E6' },
468             'PE' => { 'emoji' => '🇵🇪', 'unicode' => 'U+1F1F5 U+1F1EA' },
469             'PF' => { 'emoji' => '🇵🇫', 'unicode' => 'U+1F1F5 U+1F1EB' },
470             'PG' => { 'emoji' => '🇵🇬', 'unicode' => 'U+1F1F5 U+1F1EC' },
471             'PH' => { 'emoji' => '🇵🇭', 'unicode' => 'U+1F1F5 U+1F1ED' },
472             'PK' => { 'emoji' => '🇵🇰', 'unicode' => 'U+1F1F5 U+1F1F0' },
473             'PL' => { 'emoji' => '🇵🇱', 'unicode' => 'U+1F1F5 U+1F1F1' },
474             'PM' => { 'emoji' => '🇵🇲', 'unicode' => 'U+1F1F5 U+1F1F2' },
475             'PN' => { 'emoji' => '🇵🇳', 'unicode' => 'U+1F1F5 U+1F1F3' },
476             'PR' => { 'emoji' => '🇵🇷', 'unicode' => 'U+1F1F5 U+1F1F7' },
477             'PS' => { 'emoji' => '🇵🇸', 'unicode' => 'U+1F1F5 U+1F1F8' },
478             'PT' => { 'emoji' => '🇵🇹', 'unicode' => 'U+1F1F5 U+1F1F9' },
479             'PW' => { 'emoji' => '🇵🇼', 'unicode' => 'U+1F1F5 U+1F1FC' },
480             'PY' => { 'emoji' => '🇵🇾', 'unicode' => 'U+1F1F5 U+1F1FE' },
481             'QA' => { 'emoji' => '🇶🇦', 'unicode' => 'U+1F1F6 U+1F1E6' },
482             'RE' => { 'emoji' => '🇷🇪', 'unicode' => 'U+1F1F7 U+1F1EA' },
483             'RO' => { 'emoji' => '🇷🇴', 'unicode' => 'U+1F1F7 U+1F1F4' },
484             'RS' => { 'emoji' => '🇷🇸', 'unicode' => 'U+1F1F7 U+1F1F8' },
485             'RU' => { 'emoji' => '🇷🇺', 'unicode' => 'U+1F1F7 U+1F1FA' },
486             'RW' => { 'emoji' => '🇷🇼', 'unicode' => 'U+1F1F7 U+1F1FC' },
487             'SA' => { 'emoji' => '🇸🇦', 'unicode' => 'U+1F1F8 U+1F1E6' },
488             'SB' => { 'emoji' => '🇸🇧', 'unicode' => 'U+1F1F8 U+1F1E7' },
489             'SC' => { 'emoji' => '🇸🇨', 'unicode' => 'U+1F1F8 U+1F1E8' },
490             'SD' => { 'emoji' => '🇸🇩', 'unicode' => 'U+1F1F8 U+1F1E9' },
491             'SE' => { 'emoji' => '🇸🇪', 'unicode' => 'U+1F1F8 U+1F1EA' },
492             'SG' => { 'emoji' => '🇸🇬', 'unicode' => 'U+1F1F8 U+1F1EC' },
493             'SH' => { 'emoji' => '🇸🇭', 'unicode' => 'U+1F1F8 U+1F1ED' },
494             'SI' => { 'emoji' => '🇸🇮', 'unicode' => 'U+1F1F8 U+1F1EE' },
495             'SJ' => { 'emoji' => '🇸🇯', 'unicode' => 'U+1F1F8 U+1F1EF' },
496             'SK' => { 'emoji' => '🇸🇰', 'unicode' => 'U+1F1F8 U+1F1F0' },
497             'SL' => { 'emoji' => '🇸🇱', 'unicode' => 'U+1F1F8 U+1F1F1' },
498             'SM' => { 'emoji' => '🇸🇲', 'unicode' => 'U+1F1F8 U+1F1F2' },
499             'SN' => { 'emoji' => '🇸🇳', 'unicode' => 'U+1F1F8 U+1F1F3' },
500             'SO' => { 'emoji' => '🇸🇴', 'unicode' => 'U+1F1F8 U+1F1F4' },
501             'SR' => { 'emoji' => '🇸🇷', 'unicode' => 'U+1F1F8 U+1F1F7' },
502             'SS' => { 'emoji' => '🇸🇸', 'unicode' => 'U+1F1F8 U+1F1F8' },
503             'ST' => { 'emoji' => '🇸🇹', 'unicode' => 'U+1F1F8 U+1F1F9' },
504             'SV' => { 'emoji' => '🇸🇻', 'unicode' => 'U+1F1F8 U+1F1FB' },
505             'SX' => { 'emoji' => '🇸🇽', 'unicode' => 'U+1F1F8 U+1F1FD' },
506             'SY' => { 'emoji' => '🇸🇾', 'unicode' => 'U+1F1F8 U+1F1FE' },
507             'SZ' => { 'emoji' => '🇸🇿', 'unicode' => 'U+1F1F8 U+1F1FF' },
508             'TC' => { 'emoji' => '🇹🇨', 'unicode' => 'U+1F1F9 U+1F1E8' },
509             'TD' => { 'emoji' => '🇹🇩', 'unicode' => 'U+1F1F9 U+1F1E9' },
510             'TF' => { 'emoji' => '🇹🇫', 'unicode' => 'U+1F1F9 U+1F1EB' },
511             'TG' => { 'emoji' => '🇹🇬', 'unicode' => 'U+1F1F9 U+1F1EC' },
512             'TH' => { 'emoji' => '🇹🇭', 'unicode' => 'U+1F1F9 U+1F1ED' },
513             'TJ' => { 'emoji' => '🇹🇯', 'unicode' => 'U+1F1F9 U+1F1EF' },
514             'TK' => { 'emoji' => '🇹🇰', 'unicode' => 'U+1F1F9 U+1F1F0' },
515             'TL' => { 'emoji' => '🇹🇱', 'unicode' => 'U+1F1F9 U+1F1F1' },
516             'TM' => { 'emoji' => '🇹🇲', 'unicode' => 'U+1F1F9 U+1F1F2' },
517             'TN' => { 'emoji' => '🇹🇳', 'unicode' => 'U+1F1F9 U+1F1F3' },
518             'TO' => { 'emoji' => '🇹🇴', 'unicode' => 'U+1F1F9 U+1F1F4' },
519             'TR' => { 'emoji' => '🇹🇷', 'unicode' => 'U+1F1F9 U+1F1F7' },
520             'TT' => { 'emoji' => '🇹🇹', 'unicode' => 'U+1F1F9 U+1F1F9' },
521             'TV' => { 'emoji' => '🇹🇻', 'unicode' => 'U+1F1F9 U+1F1FB' },
522             'TW' => { 'emoji' => '🇹🇼', 'unicode' => 'U+1F1F9 U+1F1FC' },
523             'TZ' => { 'emoji' => '🇹🇿', 'unicode' => 'U+1F1F9 U+1F1FF' },
524             'UA' => { 'emoji' => '🇺🇦', 'unicode' => 'U+1F1FA U+1F1E6' },
525             'UG' => { 'emoji' => '🇺🇬', 'unicode' => 'U+1F1FA U+1F1EC' },
526             'UM' => { 'emoji' => '🇺🇲', 'unicode' => 'U+1F1FA U+1F1F2' },
527             'US' => { 'emoji' => '🇺🇸', 'unicode' => 'U+1F1FA U+1F1F8' },
528             'UY' => { 'emoji' => '🇺🇾', 'unicode' => 'U+1F1FA U+1F1FE' },
529             'UZ' => { 'emoji' => '🇺🇿', 'unicode' => 'U+1F1FA U+1F1FF' },
530             'VA' => { 'emoji' => '🇻🇦', 'unicode' => 'U+1F1FB U+1F1E6' },
531             'VC' => { 'emoji' => '🇻🇨', 'unicode' => 'U+1F1FB U+1F1E8' },
532             'VE' => { 'emoji' => '🇻🇪', 'unicode' => 'U+1F1FB U+1F1EA' },
533             'VG' => { 'emoji' => '🇻🇬', 'unicode' => 'U+1F1FB U+1F1EC' },
534             'VI' => { 'emoji' => '🇻🇮', 'unicode' => 'U+1F1FB U+1F1EE' },
535             'VN' => { 'emoji' => '🇻🇳', 'unicode' => 'U+1F1FB U+1F1F3' },
536             'VU' => { 'emoji' => '🇻🇺', 'unicode' => 'U+1F1FB U+1F1FA' },
537             'WF' => { 'emoji' => '🇼🇫', 'unicode' => 'U+1F1FC U+1F1EB' },
538             'WS' => { 'emoji' => '🇼🇸', 'unicode' => 'U+1F1FC U+1F1F8' },
539             'XK' => { 'emoji' => '🇽🇰', 'unicode' => 'U+1F1FD U+1F1F0' },
540             'YE' => { 'emoji' => '🇾🇪', 'unicode' => 'U+1F1FE U+1F1EA' },
541             'YT' => { 'emoji' => '🇾🇹', 'unicode' => 'U+1F1FE U+1F1F9' },
542             'ZA' => { 'emoji' => '🇿🇦', 'unicode' => 'U+1F1FF U+1F1E6' },
543             'ZM' => { 'emoji' => '🇿🇲', 'unicode' => 'U+1F1FF U+1F1F2' },
544             'ZW' => { 'emoji' => '🇿🇼', 'unicode' => 'U+1F1FF U+1F1FC' }
545             );
546             my @default_eu_countries = (
547             "IE", "AT", "LT", "LU", "LV", "DE", "DK", "SE", "SI", "SK",
548             "CZ", "CY", "NL", "FI", "FR", "MT", "ES", "IT", "EE", "PL",
549             "PT", "HU", "HR", "GR", "RO", "BG", "BE"
550             );
551             my %default_countries_currencies = (
552             'AD' => { 'code' => 'EUR', 'symbol' => '€' },
553             'AE' => { 'code' => 'AED', 'symbol' => 'د.إ' },
554             'AF' => { 'code' => 'AFN', 'symbol' => '؋' },
555             'AG' => { 'code' => 'XCD', 'symbol' => '$' },
556             'AI' => { 'code' => 'XCD', 'symbol' => '$' },
557             'AL' => { 'code' => 'ALL', 'symbol' => 'L' },
558             'AM' => { 'code' => 'AMD', 'symbol' => '֏' },
559             'AO' => { 'code' => 'AOA', 'symbol' => 'Kz' },
560             'AQ' => { 'code' => '', 'symbol' => '$' },
561             'AR' => { 'code' => 'ARS', 'symbol' => '$' },
562             'AS' => { 'code' => 'USD', 'symbol' => '$' },
563             'AT' => { 'code' => 'EUR', 'symbol' => '€' },
564             'AU' => { 'code' => 'AUD', 'symbol' => '$' },
565             'AW' => { 'code' => 'AWG', 'symbol' => 'ƒ' },
566             'AX' => { 'code' => 'EUR', 'symbol' => '€' },
567             'AZ' => { 'code' => 'AZN', 'symbol' => '₼' },
568             'BA' => { 'code' => 'BAM', 'symbol' => 'KM' },
569             'BB' => { 'code' => 'BBD', 'symbol' => '$' },
570             'BD' => { 'code' => 'BDT', 'symbol' => '৳' },
571             'BE' => { 'code' => 'EUR', 'symbol' => '€' },
572             'BF' => { 'code' => 'XOF', 'symbol' => 'CFA' },
573             'BG' => { 'code' => 'BGN', 'symbol' => 'лв' },
574             'BH' => { 'code' => 'BHD', 'symbol' => '.د.ب' },
575             'BI' => { 'code' => 'BIF', 'symbol' => 'FBu' },
576             'BJ' => { 'code' => 'XOF', 'symbol' => 'CFA' },
577             'BL' => { 'code' => 'EUR', 'symbol' => '€' },
578             'BM' => { 'code' => 'BMD', 'symbol' => '$' },
579             'BN' => { 'code' => 'BND', 'symbol' => '$' },
580             'BO' => { 'code' => 'BOB', 'symbol' => '$b' },
581             'BQ' => { 'code' => 'USD', 'symbol' => '$' },
582             'BR' => { 'code' => 'BRL', 'symbol' => 'R$' },
583             'BS' => { 'code' => 'BSD', 'symbol' => '$' },
584             'BT' => { 'code' => 'BTN', 'symbol' => 'Nu.' },
585             'BV' => { 'code' => 'NOK', 'symbol' => 'kr' },
586             'BW' => { 'code' => 'BWP', 'symbol' => 'P' },
587             'BY' => { 'code' => 'BYR', 'symbol' => 'Br' },
588             'BZ' => { 'code' => 'BZD', 'symbol' => 'BZ$' },
589             'CA' => { 'code' => 'CAD', 'symbol' => '$' },
590             'CC' => { 'code' => 'AUD', 'symbol' => '$' },
591             'CD' => { 'code' => 'CDF', 'symbol' => 'FC' },
592             'CF' => { 'code' => 'XAF', 'symbol' => 'FCFA' },
593             'CG' => { 'code' => 'XAF', 'symbol' => 'FCFA' },
594             'CH' => { 'code' => 'CHF', 'symbol' => 'CHF' },
595             'CI' => { 'code' => 'XOF', 'symbol' => 'CFA' },
596             'CK' => { 'code' => 'NZD', 'symbol' => '$' },
597             'CL' => { 'code' => 'CLP', 'symbol' => '$' },
598             'CM' => { 'code' => 'XAF', 'symbol' => 'FCFA' },
599             'CN' => { 'code' => 'CNY', 'symbol' => '¥' },
600             'CO' => { 'code' => 'COP', 'symbol' => '$' },
601             'CR' => { 'code' => 'CRC', 'symbol' => '₡' },
602             'CU' => { 'code' => 'CUP', 'symbol' => '₱' },
603             'CV' => { 'code' => 'CVE', 'symbol' => '$' },
604             'CW' => { 'code' => 'ANG', 'symbol' => 'ƒ' },
605             'CX' => { 'code' => 'AUD', 'symbol' => '$' },
606             'CY' => { 'code' => 'EUR', 'symbol' => '€' },
607             'CZ' => { 'code' => 'CZK', 'symbol' => 'Kč' },
608             'DE' => { 'code' => 'EUR', 'symbol' => '€' },
609             'DJ' => { 'code' => 'DJF', 'symbol' => 'Fdj' },
610             'DK' => { 'code' => 'DKK', 'symbol' => 'kr' },
611             'DM' => { 'code' => 'XCD', 'symbol' => '$' },
612             'DO' => { 'code' => 'DOP', 'symbol' => 'RD$' },
613             'DZ' => { 'code' => 'DZD', 'symbol' => 'دج' },
614             'EC' => { 'code' => 'USD', 'symbol' => '$' },
615             'EE' => { 'code' => 'EUR', 'symbol' => '€' },
616             'EG' => { 'code' => 'EGP', 'symbol' => '£' },
617             'EH' => { 'code' => 'MAD', 'symbol' => 'MAD' },
618             'ER' => { 'code' => 'ERN', 'symbol' => 'Nfk' },
619             'ES' => { 'code' => 'EUR', 'symbol' => '€' },
620             'ET' => { 'code' => 'ETB', 'symbol' => 'Br' },
621             'FI' => { 'code' => 'EUR', 'symbol' => '€' },
622             'FJ' => { 'code' => 'FJD', 'symbol' => '$' },
623             'FK' => { 'code' => 'FKP', 'symbol' => '£' },
624             'FM' => { 'code' => 'USD', 'symbol' => '$' },
625             'FO' => { 'code' => 'DKK', 'symbol' => 'kr' },
626             'FR' => { 'code' => 'EUR', 'symbol' => '€' },
627             'GA' => { 'code' => 'XAF', 'symbol' => 'FCFA' },
628             'GB' => { 'code' => 'GBP', 'symbol' => '£' },
629             'GD' => { 'code' => 'XCD', 'symbol' => '$' },
630             'GE' => { 'code' => 'GEL', 'symbol' => 'ლ' },
631             'GF' => { 'code' => 'EUR', 'symbol' => '€' },
632             'GG' => { 'code' => 'GBP', 'symbol' => '£' },
633             'GH' => { 'code' => 'GHS', 'symbol' => 'GH₵' },
634             'GI' => { 'code' => 'GIP', 'symbol' => '£' },
635             'GL' => { 'code' => 'DKK', 'symbol' => 'kr' },
636             'GM' => { 'code' => 'GMD', 'symbol' => 'D' },
637             'GN' => { 'code' => 'GNF', 'symbol' => 'FG' },
638             'GP' => { 'code' => 'EUR', 'symbol' => '€' },
639             'GQ' => { 'code' => 'XAF', 'symbol' => 'FCFA' },
640             'GR' => { 'code' => 'EUR', 'symbol' => '€' },
641             'GS' => { 'code' => 'GBP', 'symbol' => '£' },
642             'GT' => { 'code' => 'GTQ', 'symbol' => 'Q' },
643             'GU' => { 'code' => 'USD', 'symbol' => '$' },
644             'GW' => { 'code' => 'XOF', 'symbol' => 'CFA' },
645             'GY' => { 'code' => 'GYD', 'symbol' => '$' },
646             'HK' => { 'code' => 'HKD', 'symbol' => '$' },
647             'HM' => { 'code' => 'AUD', 'symbol' => '$' },
648             'HN' => { 'code' => 'HNL', 'symbol' => 'L' },
649             'HR' => { 'code' => 'HRK', 'symbol' => 'kn' },
650             'HT' => { 'code' => 'HTG', 'symbol' => 'G' },
651             'HU' => { 'code' => 'HUF', 'symbol' => 'Ft' },
652             'ID' => { 'code' => 'IDR', 'symbol' => 'Rp' },
653             'IE' => { 'code' => 'EUR', 'symbol' => '€' },
654             'IL' => { 'code' => 'ILS', 'symbol' => '₪' },
655             'IM' => { 'code' => 'GBP', 'symbol' => '£' },
656             'IN' => { 'code' => 'INR', 'symbol' => '₹' },
657             'IO' => { 'code' => 'USD', 'symbol' => '$' },
658             'IQ' => { 'code' => 'IQD', 'symbol' => 'ع.د' },
659             'IR' => { 'code' => 'IRR', 'symbol' => '﷼' },
660             'IS' => { 'code' => 'ISK', 'symbol' => 'kr' },
661             'IT' => { 'code' => 'EUR', 'symbol' => '€' },
662             'JE' => { 'code' => 'GBP', 'symbol' => '£' },
663             'JM' => { 'code' => 'JMD', 'symbol' => 'J$' },
664             'JO' => { 'code' => 'JOD', 'symbol' => 'JD' },
665             'JP' => { 'code' => 'JPY', 'symbol' => '¥' },
666             'KE' => { 'code' => 'KES', 'symbol' => 'KSh' },
667             'KG' => { 'code' => 'KGS', 'symbol' => 'лв' },
668             'KH' => { 'code' => 'KHR', 'symbol' => '៛' },
669             'KI' => { 'code' => 'AUD', 'symbol' => '$' },
670             'KM' => { 'code' => 'KMF', 'symbol' => 'CF' },
671             'KN' => { 'code' => 'XCD', 'symbol' => '$' },
672             'KP' => { 'code' => 'KPW', 'symbol' => '₩' },
673             'KR' => { 'code' => 'KRW', 'symbol' => '₩' },
674             'KW' => { 'code' => 'KWD', 'symbol' => 'KD' },
675             'KY' => { 'code' => 'KYD', 'symbol' => '$' },
676             'KZ' => { 'code' => 'KZT', 'symbol' => '₸' },
677             'LA' => { 'code' => 'LAK', 'symbol' => '₭' },
678             'LB' => { 'code' => 'LBP', 'symbol' => '£' },
679             'LC' => { 'code' => 'XCD', 'symbol' => '$' },
680             'LI' => { 'code' => 'CHF', 'symbol' => 'CHF' },
681             'LK' => { 'code' => 'LKR', 'symbol' => '₨' },
682             'LR' => { 'code' => 'LRD', 'symbol' => '$' },
683             'LS' => { 'code' => 'LSL', 'symbol' => 'M' },
684             'LT' => { 'code' => 'LTL', 'symbol' => 'Lt' },
685             'LU' => { 'code' => 'EUR', 'symbol' => '€' },
686             'LV' => { 'code' => 'EUR', 'symbol' => '€' },
687             'LY' => { 'code' => 'LYD', 'symbol' => 'LD' },
688             'MA' => { 'code' => 'MAD', 'symbol' => 'MAD' },
689             'MC' => { 'code' => 'EUR', 'symbol' => '€' },
690             'MD' => { 'code' => 'MDL', 'symbol' => 'lei' },
691             'ME' => { 'code' => 'EUR', 'symbol' => '€' },
692             'MF' => { 'code' => 'EUR', 'symbol' => '€' },
693             'MG' => { 'code' => 'MGA', 'symbol' => 'Ar' },
694             'MH' => { 'code' => 'USD', 'symbol' => '$' },
695             'MK' => { 'code' => 'MKD', 'symbol' => 'ден' },
696             'ML' => { 'code' => 'XOF', 'symbol' => 'CFA' },
697             'MM' => { 'code' => 'MMK', 'symbol' => 'K' },
698             'MN' => { 'code' => 'MNT', 'symbol' => '₮' },
699             'MO' => { 'code' => 'MOP', 'symbol' => 'MOP$' },
700             'MP' => { 'code' => 'USD', 'symbol' => '$' },
701             'MQ' => { 'code' => 'EUR', 'symbol' => '€' },
702             'MR' => { 'code' => 'MRO', 'symbol' => 'UM' },
703             'MS' => { 'code' => 'XCD', 'symbol' => '$' },
704             'MT' => { 'code' => 'EUR', 'symbol' => '€' },
705             'MU' => { 'code' => 'MUR', 'symbol' => '₨' },
706             'MV' => { 'code' => 'MVR', 'symbol' => 'Rf' },
707             'MW' => { 'code' => 'MWK', 'symbol' => 'MK' },
708             'MX' => { 'code' => 'MXN', 'symbol' => '$' },
709             'MY' => { 'code' => 'MYR', 'symbol' => 'RM' },
710             'MZ' => { 'code' => 'MZN', 'symbol' => 'MT' },
711             'NA' => { 'code' => 'NAD', 'symbol' => '$' },
712             'NC' => { 'code' => 'XPF', 'symbol' => '₣' },
713             'NE' => { 'code' => 'XOF', 'symbol' => 'CFA' },
714             'NF' => { 'code' => 'AUD', 'symbol' => '$' },
715             'NG' => { 'code' => 'NGN', 'symbol' => '₦' },
716             'NI' => { 'code' => 'NIO', 'symbol' => 'C$' },
717             'NL' => { 'code' => 'EUR', 'symbol' => '€' },
718             'NO' => { 'code' => 'NOK', 'symbol' => 'kr' },
719             'NP' => { 'code' => 'NPR', 'symbol' => '₨' },
720             'NR' => { 'code' => 'AUD', 'symbol' => '$' },
721             'NU' => { 'code' => 'NZD', 'symbol' => '$' },
722             'NZ' => { 'code' => 'NZD', 'symbol' => '$' },
723             'OM' => { 'code' => 'OMR', 'symbol' => '﷼' },
724             'PA' => { 'code' => 'PAB', 'symbol' => 'B/.' },
725             'PE' => { 'code' => 'PEN', 'symbol' => 'S/.' },
726             'PF' => { 'code' => 'XPF', 'symbol' => '₣' },
727             'PG' => { 'code' => 'PGK', 'symbol' => 'K' },
728             'PH' => { 'code' => 'PHP', 'symbol' => '₱' },
729             'PK' => { 'code' => 'PKR', 'symbol' => '₨' },
730             'PL' => { 'code' => 'PLN', 'symbol' => 'zł' },
731             'PM' => { 'code' => 'EUR', 'symbol' => '€' },
732             'PN' => { 'code' => 'NZD', 'symbol' => '$' },
733             'PR' => { 'code' => 'USD', 'symbol' => '$' },
734             'PS' => { 'code' => 'ILS', 'symbol' => '₪' },
735             'PT' => { 'code' => 'EUR', 'symbol' => '€' },
736             'PW' => { 'code' => 'USD', 'symbol' => '$' },
737             'PY' => { 'code' => 'PYG', 'symbol' => 'Gs' },
738             'QA' => { 'code' => 'QAR', 'symbol' => '﷼' },
739             'RE' => { 'code' => 'EUR', 'symbol' => '€' },
740             'RO' => { 'code' => 'RON', 'symbol' => 'lei' },
741             'RS' => { 'code' => 'RSD', 'symbol' => 'Дин.' },
742             'RU' => { 'code' => 'RUB', 'symbol' => '₽' },
743             'RW' => { 'code' => 'RWF', 'symbol' => 'R₣' },
744             'SA' => { 'code' => 'SAR', 'symbol' => '﷼' },
745             'SB' => { 'code' => 'SBD', 'symbol' => '$' },
746             'SC' => { 'code' => 'SCR', 'symbol' => '₨' },
747             'SD' => { 'code' => 'SDG', 'symbol' => 'ج.س.' },
748             'SE' => { 'code' => 'SEK', 'symbol' => 'kr' },
749             'SG' => { 'code' => 'SGD', 'symbol' => 'S$' },
750             'SH' => { 'code' => 'SHP', 'symbol' => '£' },
751             'SI' => { 'code' => 'EUR', 'symbol' => '€' },
752             'SJ' => { 'code' => 'NOK', 'symbol' => 'kr' },
753             'SK' => { 'code' => 'EUR', 'symbol' => '€' },
754             'SL' => { 'code' => 'SLL', 'symbol' => 'Le' },
755             'SM' => { 'code' => 'EUR', 'symbol' => '€' },
756             'SN' => { 'code' => 'XOF', 'symbol' => 'CFA' },
757             'SO' => { 'code' => 'SOS', 'symbol' => 'S' },
758             'SR' => { 'code' => 'SRD', 'symbol' => '$' },
759             'SS' => { 'code' => 'SSP', 'symbol' => '£' },
760             'ST' => { 'code' => 'STD', 'symbol' => 'Db' },
761             'SV' => { 'code' => 'USD', 'symbol' => '$' },
762             'SX' => { 'code' => 'ANG', 'symbol' => 'ƒ' },
763             'SY' => { 'code' => 'SYP', 'symbol' => '£' },
764             'SZ' => { 'code' => 'SZL', 'symbol' => 'E' },
765             'TC' => { 'code' => 'USD', 'symbol' => '$' },
766             'TD' => { 'code' => 'XAF', 'symbol' => 'FCFA' },
767             'TF' => { 'code' => 'EUR', 'symbol' => '€' },
768             'TG' => { 'code' => 'XOF', 'symbol' => 'CFA' },
769             'TH' => { 'code' => 'THB', 'symbol' => '฿' },
770             'TJ' => { 'code' => 'TJS', 'symbol' => 'SM' },
771             'TK' => { 'code' => 'NZD', 'symbol' => '$' },
772             'TL' => { 'code' => 'USD', 'symbol' => '$' },
773             'TM' => { 'code' => 'TMT', 'symbol' => 'T' },
774             'TN' => { 'code' => 'TND', 'symbol' => 'د.ت' },
775             'TO' => { 'code' => 'TOP', 'symbol' => 'T$' },
776             'TR' => { 'code' => 'TRY', 'symbol' => '₺' },
777             'TT' => { 'code' => 'TTD', 'symbol' => 'TT$' },
778             'TV' => { 'code' => 'AUD', 'symbol' => '$' },
779             'TW' => { 'code' => 'TWD', 'symbol' => 'NT$' },
780             'TZ' => { 'code' => 'TZS', 'symbol' => 'TSh' },
781             'UA' => { 'code' => 'UAH', 'symbol' => '₴' },
782             'UG' => { 'code' => 'UGX', 'symbol' => 'USh' },
783             'UM' => { 'code' => 'USD', 'symbol' => '$' },
784             'US' => { 'code' => 'USD', 'symbol' => '$' },
785             'UY' => { 'code' => 'UYU', 'symbol' => '$U' },
786             'UZ' => { 'code' => 'UZS', 'symbol' => 'лв' },
787             'VA' => { 'code' => 'EUR', 'symbol' => '€' },
788             'VC' => { 'code' => 'XCD', 'symbol' => '$' },
789             'VE' => { 'code' => 'VEF', 'symbol' => 'Bs' },
790             'VG' => { 'code' => 'USD', 'symbol' => '$' },
791             'VI' => { 'code' => 'USD', 'symbol' => '$' },
792             'VN' => { 'code' => 'VND', 'symbol' => '₫' },
793             'VU' => { 'code' => 'VUV', 'symbol' => 'VT' },
794             'WF' => { 'code' => 'XPF', 'symbol' => '₣' },
795             'WS' => { 'code' => 'WST', 'symbol' => 'WS$' },
796             'XK' => { 'code' => 'EUR', 'symbol' => '€' },
797             'YE' => { 'code' => 'YER', 'symbol' => '﷼' },
798             'YT' => { 'code' => 'EUR', 'symbol' => '€' },
799             'ZA' => { 'code' => 'ZAR', 'symbol' => 'R' },
800             'ZM' => { 'code' => 'ZMK', 'symbol' => 'ZK' },
801             'ZW' => { 'code' => 'ZWL', 'symbol' => '$' }
802             );
803             my %default_continents = (
804             "BD" => { "code" => "AS", "name" => "Asia" },
805             "BE" => { "code" => "EU", "name" => "Europe" },
806             "BF" => { "code" => "AF", "name" => "Africa" },
807             "BG" => { "code" => "EU", "name" => "Europe" },
808             "BA" => { "code" => "EU", "name" => "Europe" },
809             "BB" => { "code" => "NA", "name" => "North America" },
810             "WF" => { "code" => "OC", "name" => "Oceania" },
811             "BL" => { "code" => "NA", "name" => "North America" },
812             "BM" => { "code" => "NA", "name" => "North America" },
813             "BN" => { "code" => "AS", "name" => "Asia" },
814             "BO" => { "code" => "SA", "name" => "South America" },
815             "BH" => { "code" => "AS", "name" => "Asia" },
816             "BI" => { "code" => "AF", "name" => "Africa" },
817             "BJ" => { "code" => "AF", "name" => "Africa" },
818             "BT" => { "code" => "AS", "name" => "Asia" },
819             "JM" => { "code" => "NA", "name" => "North America" },
820             "BV" => { "code" => "AN", "name" => "Antarctica" },
821             "BW" => { "code" => "AF", "name" => "Africa" },
822             "WS" => { "code" => "OC", "name" => "Oceania" },
823             "BQ" => { "code" => "NA", "name" => "North America" },
824             "BR" => { "code" => "SA", "name" => "South America" },
825             "BS" => { "code" => "NA", "name" => "North America" },
826             "JE" => { "code" => "EU", "name" => "Europe" },
827             "BY" => { "code" => "EU", "name" => "Europe" },
828             "BZ" => { "code" => "NA", "name" => "North America" },
829             "RU" => { "code" => "EU", "name" => "Europe" },
830             "RW" => { "code" => "AF", "name" => "Africa" },
831             "RS" => { "code" => "EU", "name" => "Europe" },
832             "TL" => { "code" => "OC", "name" => "Oceania" },
833             "RE" => { "code" => "AF", "name" => "Africa" },
834             "TM" => { "code" => "AS", "name" => "Asia" },
835             "TJ" => { "code" => "AS", "name" => "Asia" },
836             "RO" => { "code" => "EU", "name" => "Europe" },
837             "TK" => { "code" => "OC", "name" => "Oceania" },
838             "GW" => { "code" => "AF", "name" => "Africa" },
839             "GU" => { "code" => "OC", "name" => "Oceania" },
840             "GT" => { "code" => "NA", "name" => "North America" },
841             "GS" => { "code" => "AN", "name" => "Antarctica" },
842             "GR" => { "code" => "EU", "name" => "Europe" },
843             "GQ" => { "code" => "AF", "name" => "Africa" },
844             "GP" => { "code" => "NA", "name" => "North America" },
845             "JP" => { "code" => "AS", "name" => "Asia" },
846             "GY" => { "code" => "SA", "name" => "South America" },
847             "GG" => { "code" => "EU", "name" => "Europe" },
848             "GF" => { "code" => "SA", "name" => "South America" },
849             "GE" => { "code" => "AS", "name" => "Asia" },
850             "GD" => { "code" => "NA", "name" => "North America" },
851             "GB" => { "code" => "EU", "name" => "Europe" },
852             "GA" => { "code" => "AF", "name" => "Africa" },
853             "SV" => { "code" => "NA", "name" => "North America" },
854             "GN" => { "code" => "AF", "name" => "Africa" },
855             "GM" => { "code" => "AF", "name" => "Africa" },
856             "GL" => { "code" => "NA", "name" => "North America" },
857             "GI" => { "code" => "EU", "name" => "Europe" },
858             "GH" => { "code" => "AF", "name" => "Africa" },
859             "OM" => { "code" => "AS", "name" => "Asia" },
860             "TN" => { "code" => "AF", "name" => "Africa" },
861             "JO" => { "code" => "AS", "name" => "Asia" },
862             "HR" => { "code" => "EU", "name" => "Europe" },
863             "HT" => { "code" => "NA", "name" => "North America" },
864             "HU" => { "code" => "EU", "name" => "Europe" },
865             "HK" => { "code" => "AS", "name" => "Asia" },
866             "HN" => { "code" => "NA", "name" => "North America" },
867             "HM" => { "code" => "AN", "name" => "Antarctica" },
868             "VE" => { "code" => "SA", "name" => "South America" },
869             "PR" => { "code" => "NA", "name" => "North America" },
870             "PS" => { "code" => "AS", "name" => "Asia" },
871             "PW" => { "code" => "OC", "name" => "Oceania" },
872             "PT" => { "code" => "EU", "name" => "Europe" },
873             "SJ" => { "code" => "EU", "name" => "Europe" },
874             "PY" => { "code" => "SA", "name" => "South America" },
875             "IQ" => { "code" => "AS", "name" => "Asia" },
876             "PA" => { "code" => "NA", "name" => "North America" },
877             "PF" => { "code" => "OC", "name" => "Oceania" },
878             "PG" => { "code" => "OC", "name" => "Oceania" },
879             "PE" => { "code" => "SA", "name" => "South America" },
880             "PK" => { "code" => "AS", "name" => "Asia" },
881             "PH" => { "code" => "AS", "name" => "Asia" },
882             "PN" => { "code" => "OC", "name" => "Oceania" },
883             "PL" => { "code" => "EU", "name" => "Europe" },
884             "PM" => { "code" => "NA", "name" => "North America" },
885             "ZM" => { "code" => "AF", "name" => "Africa" },
886             "EH" => { "code" => "AF", "name" => "Africa" },
887             "EE" => { "code" => "EU", "name" => "Europe" },
888             "EG" => { "code" => "AF", "name" => "Africa" },
889             "ZA" => { "code" => "AF", "name" => "Africa" },
890             "EC" => { "code" => "SA", "name" => "South America" },
891             "IT" => { "code" => "EU", "name" => "Europe" },
892             "VN" => { "code" => "AS", "name" => "Asia" },
893             "SB" => { "code" => "OC", "name" => "Oceania" },
894             "ET" => { "code" => "AF", "name" => "Africa" },
895             "SO" => { "code" => "AF", "name" => "Africa" },
896             "ZW" => { "code" => "AF", "name" => "Africa" },
897             "SA" => { "code" => "AS", "name" => "Asia" },
898             "ES" => { "code" => "EU", "name" => "Europe" },
899             "ER" => { "code" => "AF", "name" => "Africa" },
900             "ME" => { "code" => "EU", "name" => "Europe" },
901             "MD" => { "code" => "EU", "name" => "Europe" },
902             "MG" => { "code" => "AF", "name" => "Africa" },
903             "MF" => { "code" => "NA", "name" => "North America" },
904             "MA" => { "code" => "AF", "name" => "Africa" },
905             "MC" => { "code" => "EU", "name" => "Europe" },
906             "UZ" => { "code" => "AS", "name" => "Asia" },
907             "MM" => { "code" => "AS", "name" => "Asia" },
908             "ML" => { "code" => "AF", "name" => "Africa" },
909             "MO" => { "code" => "AS", "name" => "Asia" },
910             "MN" => { "code" => "AS", "name" => "Asia" },
911             "MH" => { "code" => "OC", "name" => "Oceania" },
912             "MK" => { "code" => "EU", "name" => "Europe" },
913             "MU" => { "code" => "AF", "name" => "Africa" },
914             "MT" => { "code" => "EU", "name" => "Europe" },
915             "MW" => { "code" => "AF", "name" => "Africa" },
916             "MV" => { "code" => "AS", "name" => "Asia" },
917             "MQ" => { "code" => "NA", "name" => "North America" },
918             "MP" => { "code" => "OC", "name" => "Oceania" },
919             "MS" => { "code" => "NA", "name" => "North America" },
920             "MR" => { "code" => "AF", "name" => "Africa" },
921             "IM" => { "code" => "EU", "name" => "Europe" },
922             "UG" => { "code" => "AF", "name" => "Africa" },
923             "TZ" => { "code" => "AF", "name" => "Africa" },
924             "MY" => { "code" => "AS", "name" => "Asia" },
925             "MX" => { "code" => "NA", "name" => "North America" },
926             "IL" => { "code" => "AS", "name" => "Asia" },
927             "FR" => { "code" => "EU", "name" => "Europe" },
928             "IO" => { "code" => "AS", "name" => "Asia" },
929             "SH" => { "code" => "AF", "name" => "Africa" },
930             "FI" => { "code" => "EU", "name" => "Europe" },
931             "FJ" => { "code" => "OC", "name" => "Oceania" },
932             "FK" => { "code" => "SA", "name" => "South America" },
933             "FM" => { "code" => "OC", "name" => "Oceania" },
934             "FO" => { "code" => "EU", "name" => "Europe" },
935             "NI" => { "code" => "NA", "name" => "North America" },
936             "NL" => { "code" => "EU", "name" => "Europe" },
937             "NO" => { "code" => "EU", "name" => "Europe" },
938             "NA" => { "code" => "AF", "name" => "Africa" },
939             "VU" => { "code" => "OC", "name" => "Oceania" },
940             "NC" => { "code" => "OC", "name" => "Oceania" },
941             "NE" => { "code" => "AF", "name" => "Africa" },
942             "NF" => { "code" => "OC", "name" => "Oceania" },
943             "NG" => { "code" => "AF", "name" => "Africa" },
944             "NZ" => { "code" => "OC", "name" => "Oceania" },
945             "NP" => { "code" => "AS", "name" => "Asia" },
946             "NR" => { "code" => "OC", "name" => "Oceania" },
947             "NU" => { "code" => "OC", "name" => "Oceania" },
948             "CK" => { "code" => "OC", "name" => "Oceania" },
949             "XK" => { "code" => "EU", "name" => "Europe" },
950             "CI" => { "code" => "AF", "name" => "Africa" },
951             "CH" => { "code" => "EU", "name" => "Europe" },
952             "CO" => { "code" => "SA", "name" => "South America" },
953             "CN" => { "code" => "AS", "name" => "Asia" },
954             "CM" => { "code" => "AF", "name" => "Africa" },
955             "CL" => { "code" => "SA", "name" => "South America" },
956             "CC" => { "code" => "AS", "name" => "Asia" },
957             "CA" => { "code" => "NA", "name" => "North America" },
958             "CG" => { "code" => "AF", "name" => "Africa" },
959             "CF" => { "code" => "AF", "name" => "Africa" },
960             "CD" => { "code" => "AF", "name" => "Africa" },
961             "CZ" => { "code" => "EU", "name" => "Europe" },
962             "CY" => { "code" => "EU", "name" => "Europe" },
963             "CX" => { "code" => "AS", "name" => "Asia" },
964             "CR" => { "code" => "NA", "name" => "North America" },
965             "CW" => { "code" => "NA", "name" => "North America" },
966             "CV" => { "code" => "AF", "name" => "Africa" },
967             "CU" => { "code" => "NA", "name" => "North America" },
968             "SZ" => { "code" => "AF", "name" => "Africa" },
969             "SY" => { "code" => "AS", "name" => "Asia" },
970             "SX" => { "code" => "NA", "name" => "North America" },
971             "KG" => { "code" => "AS", "name" => "Asia" },
972             "KE" => { "code" => "AF", "name" => "Africa" },
973             "SS" => { "code" => "AF", "name" => "Africa" },
974             "SR" => { "code" => "SA", "name" => "South America" },
975             "KI" => { "code" => "OC", "name" => "Oceania" },
976             "KH" => { "code" => "AS", "name" => "Asia" },
977             "KN" => { "code" => "NA", "name" => "North America" },
978             "KM" => { "code" => "AF", "name" => "Africa" },
979             "ST" => { "code" => "AF", "name" => "Africa" },
980             "SK" => { "code" => "EU", "name" => "Europe" },
981             "KR" => { "code" => "AS", "name" => "Asia" },
982             "SI" => { "code" => "EU", "name" => "Europe" },
983             "KP" => { "code" => "AS", "name" => "Asia" },
984             "KW" => { "code" => "AS", "name" => "Asia" },
985             "SN" => { "code" => "AF", "name" => "Africa" },
986             "SM" => { "code" => "EU", "name" => "Europe" },
987             "SL" => { "code" => "AF", "name" => "Africa" },
988             "SC" => { "code" => "AF", "name" => "Africa" },
989             "KZ" => { "code" => "AS", "name" => "Asia" },
990             "KY" => { "code" => "NA", "name" => "North America" },
991             "SG" => { "code" => "AS", "name" => "Asia" },
992             "SE" => { "code" => "EU", "name" => "Europe" },
993             "SD" => { "code" => "AF", "name" => "Africa" },
994             "DO" => { "code" => "NA", "name" => "North America" },
995             "DM" => { "code" => "NA", "name" => "North America" },
996             "DJ" => { "code" => "AF", "name" => "Africa" },
997             "DK" => { "code" => "EU", "name" => "Europe" },
998             "VG" => { "code" => "NA", "name" => "North America" },
999             "DE" => { "code" => "EU", "name" => "Europe" },
1000             "YE" => { "code" => "AS", "name" => "Asia" },
1001             "DZ" => { "code" => "AF", "name" => "Africa" },
1002             "US" => { "code" => "NA", "name" => "North America" },
1003             "UY" => { "code" => "SA", "name" => "South America" },
1004             "YT" => { "code" => "AF", "name" => "Africa" },
1005             "UM" => { "code" => "OC", "name" => "Oceania" },
1006             "LB" => { "code" => "AS", "name" => "Asia" },
1007             "LC" => { "code" => "NA", "name" => "North America" },
1008             "LA" => { "code" => "AS", "name" => "Asia" },
1009             "TV" => { "code" => "OC", "name" => "Oceania" },
1010             "TW" => { "code" => "AS", "name" => "Asia" },
1011             "TT" => { "code" => "NA", "name" => "North America" },
1012             "TR" => { "code" => "AS", "name" => "Asia" },
1013             "LK" => { "code" => "AS", "name" => "Asia" },
1014             "LI" => { "code" => "EU", "name" => "Europe" },
1015             "LV" => { "code" => "EU", "name" => "Europe" },
1016             "TO" => { "code" => "OC", "name" => "Oceania" },
1017             "LT" => { "code" => "EU", "name" => "Europe" },
1018             "LU" => { "code" => "EU", "name" => "Europe" },
1019             "LR" => { "code" => "AF", "name" => "Africa" },
1020             "LS" => { "code" => "AF", "name" => "Africa" },
1021             "TH" => { "code" => "AS", "name" => "Asia" },
1022             "TF" => { "code" => "AN", "name" => "Antarctica" },
1023             "TG" => { "code" => "AF", "name" => "Africa" },
1024             "TD" => { "code" => "AF", "name" => "Africa" },
1025             "TC" => { "code" => "NA", "name" => "North America" },
1026             "LY" => { "code" => "AF", "name" => "Africa" },
1027             "VA" => { "code" => "EU", "name" => "Europe" },
1028             "VC" => { "code" => "NA", "name" => "North America" },
1029             "AE" => { "code" => "AS", "name" => "Asia" },
1030             "AD" => { "code" => "EU", "name" => "Europe" },
1031             "AG" => { "code" => "NA", "name" => "North America" },
1032             "AF" => { "code" => "AS", "name" => "Asia" },
1033             "AI" => { "code" => "NA", "name" => "North America" },
1034             "VI" => { "code" => "NA", "name" => "North America" },
1035             "IS" => { "code" => "EU", "name" => "Europe" },
1036             "IR" => { "code" => "AS", "name" => "Asia" },
1037             "AM" => { "code" => "AS", "name" => "Asia" },
1038             "AL" => { "code" => "EU", "name" => "Europe" },
1039             "AO" => { "code" => "AF", "name" => "Africa" },
1040             "AQ" => { "code" => "AN", "name" => "Antarctica" },
1041             "AS" => { "code" => "OC", "name" => "Oceania" },
1042             "AR" => { "code" => "SA", "name" => "South America" },
1043             "AU" => { "code" => "OC", "name" => "Oceania" },
1044             "AT" => { "code" => "EU", "name" => "Europe" },
1045             "AW" => { "code" => "NA", "name" => "North America" },
1046             "IN" => { "code" => "AS", "name" => "Asia" },
1047             "AX" => { "code" => "EU", "name" => "Europe" },
1048             "AZ" => { "code" => "AS", "name" => "Asia" },
1049             "IE" => { "code" => "EU", "name" => "Europe" },
1050             "ID" => { "code" => "AS", "name" => "Asia" },
1051             "UA" => { "code" => "EU", "name" => "Europe" },
1052             "QA" => { "code" => "AS", "name" => "Asia" },
1053             "MZ" => { "code" => "AF", "name" => "Africa" }
1054             );
1055              
1056             #-------------------------------------------------------------------------------
1057              
1058             sub new {
1059 1     1 1 926 my ( $pkg, $token, %options ) = @_;
1060              
1061 1         1 my $self = {};
1062 1 50       4 $token = defined $token ? $token : '';
1063              
1064 1         2 $self->{base_url} = $base_url;
1065 1         2 $self->{base_url_ipv6} = $base_url_ipv6;
1066 1         8 $self->{ua} = LWP::UserAgent->new;
1067 1         2425 $self->{ua}->ssl_opts( 'verify_hostname' => 0 );
1068             $self->{ua}->default_headers(
1069 1         27 HTTP::Headers->new(
1070             Accept => 'application/json',
1071             Authorization => 'Bearer ' . $token
1072             )
1073             );
1074 1         78 $self->{ua}->agent("IPinfoClient/Perl/$VERSION");
1075              
1076             my $timeout =
1077 1 50       39 defined $options{timeout} ? $options{timeout} : DEFAULT_TIMEOUT;
1078 1         5 $self->{ua}->timeout($timeout);
1079              
1080 1         14 $self->{message} = '';
1081              
1082 1         3 bless $self, $pkg;
1083              
1084             my $countries =
1085 1 50       4 exists $options{countries} ? $options{countries} : \%default_countries;
1086             my $eu_countries =
1087             exists $options{eu_countries}
1088             ? $options{eu_countries}
1089 1 50       2 : \@default_eu_countries;
1090             my $countries_flags =
1091             exists $options{countries_flags}
1092             ? $options{countries_flags}
1093 1 50       2 : \%default_countries_flags;
1094             my $countries_currencies =
1095             exists $options{countries_currencies}
1096             ? $options{countries_currencies}
1097 1 50       3 : \%default_countries_currencies;
1098             my $continents =
1099             exists $options{continents}
1100             ? $options{continents}
1101 1 50       2 : \%default_continents;
1102              
1103 1         3 $self->{countries} = $countries;
1104 1         2 $self->{eu_countries} = $eu_countries;
1105 1         7 $self->{countries_flags} = $countries_flags;
1106 1         1 $self->{countries_currencies} = $countries_currencies;
1107 1         2 $self->{continents} = $continents;
1108 1         2 $self->{cache} = $self->_build_cache(%options);
1109              
1110 1         11 return $self;
1111             }
1112              
1113             #-------------------------------------------------------------------------------
1114              
1115             sub info {
1116 2     2 1 1365 my ( $self, $ip ) = @_;
1117              
1118 2         10 return $self->_get_info( $ip, '', 0 );
1119             }
1120              
1121             #-------------------------------------------------------------------------------
1122              
1123             sub info_v6 {
1124 0     0 1 0 my ( $self, $ip ) = @_;
1125              
1126 0         0 return $self->_get_info( $ip, '', 1 );
1127             }
1128              
1129             #-------------------------------------------------------------------------------
1130              
1131             sub geo {
1132 0     0 1 0 my ( $self, $ip ) = @_;
1133              
1134 0         0 return $self->_get_info( $ip, 'geo', 0 );
1135             }
1136              
1137             #-------------------------------------------------------------------------------
1138              
1139             sub resproxy {
1140 0     0 0 0 my ( $self, $ip ) = @_;
1141              
1142 0 0       0 $ip = defined $ip ? $ip : '';
1143              
1144 0 0       0 if ( $ip eq '' ) {
1145 0         0 $self->{message} = 'IP address is required for resproxy lookup';
1146 0         0 return undef;
1147             }
1148              
1149 0         0 my $validated_ip = Net::CIDR::cidrvalidate($ip);
1150 0 0       0 if ( !defined $validated_ip ) {
1151 0         0 $self->{message} = 'Invalid IP address';
1152 0         0 return undef;
1153             }
1154              
1155 0         0 my $cache_key = 'resproxy/' . $ip;
1156 0         0 my $cached_info = $self->_lookup_info_from_cache($cache_key);
1157              
1158 0 0       0 if ( defined $cached_info ) {
1159 0         0 $self->{message} = '';
1160 0         0 return $cached_info;
1161             }
1162              
1163 0         0 my $url = $self->{base_url} . 'resproxy/' . $ip;
1164 0         0 my $response = $self->{ua}->get($url);
1165              
1166 0 0       0 if ( $response->is_success ) {
1167 0   0     0 my $content_type = $response->header('Content-Type') || '';
1168 0         0 my $info;
1169              
1170 0 0       0 if ( $content_type =~ m{application/json}i ) {
1171 0         0 eval { $info = from_json( $response->decoded_content ); };
  0         0  
1172 0 0       0 if ($@) {
1173 0         0 $self->{message} = 'Error parsing JSON response.';
1174 0         0 return undef;
1175             }
1176             }
1177             else {
1178 0         0 $info = $response->decoded_content;
1179 0         0 chomp($info);
1180             }
1181              
1182 0         0 $info->{meta}->{time} = time();
1183 0         0 $self->{cache}->set( $cache_key, $info );
1184 0         0 $self->{message} = '';
1185 0         0 return $info;
1186             }
1187              
1188 0 0       0 if ( $response->code == HTTP_TOO_MANY_REQUEST ) {
1189 0         0 $self->{message} = 'Your monthly request quota has been exceeded.';
1190 0         0 return undef;
1191             }
1192              
1193 0         0 $self->{message} = $response->status_line;
1194 0         0 return undef;
1195             }
1196              
1197             #-------------------------------------------------------------------------------
1198              
1199             sub field {
1200 0     0 1 0 my ( $self, $ip, $field ) = @_;
1201              
1202 0 0       0 if ( not defined $field ) {
1203 0         0 $self->{message} = 'Field must be defined.';
1204 0         0 return;
1205             }
1206              
1207 0 0       0 if ( not defined $valid_fields{$field} ) {
1208 0         0 $self->{message} = "Invalid field: $field";
1209 0         0 return;
1210             }
1211              
1212 0         0 return $self->_get_info( $ip, $field, 0 );
1213             }
1214              
1215             #-------------------------------------------------------------------------------
1216              
1217             sub error_msg {
1218 0     0 1 0 my $self = shift;
1219              
1220 0         0 return $self->{message};
1221             }
1222              
1223             #-------------------------------------------------------------------------------
1224             #-- private method(s) below, don't call them directly -------------------------
1225              
1226             sub _get_info {
1227 2     2   5 my ( $self, $ip, $field, $ipv6_lookup ) = @_;
1228              
1229 2 50       5 $ip = defined $ip ? $ip : '';
1230 2 50       5 $field = defined $field ? $field : '';
1231              
1232 2 50       7 if ( $ip ne '' ) {
1233 2         12 my $validated_ip = Net::CIDR::cidrvalidate($ip);
1234 2 50       1864 if ( !defined $validated_ip ) {
1235 0         0 $self->{message} = 'Invalid IP address';
1236 0         0 return undef;
1237             }
1238             }
1239              
1240 2         7 my ( $info, $message ) = $self->_lookup_info( $ip, $field, $ipv6_lookup );
1241 2         8 $self->{message} = $message;
1242 2 100       3 return $info if eval { $info->isa('Geo::Details') };
  2         21  
1243              
1244 1 50 33     5 if ( $field ne '' && ref($info) eq 'HASH' ) {
1245 0 0       0 if ( exists $info->{'bogon'} ) {
1246 0         0 $self->{message} = 'Field info not available for bogon IPs';
1247 0         0 return undef;
1248             }
1249             }
1250              
1251 1 50       11 return defined $info ? Geo::Details->new( $info, $field ) : undef;
1252             }
1253              
1254             sub _lookup_info {
1255 2     2   5 my ( $self, $ip, $field, $ipv6_lookup ) = @_;
1256              
1257             # checking bogon IP and returning response locally.
1258 2 50       7 if ( $ip ne '' ) {
1259 2 50       6 if ( _is_bogon($ip) ) {
1260 0         0 my $details = {};
1261 0         0 $details->{ip} = $ip;
1262 0         0 $details->{bogon} = "True";
1263 0         0 return ( $details, '' );
1264             }
1265             }
1266              
1267 2         6 my $key = $ip . '/' . $field;
1268 2         6 my $cached_info = $self->_lookup_info_from_cache($key);
1269              
1270 2 100       5 if ( defined $cached_info ) {
1271 1         6 return ( $cached_info, '' );
1272             }
1273              
1274 1         2 my ( $source_info, $message ) = $self->_lookup_info_from_source($ipv6_lookup, $key);
1275 1 50       6 if ( not defined $source_info ) {
1276 0         0 return ( $source_info, $message );
1277             }
1278              
1279 1 50       6 if ( ref($source_info) eq '' ) {
1280 0         0 return ( $source_info, $message );
1281             }
1282              
1283 1         4 my $country = $source_info->{country};
1284 1 50       4 if ( defined $country ) {
1285 1         6 $source_info->{country_name} = $self->{countries}->{$country};
1286 1         4 $source_info->{country_flag} = $self->{countries_flags}->{$country};
1287             $source_info->{country_flag_url} =
1288 1         3 $country_flag_url . $country . ".svg";
1289             $source_info->{country_currency} =
1290 1         5 $self->{countries_currencies}->{$country};
1291 1         4 $source_info->{continent} = $self->{continents}->{$country};
1292 1 50       2 if ( grep { $_ eq $country } @{ $self->{eu_countries} } ) {
  27         54  
  1         5  
1293 0         0 $source_info->{is_eu} = "True";
1294             }
1295             else {
1296 1         4 $source_info->{is_eu} = undef;
1297             }
1298             }
1299              
1300 1 50       4 if ( defined $source_info->{'loc'} ) {
1301 1         6 my ( $lat, $lon ) = split /,/, $source_info->{loc};
1302 1         4 $source_info->{latitude} = $lat;
1303 1         3 $source_info->{longitude} = $lon;
1304             }
1305              
1306 1         6 $source_info->{meta} = { time => time(), from_cache => 0 };
1307 1         8 $self->{cache}->set( $key, $source_info );
1308              
1309 1         35 return ( $source_info, $message );
1310             }
1311              
1312             sub _lookup_info_from_cache {
1313 2     2   5 my ( $self, $cache_key ) = @_;
1314              
1315 2         11 my $cached_info = $self->{cache}->get($cache_key);
1316 2 100       33 if ( defined $cached_info ) {
1317 1         5 my $timedelta = time() - $cached_info->{meta}->{time};
1318 1 50 33     9 if ( $timedelta <= $cache_ttl || $custom_cache == 1 ) {
1319 1         31 $cached_info->{meta}->{from_cache} = 1;
1320              
1321 1         4 return $cached_info;
1322             }
1323             }
1324              
1325 1         2 return;
1326             }
1327              
1328             sub _lookup_info_from_source {
1329 1     1   2 my ( $self, $is_ipv6, $key ) = @_;
1330              
1331 1         1 my $url = '';
1332 1 50       3 if ( $is_ipv6 ) {
1333 0         0 $url = $self->{base_url_ipv6} . $key;
1334             } else {
1335 1         3 $url = $self->{base_url} . $key;
1336             }
1337              
1338 1         4 my $response = $self->{ua}->get($url);
1339              
1340 1 50       466484 if ( $response->is_success ) {
1341              
1342 1   50     19 my $content_type = $response->header('Content-Type') || '';
1343 1         58 my $info;
1344              
1345 1 50       10 if ( $content_type =~ m{application/json}i ) {
1346 1         3 eval { $info = from_json( $response->decoded_content ); };
  1         9  
1347 1 50       412 if ($@) {
1348 0         0 return ( undef, 'Error parsing JSON response.' );
1349             }
1350             }
1351             else {
1352 0         0 $info = $response->decoded_content;
1353 0         0 chomp($info);
1354             }
1355              
1356 1         40 return ( $info, '' );
1357             }
1358 0 0       0 if ( $response->code == HTTP_TOO_MANY_REQUEST ) {
1359 0         0 return ( undef, 'Your monthly request quota has been exceeded.' );
1360             }
1361              
1362 0         0 return ( undef, $response->status_line );
1363             }
1364              
1365             sub _build_cache {
1366 1     1   2 my ( $pkg, %options ) = @_;
1367              
1368 1 50       3 if ( defined $options{cache} ) {
1369 0         0 $custom_cache = 1;
1370              
1371 0         0 return $options{cache};
1372             }
1373              
1374 1         1 $cache_ttl = DEFAULT_CACHE_TTL;
1375 1 50       2 if ( defined $options{cache_ttl} ) {
1376 0         0 $cache_ttl = $options{cache_ttl};
1377             }
1378              
1379             return Cache::LRU->new(
1380             size => defined $options{cache_max_size}
1381             ? $options{cache_max_size}
1382 1 50       6 : DEFAULT_CACHE_MAX_SIZE
1383             );
1384             }
1385              
1386             # Lists of bogon CIDRs.
1387             my @ip4_bogon_networks = (
1388             "0.0.0.0/8", "10.0.0.0/8",
1389             "100.64.0.0/10", "127.0.0.0/8",
1390             "169.254.0.0/16", "172.16.0.0/12",
1391             "192.0.0.0/24", "192.0.2.0/24",
1392             "192.168.0.0/16", "198.18.0.0/15",
1393             "198.51.100.0/24", "203.0.113.0/24",
1394             "224.0.0.0/4", "240.0.0.0/4",
1395             "255.255.255.255/32"
1396             );
1397             my @ip6_bogon_networks = (
1398             "0:0:0:0:0:0:0:0/128", "0:0:0:0:0:0:0:1/128",
1399             "0:0:0:0:0:ffff:0:0/96", "0:0:0:0:0:0:0:0/96",
1400             "100::/64", "2001:10::/28",
1401             "2001:db8::/32", "fc00::/7",
1402             "fe80::/10", "fec0::/10",
1403             "ff00::/8", "2002::/24",
1404             "2002:a00::/24", "2002:7f00::/24",
1405             "2002:a9fe::/32", "2002:ac10::/28",
1406             "2002:c000::/40", "2002:c000:200::/40",
1407             "2002:c0a8::/32", "2002:c612::/31",
1408             "2002:c633:6400::/40", "2002:cb00:7100::/40",
1409             "2002:e000::/20", "2002:f000::/20",
1410             "2002:ffff:ffff::/48", "2001::/40",
1411             "2001:0:a00::/40", "2001:0:7f00::/40",
1412             "2001:0:a9fe::/48", "2001:0:ac10::/44",
1413             "2001:0:c000::/56", "2001:0:c000:200::/56",
1414             "2001:0:c0a8::/48", "2001:0:c612::/47",
1415             "2001:0:c633:6400::/56", "2001:0:cb00:7100::/56",
1416             "2001:0:e000::/36", "2001:0:f000::/36",
1417             "2001:0:ffff:ffff::/64"
1418             );
1419              
1420             # Check if an IP address is a bogon.
1421             sub _is_bogon {
1422 2     2   4 my $ip = shift;
1423              
1424 2         3 my $ip_is_bogon = 0;
1425              
1426 2 50       9 if ( $ip =~ /:/ ) { # IPv6 address
1427 0         0 my $ip6_bogon_cidr_set = Net::CIDR::Set->new();
1428 0         0 $ip6_bogon_cidr_set->add($_) foreach (@ip6_bogon_networks);
1429 0         0 $ip_is_bogon = $ip6_bogon_cidr_set->contains($ip);
1430             }
1431             else { # IPv4 address
1432 2         19 my $ip4_bogon_cidr_set = Net::CIDR::Set->new();
1433 2         49 $ip4_bogon_cidr_set->add($_) foreach (@ip4_bogon_networks);
1434 2         3560 $ip_is_bogon = $ip4_bogon_cidr_set->contains($ip);
1435             }
1436              
1437 2         3736 return $ip_is_bogon;
1438             }
1439              
1440             #-------------------------------------------------------------------------------
1441              
1442             1;
1443             __END__