public HashMap<String, Object> getPageInfo(String page) throws IOException
    {
        // fetch
        StringBuilder url = new StringBuilder(query);
        url.append("prop=info&intoken=edit%7Cwatch&inprop=protection%7Cdisplaytitle&titles=");
        url.append(URLEncoder.encode(normalize(page), "UTF-8"));
        String line = fetch(url.toString(), "getPageInfo");
        HashMap<String, Object> info = new HashMap<String, Object>(15);

        // does the page exist?
        boolean exists = !line.contains("missing=\"\"");
        info.put("exists", exists);
        if (exists)
        {
            // last edit time
            int a = line.indexOf("touched=\"") + 9;
            int b = line.indexOf('\"', a);
            info.put("lastpurged", timestampToCalendar(convertTimestamp(line.substring(a, b))));

            // last revid
            a = line.indexOf("lastrevid=\"") + 11;
            b = line.indexOf('\"', a);
            info.put("lastrevid", Long.parseLong(line.substring(a, b)));

            // size
            a = line.indexOf("length=\"") + 8;
            b = line.indexOf('\"', a);
            info.put("size", Integer.parseInt(line.substring(a, b)));

            // parse protection level
            // FIXME: this probably needs to be rewritten
            int z = line.indexOf("type=\"edit\"");
            if (z != -1)
            {
                String s = line.substring(z, z + 30);
                if (s.contains("sysop"))
                    info.put("protection", FULL_PROTECTION);
                else
                {
                    s = line.substring(z + 30); // cut out edit tag
                    info.put("protection", line.contains("level=\"sysop\"") ? SEMI_AND_MOVE_PROTECTION : SEMI_PROTECTION);
                }
            }
            else
                info.put("protection", line.contains("type=\"move\"") ? MOVE_PROTECTION : NO_PROTECTION);
        }
        else
        {
            info.put("lastedited", null);
            info.put("lastrevid", -1L);
            info.put("size", -1);
            // is the page create protected?
            info.put("protection", line.contains("type=\"create\"") ? PROTECTED_DELETED_PAGE : NO_PROTECTION);
        }

        // cascade protection
        info.put("cascade", line.contains("cascade=\"\""));

        // displaytitle
        int a = line.indexOf("displaytitle=\"") + 14;
        int b = line.indexOf('\"', a);
        info.put("displaytitle", line.substring(a, b));

        // edit/move/upload/protect/delete token
        a = line.indexOf("edittoken=\"") + 11;
        b = line.indexOf('\"', a);
        info.put("token", line.substring(a, b));
        
        // watchlist token
        a = line.indexOf("watchtoken=\"") + 12;
        b = line.indexOf('\"', a);
        info.put("watchtoken", line.substring(a, b));

        // timestamp
        info.put("timestamp", makeCalendar());

        log(Level.INFO, "Successfully retrieved page info for " + page, "getPageInfo");
        return info;
    }