/* * HTTPUtils.java * * Created on Jan 29, 2010, 9:21:55 AM * * Description: Provides HTTP utilities. * * Copyright (C) Jan 29, 2010 reed. * * This program is free software; you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.texai.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PushbackInputStream; import java.util.HashMap; import java.util.Map; import net.jcip.annotations.NotThreadSafe; import org.apache.log4j.Logger; /** Provides HTTP utilities. * * @author reed */ @NotThreadSafe public final class HTTPUtils { /** the logger */ private static final Logger LOGGER = Logger.getLogger(HTTPUtils.class); /** the HTTP response buffer size */ public static final int HTTP_RESPONSE_BUFFER_SIZE = 2048; /** Prevents the instantiation of this utility class. */ private HTTPUtils() { } /** Consumes bytes from the given pushback input stream and returns the HTTP message when the end of it is reached. The * input stream remains open for the next message in the pipeline. * Per: http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 * * @param pushbackInputStream the given pushback input stream * @return the HTTP message */ public static byte[] consumeHTTPMessage(final PushbackInputStream pushbackInputStream) { //Preconditions assert pushbackInputStream != null : "inputStream must not be null"; final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte previousByte = -1; final byte[] buffer = new byte[HTTP_RESPONSE_BUFFER_SIZE]; int nbrBytesRead = 0; int index = 0; int contentLength = 0; final StringBuilder stringBuilder = new StringBuilder(); try { // Consume the status lines and response headers, each which is terminated by CRLF. // Parse the Content-Length header to obtain the length of the subsequent message body. // An empty line terminates the headers. while (true) { if (index + 1 > nbrBytesRead) { nbrBytesRead = pushbackInputStream.read(buffer); if (nbrBytesRead == -1) { throw new TexaiException("unexpected end of stream while reading response headers"); } index = 0; } final byte currentByte = buffer[index++]; byteArrayOutputStream.write(currentByte); stringBuilder.append((char) currentByte); if (previousByte == '\r' && currentByte == '\n') { final String headerLine = stringBuilder.substring(0, stringBuilder.length() - 2); stringBuilder.setLength(0); LOGGER.debug("headerLine: " + headerLine); if (headerLine.isEmpty()) { break; } else if (headerLine.startsWith("Content-Length:")) { contentLength = Integer.parseInt(headerLine.substring(15).trim()); } } previousByte = currentByte; } // consume the message body for (int i = 0; i < contentLength; i++) { if (index + 1 > nbrBytesRead) { nbrBytesRead = pushbackInputStream.read(buffer); if (nbrBytesRead == -1) { throw new TexaiException("unexpected end of stream while reading response headers"); } index = 0; } final byte currentByte = buffer[index++]; //LOGGER.info("body: " + String.valueOf((char) currentByte)); byteArrayOutputStream.write(currentByte); } // push back any remaining bytes in the buffer final int nbrBytesRemaining = nbrBytesRead - index; if (nbrBytesRemaining > 0) { pushbackInputStream.unread(buffer, index, nbrBytesRead - index); } } catch (IOException ex) { throw new TexaiException(ex); } return byteArrayOutputStream.toByteArray(); } /** Returns the parameter dictionary, parameter --> value, parsed from the given well-formed HTTP query. * * @param query the given well-formed HTTP query * @return the parameter dictionary, parameter --> value */ public static Map<String, String> getQueryMap(final String query) { //Preconditions assert query != null : "query must not be null"; assert !query.isEmpty() : "query must not be empty"; final String[] parameterAssignments = query.split("&"); final Map<String, String> parameterDictionary = new HashMap<>(); for (String parameterAssignment : parameterAssignments) { final String parameter = parameterAssignment.split("=")[0]; final String value = parameterAssignment.split("=")[1]; parameterDictionary.put(parameter, value); } return parameterDictionary; } /** Gets the version number from the given user-agent string. * * @param userAgent the user-agent string * @param position the position * @return the version number */ @SuppressWarnings("fallthrough") public static String getVersionNumber(final String userAgent, final int position) { int position1 = position; if (position1 < 0) { return ""; } final StringBuilder stringBuilder = new StringBuilder(); int status = 0; while (position1 < userAgent.length()) { final char ch = userAgent.charAt(position1); switch (status) { case 0: // No valid digits encountered yet if (ch == ' ' || ch == '/') { break; } if (ch == ';' || ch == ')') { return ""; } status = 1; // intentional fallthrough to case 1 case 1: // Version number in progress if (ch == ';' || ch == '/' || ch == ')' || ch == '(' || ch == '[') { return stringBuilder.toString().trim(); } if (ch == ' ') { status = 2; } stringBuilder.append(ch); break; case 2: // Space encountered - Might need to end the parsing if ((Character.isLetter(ch) && Character.isLowerCase(ch)) || Character.isDigit(ch)) { stringBuilder.append(ch); status = 1; } else { return stringBuilder.toString().trim(); } break; } position1++; } return stringBuilder.toString().trim(); } /** Gets the first version number in the given user-agent string, truncated to the given number of digits * * @param userAgent the user agent string * @param position the position * @param numDigits the number of digits to truncate * @return */ private static String getFirstVersionNumber(final String userAgent, final int position, final int numDigits) { final String versionNumber = getVersionNumber(userAgent, position); if (versionNumber == null) { return ""; } int i = 0; String truncatedVersionNumber = ""; while (i < versionNumber.length() && i < numDigits) { truncatedVersionNumber += String.valueOf(versionNumber.charAt(i)); i++; } return truncatedVersionNumber; } /** Returns a string array of the given three strings. * * @param string1 the first string * @param string2 the second string * @param string3 the third string * @return a string array of the given three strings */ private static String[] getArray(final String string1, final String string2, final String string3) { final String[] stringArray = new String[3]; stringArray[0] = string1; stringArray[1] = string2; stringArray[2] = string3; return stringArray; } /** Returns the name and version of the bot if the given user-agent is a bot. * * @param userAgent the given user-agent string * @return the name of the bot if the given user-agent is a bot, otherwise returns null */ @SuppressWarnings("UnusedAssignment") public static String[] getBotName(final String userAgent) { final String userAgentLowerCase = userAgent.toLowerCase(); int position = 0; final String botName; if ((position = userAgentLowerCase.indexOf("google/")) > -1) { botName = "Google"; position += 7; } else if ((position = userAgentLowerCase.indexOf("msnbot/")) > -1) { botName = "MSNBot"; position += 7; } else if ((position = userAgentLowerCase.indexOf("googlebot/")) > -1) { botName = "Google"; position += 10; } else if ((position = userAgentLowerCase.indexOf("webcrawler/")) > -1) { botName = "WebCrawler"; position += 11; } else if ((position = userAgentLowerCase.indexOf("gulper web bot")) > -1) { botName = "Gulper"; position += 15; } else // The following bots don't have any version number in their User-Agent strings. if ((position = userAgentLowerCase.indexOf("inktomi")) > -1) { botName = "Inktomi"; position = -1; } else if ((position = userAgentLowerCase.indexOf("teoma")) > -1) { botName = "Teoma"; position = -1; } else if ((position = userAgentLowerCase.indexOf("yahoo! slurp")) > -1) { botName = "Yahoo"; position = -1; } else if ((position = userAgentLowerCase.indexOf("scoutjet")) > -1) { botName = "ScoutJet"; position = -1; } else { return null; } return getArray(botName, botName, botName + getVersionNumber(userAgentLowerCase, position)); } /** Gets the client operating system from the given user-agent string. * * @param userAgent the given user-agent string * @return the client operating system */ @SuppressWarnings("UnusedAssignment") public static String[] getOS(final String userAgent) { if (getBotName(userAgent) != null) { return getArray("Bot", "Bot", "Bot"); } String[] result = null; int position; if ((position = userAgent.indexOf("Windows-NT")) > -1) { result = getArray("Win", "WinNT", "Win" + getVersionNumber(userAgent, position + 8)); } else if (userAgent.contains("Windows NT")) { // The different versions of Windows NT are decoded in the verbosity level 2 // ie: Windows NT 5.1 = Windows XP if ((position = userAgent.indexOf("Windows NT 5.1")) > -1) { result = getArray("Win", "WinXP", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows NT 6.0")) > -1) { result = getArray("Win", "Vista", "Vista" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows NT 5.0")) > -1) { result = getArray("Win", "Seven", "Seven " + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows NT 5.0")) > -1) { result = getArray("Win", "Win2000", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows NT 5.2")) > -1) { result = getArray("Win", "Win2003", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows NT 4.0")) > -1) { result = getArray("Win", "WinNT4", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows NT)")) > -1) { result = getArray("Win", "WinNT", "WinNT"); } else if ((position = userAgent.indexOf("Windows NT;")) > -1) { result = getArray("Win", "WinNT", "WinNT"); } else { result = getArray("Win", "<b>WinNT?</b>", "<b>WinNT?</b>"); } } else if (userAgent.contains("Win")) { if (userAgent.contains("Windows")) { if ((position = userAgent.indexOf("Windows 98")) > -1) { result = getArray("Win", "Win98", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows_98")) > -1) { result = getArray("Win", "Win98", "Win" + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("Windows 2000")) > -1) { result = getArray("Win", "Win2000", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows 95")) > -1) { result = getArray("Win", "Win95", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows 9x")) > -1) { result = getArray("Win", "Win9x", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows ME")) > -1) { result = getArray("Win", "WinME", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows CE")) > -1) { result = getArray("Win", "WinCE", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows XP")) > -1) { result = getArray("Win", "WinXP", "Win" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Windows 3.1")) > -1) { result = getArray("Win", "Win31", "Win" + getVersionNumber(userAgent, position + 7)); } // If no version was found, rely on the following code to detect "WinXX" // As some User-Agents include two references to Windows // Ex: Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.5) } if (result == null) { if ((position = userAgent.indexOf("Win98")) > -1) { result = getArray("Win", "Win98", "Win" + getVersionNumber(userAgent, position + 3)); } else if ((position = userAgent.indexOf("Win31")) > -1) { result = getArray("Win", "Win31", "Win" + getVersionNumber(userAgent, position + 3)); } else if ((position = userAgent.indexOf("Win95")) > -1) { result = getArray("Win", "Win95", "Win" + getVersionNumber(userAgent, position + 3)); } else if ((position = userAgent.indexOf("Win 9x")) > -1) { result = getArray("Win", "Win9x", "Win" + getVersionNumber(userAgent, position + 3)); } else if ((position = userAgent.indexOf("WinNT4.0")) > -1) { result = getArray("Win", "WinNT4", "Win" + getVersionNumber(userAgent, position + 3)); } else if ((position = userAgent.indexOf("WinNT")) > -1) { result = getArray("Win", "WinNT", "Win" + getVersionNumber(userAgent, position + 3)); } } if (result == null) { if ((position = userAgent.indexOf("Windows XP")) > -1) { result = getArray("Win", "WinXP", "<b>Win?</b>"); } } if (result == null) { if ((position = userAgent.indexOf("Windows")) > -1) { result = getArray("Win", "<b>Win?</b>", "<b>Win?" + getVersionNumber(userAgent, position + 7) + "</b>"); } else if ((position = userAgent.indexOf("Win")) > -1) { result = getArray("Win", "<b>Win?</b>", "<b>Win?" + getVersionNumber(userAgent, position + 3) + "</b>"); } else // Should not happen at this point { result = getArray("Win", "<b>Win?</b>", "<b>Win?</b>"); } } } else if ((position = userAgent.indexOf("Mac OS X")) > -1) { if ((userAgent.indexOf("iPhone")) > -1) { position = userAgent.indexOf("iPhone OS"); result = getArray("Mac", "MacOSX-iPhone", "MacOS-iPhone " + ((position < 0) ? "" : getVersionNumber(userAgent, position + 9))); } else { result = getArray("Mac", "MacOSX", "MacOS " + getVersionNumber(userAgent, position + 8)); } } else if ((position = userAgent.indexOf("Mac_PowerPC")) > -1) { result = getArray("Mac", "MacPPC", "MacOS " + getVersionNumber(userAgent, position + 3)); } else if ((position = userAgent.indexOf("Macintosh")) > -1) { if (userAgent.contains("PPC")) { result = getArray("Mac", "MacPPC", "MacOS?"); } else { result = getArray("Mac?", "Mac?", "MacOS?"); } } else if ((position = userAgent.indexOf("FreeBSD")) > -1) { result = getArray("*BSD", "*BSD FreeBSD", "FreeBSD " + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("OpenBSD")) > -1) { result = getArray("*BSD", "*BSD OpenBSD", "OpenBSD " + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Android ")) > -1) { result = getArray("Android", "Android", "Android " + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("Linux")) > -1) { String detail = "Linux " + getVersionNumber(userAgent, position + 5); String med = "Linux"; if ((position = userAgent.indexOf("Ubuntu/")) > -1) { detail = "Ubuntu " + getVersionNumber(userAgent, position + 7); med += " Ubuntu"; } result = getArray("Linux", med, detail); } else if ((position = userAgent.indexOf("CentOS")) > -1) { result = getArray("Linux", "Linux CentOS", "CentOS"); } else if ((position = userAgent.indexOf("NetBSD")) > -1) { result = getArray("*BSD", "*BSD NetBSD", "NetBSD " + getVersionNumber(userAgent, position + 6)); } else if ((position = userAgent.indexOf("Unix")) > -1) { result = getArray("Linux", "Linux", "Linux " + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("SunOS")) > -1) { result = getArray("Unix", "SunOS", "SunOS" + getVersionNumber(userAgent, position + 5)); } else if ((position = userAgent.indexOf("IRIX")) > -1) { result = getArray("Unix", "IRIX", "IRIX" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("SonyEricsson")) > -1) { result = getArray("SonyEricsson", "SonyEricsson", "SonyEricsson" + getVersionNumber(userAgent, position + 12)); } else if ((position = userAgent.indexOf("Nokia")) > -1) { result = getArray("Nokia", "Nokia", "Nokia" + getVersionNumber(userAgent, position + 5)); } else if ((position = userAgent.indexOf("BlackBerry")) > -1) { result = getArray("BlackBerry", "BlackBerry", "BlackBerry" + getVersionNumber(userAgent, position + 10)); } else if ((position = userAgent.indexOf("SymbianOS")) > -1) { result = getArray("SymbianOS", "SymbianOS", "SymbianOS" + getVersionNumber(userAgent, position + 10)); } else if ((position = userAgent.indexOf("BeOS")) > -1) { result = getArray("BeOS", "BeOS", "BeOS"); } else if ((position = userAgent.indexOf("Nintendo Wii")) > -1) { result = getArray("Nintendo Wii", "Nintendo Wii", "Nintendo Wii" + getVersionNumber(userAgent, position + 10)); } else { result = getArray("<b>?</b>", "<b>?</b>", "<b>?</b>"); } return result; } /** Gets the browser and version number from the given user-agent string. * * @param userAgent the given user-agent string * @return the browser and version number */ @SuppressWarnings("UnusedAssignment") public static String[] getBrowser(String userAgent) { final String[] botName; if ((botName = getBotName(userAgent)) != null) { return botName; } final String[] result; int position; if ((position = userAgent.indexOf("Lotus-Notes/")) > -1) { result = getArray("LotusNotes", "LotusNotes", "LotusNotes" + getVersionNumber(userAgent, position + 12)); } else if ((position = userAgent.indexOf("Opera")) > -1) { result = getArray("Opera", "Opera" + getFirstVersionNumber(userAgent, position + 5, 1), "Opera" + getVersionNumber(userAgent, position + 5)); } else if ((position = userAgent.indexOf("OmniWeb/")) > -1) { result = getArray("OmniWeb", "OmniWeb", "OmniWeb" + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("Debian/")) > -1) { result = getArray("Debian", "Debian", "Debian" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Epiphany/")) > -1) { result = getArray("Epiphany", "Epiphany", "Epiphany" + getVersionNumber(userAgent, position + 9)); } else if ((position = userAgent.indexOf("Galeon/")) > -1) { result = getArray("Galeon", "Galeon", "Galeon" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("ELinks/")) > -1) { result = getArray("ELinks", "ELinks", "ELinks" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("ELinks (")) > -1) { result = getArray("ELinks", "ELinks", "ELinks" + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("Links/")) > -1) { result = getArray("Links", "Links", "Links" + getVersionNumber(userAgent, position + 6)); } else if ((position = userAgent.indexOf("Links (")) > -1) { result = getArray("Links", "Links", "Links" + getVersionNumber(userAgent, position + 7)); } else if ((position = userAgent.indexOf("Lynx/")) > -1) { result = getArray("Lynx", "Lynx", "Lynx" + getVersionNumber(userAgent, position + 5)); } else if ((position = userAgent.indexOf("w3m/")) > -1) { result = getArray("w3m", "w3m", "w3m" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("HandHTTP ")) > -1) { result = getArray("HandHTTP", "HandHTTP", "HandHTTP" + getVersionNumber(userAgent, position + 9)); } else if (userAgent.contains("MSIE")) { if ((position = userAgent.indexOf("MSIE 6.0")) > -1) { result = getArray("MSIE", "MSIE6", "MSIE" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("MSIE 5.0")) > -1) { result = getArray("MSIE", "MSIE5", "MSIE" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("MSIE 5.5")) > -1) { result = getArray("MSIE", "MSIE5.5", "MSIE" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("MSIE 5.")) > -1) { result = getArray("MSIE", "MSIE5.x", "MSIE" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("MSIE 4")) > -1) { result = getArray("MSIE", "MSIE4", "MSIE" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("MSIE 7")) > -1 && !userAgent.contains("Trident/4.0")) { result = getArray("MSIE", "MSIE7", "MSIE" + getVersionNumber(userAgent, position + 4)); } else if ((position = userAgent.indexOf("MSIE 8")) > -1 || userAgent.contains("Trident/4.0")) { result = getArray("MSIE", "MSIE8", "MSIE" + getVersionNumber(userAgent, position + 4)); } else { result = getArray("MSIE", "<b>MSIE?</b>", "<b>MSIE?" + getVersionNumber(userAgent, userAgent.indexOf("MSIE") + 4) + "</b>"); } } else if ((position = userAgent.indexOf("Gecko/")) > -1) { result = getArray("Gecko", "Gecko", "Gecko" + getFirstVersionNumber(userAgent, position + 5, 4)); if ((position = userAgent.indexOf("Camino/")) > -1) { result[1] += "(Camino)"; result[2] += "(Camino" + getVersionNumber(userAgent, position + 7) + ")"; } else if ((position = userAgent.indexOf("Chimera/")) > -1) { result[1] += "(Chimera)"; result[2] += "(Chimera" + getVersionNumber(userAgent, position + 8) + ")"; } else if ((position = userAgent.indexOf("Firebird/")) > -1) { result[1] += "(Firebird)"; result[2] += "(Firebird" + getVersionNumber(userAgent, position + 9) + ")"; } else if ((position = userAgent.indexOf("Phoenix/")) > -1) { result[1] += "(Phoenix)"; result[2] += "(Phoenix" + getVersionNumber(userAgent, position + 8) + ")"; } else if ((position = userAgent.indexOf("Galeon/")) > -1) { result[1] += "(Galeon)"; result[2] += "(Galeon" + getVersionNumber(userAgent, position + 7) + ")"; } else if ((position = userAgent.indexOf("Firefox/")) > -1) { result[1] += "(Firefox)"; result[2] += "(Firefox" + getVersionNumber(userAgent, position + 8) + ")"; } else if ((position = userAgent.indexOf("Netscape/")) > -1) { if ((position = userAgent.indexOf("Netscape/6")) > -1) { result[1] += "(NS6)"; result[2] += "(NS" + getVersionNumber(userAgent, position + 9) + ")"; } else if ((position = userAgent.indexOf("Netscape/7")) > -1) { result[1] += "(NS7)"; result[2] += "(NS" + getVersionNumber(userAgent, position + 9) + ")"; } else { result[1] += "(NS?)"; result[2] += "(NS?" + getVersionNumber(userAgent, userAgent.indexOf("Netscape/") + 9) + ")"; } } } else if ((position = userAgent.indexOf("Netscape/")) > -1) { if ((position = userAgent.indexOf("Netscape/4")) > -1) { result = getArray("NS", "NS4", "NS" + getVersionNumber(userAgent, position + 9)); } else { result = getArray("NS", "NS?", "NS?" + getVersionNumber(userAgent, position + 9)); } } else if ((position = userAgent.indexOf("Chrome/")) > -1) { result = getArray("KHTML", "KHTML(Chrome)", "KHTML(Chrome" + getVersionNumber(userAgent, position + 6) + ")"); } else if ((position = userAgent.indexOf("Safari/")) > -1) { result = getArray("KHTML", "KHTML(Safari)", "KHTML(Safari" + getVersionNumber(userAgent, position + 6) + ")"); } else if ((position = userAgent.indexOf("Konqueror/")) > -1) { result = getArray("KHTML", "KHTML(Konqueror)", "KHTML(Konqueror" + getVersionNumber(userAgent, position + 9) + ")"); } else if ((position = userAgent.indexOf("KHTML")) > -1) { result = getArray("KHTML", "KHTML?", "KHTML?(" + getVersionNumber(userAgent, position + 5) + ")"); } else if ((position = userAgent.indexOf("NetFront")) > -1) { result = getArray("NetFront", "NetFront", "NetFront " + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("MultiZilla/")) > -1) { result = getArray("MultiZilla", "MultiZilla", "MultiZilla" + getVersionNumber(userAgent, position + 11) + ")"); } else // We will interpret Mozilla/4.x as Netscape Communicator is and only if x // is not 0 or 5 if (userAgent.indexOf("Mozilla/4.") == 0 && !userAgent.contains("Mozilla/4.0") && !userAgent.contains("Mozilla/4.5 ")) { result = getArray("Communicator", "Communicator", "Communicator" + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("Mozilla/")) > -1) { result = getArray("Mozilla", "Mozilla", "Mozilla" + getVersionNumber(userAgent, position + 8)); } else if ((position = userAgent.indexOf("BlackBerry")) > -1) { result = getArray("BlackBerry", "BlackBerry", "BlackBerry" + getVersionNumber(userAgent, position + 10)); } else if ((position = userAgent.indexOf("Avant Browser/")) > -1) { result = getArray("Avant", "Avant", "Avant" + getVersionNumber(userAgent, position + 14)); } else { return getArray("<b>?</b>", "<b>?</b>", "<b>?</b>"); } return result; } }