*  @param start what timestamp to start. Use null to not specify one.
     *  @param end what timestamp to end. Use null to not specify one.
     *  @return a LogEntry[] of the blocks
     *  @throws IOException if a network error occurs
     *  @throws IllegalArgumentException if start date is before end date
     *  @since 0.12
     */
    protected Wiki.LogEntry[] getIPBlockList(String user, Calendar start, Calendar end) throws IOException
    {
        // quick param check
        if (start != null && end != null)
            if (start.before(end))
                throw new IllegalArgumentException("Specified start date is before specified end date!");
        String bkstart = calendarToTimestamp(start == null ? makeCalendar() : start);

        // url base
        StringBuilder urlBase = new StringBuilder(query);
        urlBase.append("list=blocks&bklimit=");
        urlBase.append(max);
        if (end != null)
        {
            urlBase.append("&bkend=");
            urlBase.append(calendarToTimestamp(end));
        }
        if (!user.isEmpty())
        {
            urlBase.append("&bkusers=");
            urlBase.append(user);
        }
        urlBase.append("&bkstart=");

        // connection
        ArrayList<Wiki.LogEntry> entries = new ArrayList<Wiki.LogEntry>(1333);
        do
        {
            String line = fetch(urlBase.toString() + bkstart, "getIPBlockList");

            // set start parameter to new value if required
            if (line.contains("bkstart"))
                bkstart = parseAttribute(line, "bkstart", 0);
            else
                bkstart = null;

            // parse xml
            for (int a = line.indexOf("<block"); a > 0; a = line.indexOf("<block ", ++a))
            {
                // find entry
                int b = line.indexOf("/>", a);
                String temp = line.substring(a, b);
                Wiki.LogEntry le = parseLogEntry(temp);
                le.type = BLOCK_LOG;
                le.action = "block";
                // parseLogEntries parses block target into le.user due to mw.api 
                // attribute name
                if (le.user == null) // autoblock
                    le.target = "#" + parseAttribute(temp, "id", 0);
                else
                    le.target = namespaceIdentifier(USER_NAMESPACE) + ":" + le.user.username;
                // parse blocker for real
                le.user = new Wiki.User(decode(parseAttribute(temp, "by", 0)));
                entries.add(le);
            }
        }
        while (bkstart != null);

        // log statement
        StringBuilder logRecord = new StringBuilder("Successfully fetched IP block list ");
        if (!user.isEmpty())
        {
            logRecord.append(" for ");
            logRecord.append(user);
        }
        if (start != null)
        {
            logRecord.append(" from ");
            logRecord.append(start.getTime().toString());
        }
        if (end != null)
        {
            logRecord.append(" to ");
            logRecord.append(end.getTime().toString());
        }
        int size = entries.size();
        logRecord.append(" (");
        logRecord.append(size);
        logRecord.append(" entries)");
        log(Level.INFO, logRecord.toString(), "getIPBlockList");
        return entries.toArray(new Wiki.LogEntry[size]);
     }