3 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 2 -*- */
6 * Copyright (C) 2007 MaxMind LLC
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 define("GEOIP_COUNTRY_BEGIN", 16776960);
24 define("GEOIP_STATE_BEGIN_REV0", 16700000);
25 define("GEOIP_STATE_BEGIN_REV1", 16000000);
26 define("GEOIP_STANDARD", 0);
27 define("GEOIP_MEMORY_CACHE", 1);
28 define("GEOIP_SHARED_MEMORY", 2);
29 define("STRUCTURE_INFO_MAX_SIZE", 20);
30 define("DATABASE_INFO_MAX_SIZE", 100);
31 define("GEOIP_COUNTRY_EDITION", 1);
32 define("GEOIP_PROXY_EDITION", 8);
33 define("GEOIP_ASNUM_EDITION", 9);
34 define("GEOIP_NETSPEED_EDITION", 10);
35 define("GEOIP_REGION_EDITION_REV0", 7);
36 define("GEOIP_REGION_EDITION_REV1", 3);
37 define("GEOIP_CITY_EDITION_REV0", 6);
38 define("GEOIP_CITY_EDITION_REV1", 2);
39 define("GEOIP_ORG_EDITION", 5);
40 define("GEOIP_ISP_EDITION", 4);
41 define("SEGMENT_RECORD_LENGTH", 3);
42 define("STANDARD_RECORD_LENGTH", 3);
43 define("ORG_RECORD_LENGTH", 4);
44 define("MAX_RECORD_LENGTH", 4);
45 define("MAX_ORG_RECORD_LENGTH", 300);
46 define("GEOIP_SHM_KEY", 0x4f415401);
47 define("US_OFFSET", 1);
48 define("CANADA_OFFSET", 677);
49 define("WORLD_OFFSET", 1353);
50 define("FIPS_RANGE", 360);
51 define("GEOIP_UNKNOWN_SPEED", 0);
52 define("GEOIP_DIALUP_SPEED", 1);
53 define("GEOIP_CABLEDSL_SPEED", 2);
54 define("GEOIP_CORPORATE_SPEED", 3);
55 define("GEOIP_DOMAIN_EDITION", 11);
56 define("GEOIP_COUNTRY_EDITION_V6", 12);
57 define("GEOIP_LOCATIONA_EDITION", 13);
58 define("GEOIP_ACCURACYRADIUS_EDITION", 14);
59 define("GEOIP_CITYCOMBINED_EDITION", 15);
60 define("GEOIP_CITY_EDITION_REV1_V6", 30);
61 define("GEOIP_CITY_EDITION_REV0_V6", 31);
62 define("GEOIP_NETSPEED_EDITION_REV1", 32);
63 define("GEOIP_NETSPEED_EDITION_REV1_V6", 33);
64 define("GEOIP_USERTYPE_EDITION", 28);
65 define("GEOIP_USERTYPE_EDITION_V6", 29);
66 define("GEOIP_ASNUM_EDITION_V6", 21);
67 define("GEOIP_ISP_EDITION_V6", 22);
68 define("GEOIP_ORG_EDITION_V6", 23);
69 define("GEOIP_DOMAIN_EDITION_V6", 24);
71 define("CITYCOMBINED_FIXED_RECORD", 7);
77 public $memory_buffer;
79 public $databaseSegments;
80 public $record_length;
82 public $GEOIP_COUNTRY_CODE_TO_NUMBER = array(
340 public $GEOIP_COUNTRY_CODES = array(
599 public $GEOIP_COUNTRY_CODES3 = array(
858 public $GEOIP_COUNTRY_NAMES = array(
860 "Asia/Pacific Region",
863 "United Arab Emirates",
865 "Antigua and Barbuda",
878 "Bosnia and Herzegovina",
898 "Cocos (Keeling) Islands",
899 "Congo, The Democratic Republic of the",
900 "Central African Republic",
919 "Dominican Republic",
930 "Falkland Islands (Malvinas)",
931 "Micronesia, Federated States of",
934 "Sint Maarten (Dutch part)",
948 "South Georgia and the South Sandwich Islands",
954 "Heard Island and McDonald Islands",
963 "British Indian Ocean Territory",
965 "Iran, Islamic Republic of",
976 "Saint Kitts and Nevis",
977 "Korea, Democratic People's Republic of",
978 "Korea, Republic of",
982 "Lao People's Democratic Republic",
995 "Moldova, Republic of",
1003 "Northern Mariana Islands",
1034 "Saint Pierre and Miquelon",
1037 "Palestinian Territory",
1044 "Russian Federation",
1054 "Svalbard and Jan Mayen",
1061 "Sao Tome and Principe",
1063 "Syrian Arab Republic",
1065 "Turks and Caicos Islands",
1067 "French Southern Territories",
1077 "Trinidad and Tobago",
1080 "Tanzania, United Republic of",
1083 "United States Minor Outlying Islands",
1087 "Holy See (Vatican City State)",
1088 "Saint Vincent and the Grenadines",
1090 "Virgin Islands, British",
1091 "Virgin Islands, U.S.",
1094 "Wallis and Futuna",
1104 "Satellite Provider",
1112 "Bonaire, Saint Eustatius and Saba",
1117 public $GEOIP_CONTINENT_CODES = array(
1377 function geoip_load_shared_mem($file)
1379 $fp = fopen($file, "rb");
1381 print "error opening $file: $php_errormsg\n";
1384 $s_array = fstat($fp);
1385 $size = $s_array['size'];
1386 if (($shmid = @shmop_open(GEOIP_SHM_KEY, "w", 0, 0))) {
1387 shmop_delete($shmid);
1388 shmop_close($shmid);
1390 $shmid = shmop_open(GEOIP_SHM_KEY, "c", 0644, $size);
1391 shmop_write($shmid, fread($fp, $size), 0);
1392 shmop_close($shmid);
1395 function _setup_segments($gi)
1397 $gi->databaseType = GEOIP_COUNTRY_EDITION;
1398 $gi->record_length = STANDARD_RECORD_LENGTH;
1399 if ($gi->flags & GEOIP_SHARED_MEMORY) {
1400 $offset = shmop_size($gi->shmid) - 3;
1401 for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
1402 $delim = shmop_read($gi->shmid, $offset, 3);
1404 if ($delim == (chr(255) . chr(255) . chr(255))) {
1405 $gi->databaseType = ord(shmop_read($gi->shmid, $offset, 1));
1406 if ($gi->databaseType >= 106) {
1407 $gi->databaseType -= 105;
1411 if ($gi->databaseType == GEOIP_REGION_EDITION_REV0) {
1412 $gi->databaseSegments = GEOIP_STATE_BEGIN_REV0;
1413 } elseif ($gi->databaseType == GEOIP_REGION_EDITION_REV1) {
1414 $gi->databaseSegments = GEOIP_STATE_BEGIN_REV1;
1415 } elseif (($gi->databaseType == GEOIP_CITY_EDITION_REV0)
1416 || ($gi->databaseType == GEOIP_CITY_EDITION_REV1)
1417 || ($gi->databaseType == GEOIP_ORG_EDITION)
1418 || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
1419 || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
1420 || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
1421 || ($gi->databaseType == GEOIP_ISP_EDITION)
1422 || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
1423 || ($gi->databaseType == GEOIP_USERTYPE_EDITION)
1424 || ($gi->databaseType == GEOIP_USERTYPE_EDITION_V6)
1425 || ($gi->databaseType == GEOIP_LOCATIONA_EDITION)
1426 || ($gi->databaseType == GEOIP_ACCURACYRADIUS_EDITION)
1427 || ($gi->databaseType == GEOIP_CITY_EDITION_REV0_V6)
1428 || ($gi->databaseType == GEOIP_CITY_EDITION_REV1_V6)
1429 || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1)
1430 || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1_V6)
1431 || ($gi->databaseType == GEOIP_ASNUM_EDITION)
1432 || ($gi->databaseType == GEOIP_ASNUM_EDITION_V6)
1434 $gi->databaseSegments = 0;
1435 $buf = shmop_read($gi->shmid, $offset, SEGMENT_RECORD_LENGTH);
1436 for ($j = 0; $j < SEGMENT_RECORD_LENGTH; $j++) {
1437 $gi->databaseSegments += (ord($buf[$j]) << ($j * 8));
1439 if (($gi->databaseType == GEOIP_ORG_EDITION)
1440 || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
1441 || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
1442 || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
1443 || ($gi->databaseType == GEOIP_ISP_EDITION)
1444 || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
1446 $gi->record_length = ORG_RECORD_LENGTH;
1454 if (($gi->databaseType == GEOIP_COUNTRY_EDITION) ||
1455 ($gi->databaseType == GEOIP_COUNTRY_EDITION_V6) ||
1456 ($gi->databaseType == GEOIP_PROXY_EDITION) ||
1457 ($gi->databaseType == GEOIP_NETSPEED_EDITION)
1459 $gi->databaseSegments = GEOIP_COUNTRY_BEGIN;
1462 $filepos = ftell($gi->filehandle);
1463 fseek($gi->filehandle, -3, SEEK_END);
1464 for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
1465 $delim = fread($gi->filehandle, 3);
1466 if ($delim == (chr(255) . chr(255) . chr(255))) {
1467 $gi->databaseType = ord(fread($gi->filehandle, 1));
1468 if ($gi->databaseType >= 106) {
1469 $gi->databaseType -= 105;
1471 if ($gi->databaseType == GEOIP_REGION_EDITION_REV0) {
1472 $gi->databaseSegments = GEOIP_STATE_BEGIN_REV0;
1473 } elseif ($gi->databaseType == GEOIP_REGION_EDITION_REV1) {
1474 $gi->databaseSegments = GEOIP_STATE_BEGIN_REV1;
1475 } elseif (($gi->databaseType == GEOIP_CITY_EDITION_REV0)
1476 || ($gi->databaseType == GEOIP_CITY_EDITION_REV1)
1477 || ($gi->databaseType == GEOIP_CITY_EDITION_REV0_V6)
1478 || ($gi->databaseType == GEOIP_CITY_EDITION_REV1_V6)
1479 || ($gi->databaseType == GEOIP_ORG_EDITION)
1480 || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
1481 || ($gi->databaseType == GEOIP_ISP_EDITION)
1482 || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
1483 || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
1484 || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
1485 || ($gi->databaseType == GEOIP_LOCATIONA_EDITION)
1486 || ($gi->databaseType == GEOIP_ACCURACYRADIUS_EDITION)
1487 || ($gi->databaseType == GEOIP_CITY_EDITION_REV0_V6)
1488 || ($gi->databaseType == GEOIP_CITY_EDITION_REV1_V6)
1489 || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1)
1490 || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1_V6)
1491 || ($gi->databaseType == GEOIP_USERTYPE_EDITION)
1492 || ($gi->databaseType == GEOIP_USERTYPE_EDITION_V6)
1493 || ($gi->databaseType == GEOIP_ASNUM_EDITION)
1494 || ($gi->databaseType == GEOIP_ASNUM_EDITION_V6)
1496 $gi->databaseSegments = 0;
1498 $buf = fread($gi->filehandle, SEGMENT_RECORD_LENGTH);
1499 for ($j = 0; $j < SEGMENT_RECORD_LENGTH; $j++) {
1500 $gi->databaseSegments += (ord($buf[$j]) << ($j * 8));
1502 if (($gi->databaseType == GEOIP_ORG_EDITION)
1503 || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
1504 || ($gi->databaseType == GEOIP_ISP_EDITION)
1505 || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
1506 || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
1507 || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
1509 $gi->record_length = ORG_RECORD_LENGTH;
1514 fseek($gi->filehandle, -4, SEEK_CUR);
1517 if (($gi->databaseType == GEOIP_COUNTRY_EDITION) ||
1518 ($gi->databaseType == GEOIP_COUNTRY_EDITION_V6) ||
1519 ($gi->databaseType == GEOIP_PROXY_EDITION) ||
1520 ($gi->databaseType == GEOIP_NETSPEED_EDITION)
1522 $gi->databaseSegments = GEOIP_COUNTRY_BEGIN;
1524 fseek($gi->filehandle, $filepos, SEEK_SET);
1529 # This should be only used for variable-length records where
1530 # $start + $maxLength may be greater than the shared mem size
1531 function _sharedMemRead($gi, $start, $maxLength)
1533 $readLength = min(shmop_size($gi->shmid) - $start, $maxLength);
1534 return shmop_read($gi->shmid, $start, $readLength);
1537 function geoip_open($filename, $flags)
1540 $gi->flags = $flags;
1541 if ($gi->flags & GEOIP_SHARED_MEMORY) {
1542 $gi->shmid = shmop_open(GEOIP_SHM_KEY, "a", 0, 0);
1544 $gi->filehandle = fopen($filename, "rb") or trigger_error("GeoIP API: Can not open $filename\n", E_USER_ERROR);
1545 if ($gi->flags & GEOIP_MEMORY_CACHE) {
1546 $s_array = fstat($gi->filehandle);
1547 $gi->memory_buffer = fread($gi->filehandle, $s_array['size']);
1551 $gi = _setup_segments($gi);
1555 function geoip_close($gi)
1557 if ($gi->flags & GEOIP_SHARED_MEMORY) {
1561 return fclose($gi->filehandle);
1564 function geoip_country_id_by_name_v6($gi, $name)
1566 $rec = dns_get_record($name, DNS_AAAA);
1570 $addr = $rec[0]["ipv6"];
1571 if (!$addr || $addr == $name) {
1574 return geoip_country_id_by_addr_v6($gi, $addr);
1577 function geoip_country_id_by_name($gi, $name)
1579 $addr = gethostbyname($name);
1580 if (!$addr || $addr == $name) {
1583 return geoip_country_id_by_addr($gi, $addr);
1586 function geoip_country_code_by_name_v6($gi, $name)
1588 $country_id = geoip_country_id_by_name_v6($gi, $name);
1589 if ($country_id !== false) {
1590 return $gi->GEOIP_COUNTRY_CODES[$country_id];
1595 function geoip_country_code_by_name($gi, $name)
1597 $country_id = geoip_country_id_by_name($gi, $name);
1598 if ($country_id !== false) {
1599 return $gi->GEOIP_COUNTRY_CODES[$country_id];
1604 function geoip_country_name_by_name_v6($gi, $name)
1606 $country_id = geoip_country_id_by_name_v6($gi, $name);
1607 if ($country_id !== false) {
1608 return $gi->GEOIP_COUNTRY_NAMES[$country_id];
1613 function geoip_country_name_by_name($gi, $name)
1615 $country_id = geoip_country_id_by_name($gi, $name);
1616 if ($country_id !== false) {
1617 return $gi->GEOIP_COUNTRY_NAMES[$country_id];
1622 function geoip_country_id_by_addr_v6($gi, $addr)
1624 $ipnum = inet_pton($addr);
1625 return _geoip_seek_country_v6($gi, $ipnum) - GEOIP_COUNTRY_BEGIN;
1628 function geoip_country_id_by_addr($gi, $addr)
1630 $ipnum = ip2long($addr);
1631 return _geoip_seek_country($gi, $ipnum) - GEOIP_COUNTRY_BEGIN;
1634 function geoip_country_code_by_addr_v6($gi, $addr)
1636 $country_id = geoip_country_id_by_addr_v6($gi, $addr);
1637 if ($country_id !== false) {
1638 return $gi->GEOIP_COUNTRY_CODES[$country_id];
1643 function geoip_country_code_by_addr($gi, $addr)
1645 if ($gi->databaseType == GEOIP_CITY_EDITION_REV1) {
1646 $record = GeoIP_record_by_addr($gi, $addr);
1648 return $record->country_code;
1651 $country_id = geoip_country_id_by_addr($gi, $addr);
1652 if ($country_id !== false) {
1653 return $gi->GEOIP_COUNTRY_CODES[$country_id];
1659 function geoip_country_name_by_addr_v6($gi, $addr)
1661 $country_id = geoip_country_id_by_addr_v6($gi, $addr);
1662 if ($country_id !== false) {
1663 return $gi->GEOIP_COUNTRY_NAMES[$country_id];
1668 function geoip_country_name_by_addr($gi, $addr)
1670 if ($gi->databaseType == GEOIP_CITY_EDITION_REV1) {
1671 $record = GeoIP_record_by_addr($gi, $addr);
1672 return $record->country_name;
1674 $country_id = geoip_country_id_by_addr($gi, $addr);
1675 if ($country_id !== false) {
1676 return $gi->GEOIP_COUNTRY_NAMES[$country_id];
1682 function _geoip_seek_country_v6($gi, $ipnum)
1684 # arrays from unpack start with offset 1
1685 # yet another php mystery. array_merge work around
1686 # this broken behaviour
1687 $v6vec = array_merge(unpack("C16", $ipnum));
1690 for ($depth = 127; $depth >= 0; --$depth) {
1691 if ($gi->flags & GEOIP_MEMORY_CACHE) {
1692 $buf = _safe_substr(
1694 2 * $gi->record_length * $offset,
1695 2 * $gi->record_length
1697 } elseif ($gi->flags & GEOIP_SHARED_MEMORY) {
1698 $buf = _sharedMemRead($gi,
1699 2 * $gi->record_length * $offset,
1700 2 * $gi->record_length
1703 fseek($gi->filehandle, 2 * $gi->record_length * $offset, SEEK_SET) == 0
1704 or trigger_error("GeoIP API: fseek failed", E_USER_ERROR);
1705 $buf = fread($gi->filehandle, 2 * $gi->record_length);
1708 for ($i = 0; $i < 2; ++$i) {
1709 for ($j = 0; $j < $gi->record_length; ++$j) {
1710 $x[$i] += ord($buf[$gi->record_length * $i + $j]) << ($j * 8);
1714 $bnum = 127 - $depth;
1716 $b_mask = 1 << ($bnum & 7 ^ 7);
1717 if (($v6vec[$idx] & $b_mask) > 0) {
1718 if ($x[1] >= $gi->databaseSegments) {
1723 if ($x[0] >= $gi->databaseSegments) {
1729 trigger_error("GeoIP API: Error traversing database - perhaps it is corrupt?", E_USER_ERROR);
1733 function _geoip_seek_country($gi, $ipnum)
1736 for ($depth = 31; $depth >= 0; --$depth) {
1737 if ($gi->flags & GEOIP_MEMORY_CACHE) {
1738 $buf = _safe_substr(
1740 2 * $gi->record_length * $offset,
1741 2 * $gi->record_length
1743 } elseif ($gi->flags & GEOIP_SHARED_MEMORY) {
1744 $buf = _sharedMemRead(
1746 2 * $gi->record_length * $offset,
1747 2 * $gi->record_length
1750 fseek($gi->filehandle, 2 * $gi->record_length * $offset, SEEK_SET) == 0
1751 or trigger_error("GeoIP API: fseek failed", E_USER_ERROR);
1752 $buf = fread($gi->filehandle, 2 * $gi->record_length);
1755 for ($i = 0; $i < 2; ++$i) {
1756 for ($j = 0; $j < $gi->record_length; ++$j) {
1757 $x[$i] += ord($buf[$gi->record_length * $i + $j]) << ($j * 8);
1760 if ($ipnum & (1 << $depth)) {
1761 if ($x[1] >= $gi->databaseSegments) {
1766 if ($x[0] >= $gi->databaseSegments) {
1772 trigger_error("GeoIP API: Error traversing database - perhaps it is corrupt?", E_USER_ERROR);
1776 function _common_get_org($gi, $seek_org)
1778 $record_pointer = $seek_org + (2 * $gi->record_length - 1) * $gi->databaseSegments;
1779 if ($gi->flags & GEOIP_SHARED_MEMORY) {
1780 $org_buf = _sharedMemRead($gi, $record_pointer, MAX_ORG_RECORD_LENGTH);
1782 fseek($gi->filehandle, $record_pointer, SEEK_SET);
1783 $org_buf = fread($gi->filehandle, MAX_ORG_RECORD_LENGTH);
1785 $org_buf = _safe_substr($org_buf, 0, strpos($org_buf, "\0"));
1789 function _get_org_v6($gi, $ipnum)
1791 $seek_org = _geoip_seek_country_v6($gi, $ipnum);
1792 if ($seek_org == $gi->databaseSegments) {
1795 return _common_get_org($gi, $seek_org);
1798 function _get_org($gi, $ipnum)
1800 $seek_org = _geoip_seek_country($gi, $ipnum);
1801 if ($seek_org == $gi->databaseSegments) {
1804 return _common_get_org($gi, $seek_org);
1808 function geoip_name_by_addr_v6($gi, $addr)
1810 if ($addr == null) {
1813 $ipnum = inet_pton($addr);
1814 return _get_org_v6($gi, $ipnum);
1817 function geoip_name_by_addr($gi, $addr)
1819 if ($addr == null) {
1822 $ipnum = ip2long($addr);
1823 return _get_org($gi, $ipnum);
1826 function geoip_org_by_addr($gi, $addr)
1828 return geoip_name_by_addr($gi, $addr);
1831 function _get_region($gi, $ipnum)
1833 if ($gi->databaseType == GEOIP_REGION_EDITION_REV0) {
1834 $seek_region = _geoip_seek_country($gi, $ipnum) - GEOIP_STATE_BEGIN_REV0;
1835 if ($seek_region >= 1000) {
1836 $country_code = "US";
1837 $region = chr(($seek_region - 1000) / 26 + 65) . chr(($seek_region - 1000) % 26 + 65);
1839 $country_code = $gi->GEOIP_COUNTRY_CODES[$seek_region];
1842 return array($country_code, $region);
1843 } elseif ($gi->databaseType == GEOIP_REGION_EDITION_REV1) {
1844 $seek_region = _geoip_seek_country($gi, $ipnum) - GEOIP_STATE_BEGIN_REV1;
1845 if ($seek_region < US_OFFSET) {
1848 } elseif ($seek_region < CANADA_OFFSET) {
1849 $country_code = "US";
1850 $region = chr(($seek_region - US_OFFSET) / 26 + 65) . chr(($seek_region - US_OFFSET) % 26 + 65);
1851 } elseif ($seek_region < WORLD_OFFSET) {
1852 $country_code = "CA";
1853 $region = chr(($seek_region - CANADA_OFFSET) / 26 + 65) . chr(($seek_region - CANADA_OFFSET) % 26 + 65);
1855 $country_code = $gi->GEOIP_COUNTRY_CODES[(int) (($seek_region - WORLD_OFFSET) / FIPS_RANGE)];
1858 return array($country_code, $region);
1863 function geoip_region_by_addr($gi, $addr)
1865 if ($addr == null) {
1868 $ipnum = ip2long($addr);
1869 return _get_region($gi, $ipnum);
1872 function _safe_substr($string, $start, $length)
1874 // workaround php's broken substr, strpos, etc handling with
1875 // mbstring.func_overload and mbstring.internal_encoding
1876 $mbExists = extension_loaded('mbstring');
1879 $enc = mb_internal_encoding();
1880 mb_internal_encoding('ISO-8859-1');
1883 $buf = substr($string, $start, $length);
1886 mb_internal_encoding($enc);