/* Copyright (c) 2005 Ricebridge. All Rights Reserved. * * This file is available under the terms and conditions of the * Ricebridge "Open Source API" policy; Ricebridge grants use of this * copyrighted work under the terms of a BSD-style license only. See * http://www.opensource.org/licenses/bsd-license.php for more * information. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Ricebridge nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ import com.ricebridge.csvman.CsvManager; import java.io.*; import java.sql.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; /** Full source code for MaxMindManager class. * Please note that the code in this class does include * error handling functionality. You will need to add this if * you intend to use this code in a production environment. */ public class MaxMindManager { public static final ArrayList sProxyHeaders = new ArrayList(); static { sProxyHeaders.add("HTTP_X_FORWARDED_FOR"); sProxyHeaders.add("HTTP_FORWARDED"); sProxyHeaders.add("HTTP_CLIENT_IP"); } // public methods /** Insert records from a CSV file. * @param pDataFile CSV text file from MaxMind * @param pConnection database connection */ public void insert( File pDataFile, Connection pConnection ) throws Exception { CsvManager csvman = new CsvManager(); MaxMindInserter mmi = new MaxMindInserter( pConnection ); csvman.load( pDataFile, mmi ); } /** Update records from a CSV file. * @param pDataFile new CSV text file from MaxMind * @param pConnection database connection */ public void update( File pDataFile, Connection pConnection ) throws Exception { int numlines = (int) pDataFile.length()/64; CsvManager csvman = new CsvManager(); MaxMindLineListener mmll = new MaxMindLineListener( numlines ); csvman.load( pDataFile, mmll ); String countq = "SELECT COUNT(*) FROM IPLookup"; String dataq = "SELECT BeginIPNum, EndIPNum, CountryCode FROM IPLookup ORDER BY BeginIPNum"; int recI = 0; long[] old_range = null; short[] old_country = null; Statement st = pConnection.createStatement(); ResultSet rs = st.executeQuery( countq ); rs.next(); int numrecords = rs.getInt(1); rs.close(); st.close(); st = pConnection.createStatement(); rs = st.executeQuery( dataq ); old_range = new long[numrecords]; old_country = new short[numrecords]; while( rs.next() ) { old_range[recI] = (rs.getLong(1) << 32) | rs.getLong(2); old_country[recI] = rs.getShort(3); recI++; } rs.close(); st.close(); ArrayList newrecs = new ArrayList(); ArrayList oldrecs = new ArrayList(); compare( mmll.getRanges(), mmll.getCountries(), mmll.getNumLines(), old_range, old_country, recI, newrecs, oldrecs ); insertRecords( newrecs, pConnection ); deleteRecords( oldrecs, pConnection ); } /** Insert records into database * @param pRecords list of records * @param pConnection database connection */ public void insertRecords( List pRecords, Connection pConnection ) throws Exception { String insertq = new String( "INSERT INTO IPLookup VALUES (?,?,?)" ); PreparedStatement st = pConnection.prepareStatement( insertq ); for( Iterator rI = pRecords.iterator(); rI.hasNext(); ) { Record r = (Record) rI.next(); st.setLong( 1, r.iStart ); st.setLong( 2, r.iEnd ); st.setShort( 3, r.iCountry ); st.executeUpdate(); } st.close(); } /** Delete records from database * @param pRecords list of records * @param pConnection database connection */ public void deleteRecords( List pRecords, Connection pConnection ) throws Exception { String deleteq = new String( "DELETE FROM IPLookup WHERE BeginIPNum = ? AND EndIPNum = ? AND CountryCode = ?" ); PreparedStatement st = pConnection.prepareStatement( deleteq ); for( Iterator rI = pRecords.iterator(); rI.hasNext(); ) { Record r = (Record) rI.next(); st.setLong( 1, r.iStart ); st.setLong( 2, r.iEnd ); st.setShort( 3, r.iCountry ); st.executeUpdate(); } st.close(); } /** Find Country Code from HTTP Request * @param pRecords HTTP request * @param pConnection database connection */ public String findCountry( HttpServletRequest pRequest, Connection pConnection ) throws Exception { String ipaddr = getIPAddress( pRequest ); return findCountry( ipaddr, pConnection ); } /** Find Country Code from IP Address * @param pIPAddress IP Address in 255.255.255.255 form * @param pConnection database connection */ public String findCountry( String pIPAddress, Connection pConnection ) throws Exception { String[] quads = pIPAddress.split( "\\." ); if( 4 != quads.length ) { throw new Exception( "invalid ip addr:'"+pIPAddress+"'" ); } long ipnum = (long)16777216*Long.parseLong(quads[0]) + (long)65536*Long.parseLong(quads[1]) + (long)256*Long.parseLong(quads[2]) + (long)1*Long.parseLong(quads[3]) ; String cc = "US"; // default String ipq = "SELECT CountryCode FROM IPLookup WHERE BeginIPNum <= "+ipnum+" AND "+ipnum+" <= EndIPNum"; short ci = -1; Statement st = pConnection.createStatement(); ResultSet rs = st.executeQuery( ipq ); if( rs.next() ) { ci = rs.getShort(1); } rs.close(); st.close(); if( -1 != ci ) { cc = MaxMindDef.sCountry[ci]; } return cc; } /** Compare new and old CSV records and extract new records to add and old records to delete * @param pNewRanges new start and end ip numbers * @param pNewCountries new country codes * @param pNewMax, actual number of new records (array may be larger) * @param pOldRanges, old start and end ip numbers * @param pOldCountries old country codes * @param pOldMax, actual number of old records * @param pAdd, list to add completely new records to * @param pDelete list to add completely removed records to */ public void compare( long[] pNewRanges, short[] pNewCountries, int pNewMax, long[] pOldRanges, short[] pOldCountries, int pOldMax, ArrayList pAdd, ArrayList pDelete ) { pAdd.clear(); pDelete.clear(); long[] nr = pNewRanges; short[] nc = pNewCountries; long[] or = pOldRanges; short[] oc = pOldCountries; int nI = 0; int oI = 0; int nmax = pNewMax; int omax = pOldMax; long nrange; long ns; long ne; short nk; long orange; long os; long oe; short ok; while( nI < nmax && oI < omax ) { nrange = nr[nI]; ns = nrange >>> 32; ne = nrange & 0xFFFFFFFFL; nk = nc[nI]; orange = or[oI]; os = orange >>> 32; oe = orange & 0xFFFFFFFFL; ok = oc[oI]; if( ns == os && ne == oe && nk == ok ) { nI++; oI++; } else if( ns < os || ( ns == os && ne < oe ) || ( ns == os && ne == oe && nk < ok ) ) { pAdd.add( new Record( ns, ne, nk ) ); nI++; } else if( ns > os || ( ns == os && ne > oe ) || ( ns == os && ne == oe && nk > ok ) ) { pDelete.add( new Record( os, oe, ok ) ); oI++; } } if( nI < nmax ) { for( ; nI < nmax; nI++ ) { nrange = nr[nI]; ns = nrange >>> 32; ne = nrange & 0xFFFFFFFFL; nk = nc[nI]; pAdd.add( new Record( ns, ne, nk ) ); } } if( oI < omax ) { for( ; oI < omax; oI++ ) { orange = or[oI]; os = orange >>> 32; oe = orange & 0xFFFFFFFFL; ok = oc[oI]; pDelete.add( new Record( os, oe, ok ) ); } } } /** Simplistic hard-coded method to open a database connection. */ public Connection getConnection() throws Exception { DriverManager.registerDriver( new com.mysql.jdbc.Driver() ); return DriverManager.getConnection( "jdbc:mysql://localhost/testdb", "testuser", "testpass" ); } /** Parse IP Address String out of HTTP headers * @param pRequest HTTP request */ public String getIPAddress( HttpServletRequest pRequest ) { String ipaddr = pRequest.getRemoteAddr(); for( Iterator hIt = sProxyHeaders.iterator(); hIt.hasNext(); ) { String ph = (String) hIt.next(); String v = pRequest.getHeader( ph ); if( null != ph && null != v && !"".equals( v ) ) { ipaddr = v; break; } } return ipaddr; } /** Internal class to hold start and end ip numbers and country code. */ public static class Record { public Record( long pStart, long pEnd, short pCountry ) { iStart = pStart; iEnd = pEnd; iCountry = pCountry; } public long iStart; public long iEnd; public short iCountry; public String toString() { return "s:"+iStart+",e:"+iEnd+",c:"+MaxMindDef.sCountry[iCountry]; } } }