All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.velocity.tools.view.UAParser Maven / Gradle / Ivy

The newest version!
package org.apache.velocity.tools.view;

import org.apache.velocity.exception.VelocityException;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.velocity.tools.ClassUtils;

import org.slf4j.Logger;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class UAParser
{
    public UAParser()
    {
    }

    public static class UAEntity implements Serializable
    {
        private static final long serialVersionUID = 5148646199164619995L;

        private String name = null;
        private int majorVersion = -1;
        private int minorVersion = -1;

        public UAEntity(String n, String maj, String min)
        {
            this(n, maj, min, false);
        }

        public UAEntity(String n, String maj, String min, boolean wantsFullVersion)
        {
            name = n;
            try
            {
                majorVersion = maj == null ? -1 : Integer.valueOf(maj);
                try
                {
                    minorVersion = maj == null ? -1 : Integer.valueOf(min);
                }
                catch (NumberFormatException nfe)
                {
                    minorVersion = -1;
                    if (wantsFullVersion)
                    {
                        majorVersion = -1;
                    }
                }
            }
            catch (NumberFormatException nfe)
            {
                majorVersion = minorVersion = -1;
            }
        }

        public String getName()
        {
            return name;
        }

        public int getMajorVersion()
        {
            return majorVersion;
        }

        public int getMinorVersion()
        {
            return minorVersion;
        }
    }

    /* device, in order of growing precedence */
    public enum DeviceType
    {
        UNKNOWN,
        DESKTOP,
        MOBILE,
        TABLET,
        TV,
        ROBOT
    };


    private static Map browserTranslationMap = null;
    private static Map osTranslationMap = null;
    static
    {
        browserTranslationMap = new HashMap();
        browserTranslationMap.put("navigator","Netscape");
        browserTranslationMap.put("nokia5250", "Nokia Browser");

        osTranslationMap = new HashMap();
        osTranslationMap.put("android", "Android");
        osTranslationMap.put("bada", "Bada");
        osTranslationMap.put("bb10", "BlackBerry");
        osTranslationMap.put("blackberry", "BlackBerry");
        osTranslationMap.put("cros", "Chrome OS");
        osTranslationMap.put("fxos", "Firefox OS");
        osTranslationMap.put("hpwos", "WebOS");
        osTranslationMap.put("ipad", "iOS");
        osTranslationMap.put("iphone", "iOS");
        osTranslationMap.put("ipod", "iOS");
        osTranslationMap.put("kfthwi", "Kindle");
        osTranslationMap.put("kftt", "Kindle");
        osTranslationMap.put("mac os x", "OS X");
        osTranslationMap.put("macos x", "OS X");
        osTranslationMap.put("nokiae", "Nokia");
        osTranslationMap.put("nokiax2", "Nokia");
        osTranslationMap.put("remi", "Fedora");
        osTranslationMap.put("rhel", "Red Hat");
        osTranslationMap.put("series40", "Symbian");
        osTranslationMap.put("series60", "Symbian");
        osTranslationMap.put("series80", "Symbian");
        osTranslationMap.put("series90", "Symbian");
        osTranslationMap.put("series 40", "Symbian");
        osTranslationMap.put("series 60", "Symbian");
        osTranslationMap.put("series 80", "Symbian");
        osTranslationMap.put("series 90", "Symbian");
        osTranslationMap.put("symbianos", "Symbian");
        osTranslationMap.put("symbos", "Symbian");
        osTranslationMap.put("tigeros", "OS X");
        osTranslationMap.put("tizen", "Tizen");
        osTranslationMap.put("tt", "Android");
        osTranslationMap.put("unix", "Unix");
        osTranslationMap.put("unix bsd", "BSD");
        osTranslationMap.put("unixware", "Unix");
        osTranslationMap.put("webos", "WebOS");
        osTranslationMap.put("windows nt", "Windows");
        osTranslationMap.put("win98", "Windows");
    }

    public static class UserAgent implements Serializable
    {
        private static final long serialVersionUID = 2295189396414933725L;

        private DeviceType deviceType = null;
        private UAEntity operatingSystem = null;
        private UAEntity browser = null;
        private UAEntity renderingEngine = null;
        private boolean isRobot = false;

        public DeviceType getDeviceType() { return deviceType; }
        public UAEntity getOperatingSystem() { return operatingSystem; }
        public UAEntity getBrowser() { return browser; }
        public UAEntity getRenderingEngine() { return renderingEngine; }

        protected void setOperatingSystem(String entity, String major, String minor)
        {
            if (entity.equals("Series") && major != null)
            {
                entity += major;
                major = minor = null;
            }
            String alternate = osTranslationMap.get(entity.toLowerCase());
            if (alternate != null) entity = alternate;
            if (entity.startsWith("BlackBerry")) { entity = "BlackBerry"; }
            operatingSystem = new UAEntity(entity, major, minor);
        }

        protected void setBrowser(String entity, String major, String minor)
        {
            String alternate = browserTranslationMap.get(entity.toLowerCase());
            if (alternate != null) { entity = alternate; }
            if ("Navigator".equals(entity)) { entity = "Netscape"; }
            browser = new UAEntity(entity, major, minor, true);
            if ("Edge".equals(entity) && renderingEngine == null) { renderingEngine = new UAEntity("EdgeHTML", major, minor); }
        }

        protected void setRenderingEngine(String entity, String major, String minor)
        {
            if (deviceType == DeviceType.ROBOT) return;
            renderingEngine = new UAEntity(entity, major, minor);
        }

        protected void setDeviceType(DeviceType deviceType)
        {
            this.deviceType = deviceType;
        }
    }

    private enum EntityType
    {
        BROWSER,
        BROWSER_OS,
        ENGINE,
        FORCE_BROWSER,
        FORCE_OS,
        IGNORE,
        MAYBE_BROWSER,
        MAYBE_OS,
        MAYBE_ROBOT,
        MERGE,
        MERGE_OR_BROWSER,
        MERGE_OR_OS,
        OS,
        ROBOT
    };

    private static final String UA_KEYWORDS = "/org/apache/velocity/tools/view/ua-keywords.txt";

    private static Map> entityMap = new HashMap>();

    static
    {
        try
        {
            Properties properties = new Properties();
            InputStream stream = ClassUtils.getResourceAsStream(UA_KEYWORDS, BrowserTool.class);
            if (stream == null) { throw new IOException("could not find org.apache.velocity.tools.view.ua-keywords.txt resource"); }
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
            String line = null;
            int num = 1;
            while ((line = reader.readLine()) != null)
            {
                line = line.trim();
                if (line.length() == 0 || line.startsWith("#")) { ++num; continue; }
                int eq = line.indexOf('=');
                if (eq == -1) { throw new IOException("invalid line format in ua-keywords.txt at line " + num); }
                String key = line.substring(0, eq);
                String val = line.substring(eq + 1).toUpperCase();
                int coma = val.indexOf(',');
                DeviceType device = null;
                if (coma != -1)
                {
                    device = DeviceType.valueOf(val.substring(coma + 1));
                    val = val.substring(0, coma);
                }
                EntityType entity = val.length() > 0 ? EntityType.valueOf(val) : null;
                entityMap.put(key, new ImmutablePair(entity , device));
                ++num;
            }
        }
        catch(Exception e)
        {
            throw new VelocityException("BrowserTool: static initialization failed", e);
        }
    }

    private static final String nonMergeSep = "(;/)";

    private static Pattern versionPattern = Pattern.compile(
            /* entity name */
            "([a-z]+(?:(?=[;()@]|$)|(?:[0-9]+(?!\\.)[a-z]*)|(?:[!_+.\\-][a-z]+)+|(?=[/ ,\\-:0-9+!_=])))" +
            /* potential version */
                    "(?:([/ ,\\-:+_=])?(?:v?(\\d+)(?:\\.(\\d+))?[a-z+]*)?)",
            Pattern.CASE_INSENSITIVE);

    private static boolean isRobotToken(String token)
    {
        token = token.toLowerCase();
        return token.endsWith("bot") || token.endsWith("crawler") || token.endsWith("spider") || token.endsWith("agent") || token.endsWith("validator");
    }

    /* the big hairy parsing method */
    public static UserAgent parseUserAgent(String userAgentString, Logger log)
    {
        UserAgent ua = null;
        try
        {
            ua = new UserAgent();

            Matcher matcher = versionPattern.matcher(userAgentString);
            String merge = null;
            EntityType mergeTarget = null;
            boolean maybeBrowser = true;
            boolean maybeOS = true;
            boolean maybeRobot = false;
            boolean forcedBrowser = false;
            boolean forcedOS = false;

            while (matcher.find())
            {
                String entity = matcher.group(1);
                String separator = matcher.group(2);
                String major = matcher.group(3);
                String minor = matcher.group(4);
                char next = userAgentString.length() == matcher.end(1) ? ';' : userAgentString.charAt(matcher.end(1));
                if (entity != null)
                {
                    if (merge != null)
                    {
                        String merged = merge + " " + entity;
                        if (mergeTarget == null)
                        {
                            entity = merged;
                        }
                        else
                        {
                            Pair pair = entityMap.get(merged.toLowerCase());
                            EntityType mergedType = pair == null ? null : pair.getLeft();
                            if (mergedType != null && (
                                    mergeTarget == mergedType ||
                                            mergeTarget == EntityType.BROWSER && (mergedType == EntityType.MAYBE_BROWSER || mergedType == EntityType.FORCE_BROWSER) ||
                                            mergeTarget == EntityType.OS && (mergedType == EntityType.MAYBE_OS || mergedType == EntityType.FORCE_OS)
                            ))
                            {
                                entity = merged;
                            }
                            else
                            {
                                /* It means the merge failed, so revert it */
                                switch (mergeTarget)
                                {
                                    case BROWSER:
                                        ua.setBrowser(merge, null, null);
                                        break;
                                    case OS:
                                        ua.setOperatingSystem(merge, null, null);
                                        break;
                                    default:
                                        throw new VelocityException("BrowserTool: unhandled case!");
                                }
                            }
                        }
                        merge = null;
                        mergeTarget = null;
                    }
                    Pair identity = entityMap.get(entity.toLowerCase());
                    EntityType entityType = null;
                    DeviceType deviceType = null;
                    if (identity == null)
                    {
                        /* try again with major version appended */
                        String alternateEntity = entity + separator + major;
                        identity = entityMap.get((entity + separator + major).toLowerCase());
                        if (identity != null)
                        {
                            entity = alternateEntity;
                        }
                    }
                    if (identity != null)
                    {
                        entityType = identity.getLeft();
                        deviceType = identity.getRight();
                        DeviceType previousDeviceType = ua.getDeviceType();
                        /* only overwrite device types of lower precedence */
                        if (deviceType != null && (previousDeviceType == null || deviceType.compareTo(previousDeviceType) > 0))
                        {
                            ua.setDeviceType(deviceType); // may be overwritten by 'robot' device type
                        }
                    }
                    if (entityType != null)
                    {
                        switch (entityType)
                        {
                            case BROWSER:
                            {
                                if (ua.getBrowser() == null || !forcedBrowser)
                                {
                                    ua.setBrowser(entity, major, minor);
                                    maybeBrowser = false;
                                }
                                break;
                            }
                            case BROWSER_OS:
                            {
                                ua.setBrowser(entity, major, minor);
                                maybeBrowser = false;
                                ua.setOperatingSystem(entity, major, minor);
                                maybeOS = false;
                                break;
                            }
                            case ENGINE:
                            {
                                if (!"KHTML".equals(entity) || major != null || ua.getRenderingEngine() == null)
                                {
                                    ua.setRenderingEngine(entity, major, minor);
                                }
                                break;
                            }
                            case FORCE_BROWSER:
                            {
                                if (!forcedBrowser)
                                {
                                    ua.setBrowser(entity, major, minor);
                                    maybeBrowser = false;
                                    forcedBrowser = true;
                                }
                                break;
                            }
                            case FORCE_OS:
                            {
                                if (!forcedOS)
                                {
                                    ua.setOperatingSystem(entity, major, minor);
                                    maybeOS = false;
                                    forcedOS = true;
                                }
                                break;
                            }
                            case IGNORE:
                            {
                                break;
                            }
                            case MAYBE_BROWSER:
                            {
                                if (maybeBrowser)
                                {
                                    if ("rv".equals(entity))
                                    {
                                        if (ua.getBrowser() != null && ua.getBrowser().getName().equals("Mozilla"))
                                        {
                                            entity = "Mozilla";
                                        } else
                                        {
                                            entity = null;
                                        }
                                    } else if ("Version".equals(entity))
                                    {
                                        if (ua.getBrowser() != null && ua.getBrowser().getName().startsWith("Opera"))
                                        {
                                            entity = ua.getBrowser().getName();
                                        } else if (ua.getBrowser() != null && ua.getBrowser().getName().equals("Mozilla"))
                                        {
                                            entity = "Safari";
                                        } else
                                        {
                                            entity = null;
                                        }
                                    } else if ("Safari".equals(entity) && ua.getBrowser() != null && "Safari".equals(ua.getBrowser().getName()))
                                    {
                                        entity = null;
                                    }
                                    if (entity != null)
                                    {
                                        ua.setBrowser(entity, major, minor);
                                    }
                                }
                                break;
                            }
                            case MAYBE_OS:
                            {
                                if (maybeOS)
                                {
                                    ua.setOperatingSystem(entity, major, minor);
                                }
                                break;
                            }
                            case MAYBE_ROBOT:
                            {
                                maybeRobot = true;
                                break;
                            }
                            case MERGE:
                            {
                                if (major == null)
                                {
                                    if (nonMergeSep.indexOf(next) == -1)
                                    {
                                        merge = merge == null ? entity : merge + " " + entity;
                                    } else
                                    {
                                        if ("Mobile".equals(entity) && ua.getOperatingSystem() != null)
                                        {
                                            if (ua.getOperatingSystem().getName().equals("Ubuntu"))
                                            {
                                                ua.setOperatingSystem("Ubuntu Mobile", String.valueOf(ua.getOperatingSystem().getMajorVersion()), String.valueOf(ua.getOperatingSystem().getMinorVersion()));
                                            } else if (ua.getOperatingSystem().getName().equals("Linux"))
                                            {
                                                ua.setOperatingSystem("Android", null, null);
                                            }
                                        }
                                    }
                                }
                                break;
                            }
                            case MERGE_OR_BROWSER:
                            {
                                if (!forcedBrowser)
                                {
                                    if (major != null || nonMergeSep.indexOf(next) != -1)
                                    {
                                        ua.setBrowser(entity, major, minor);
                                    } else
                                    {
                                        merge = entity;
                                        mergeTarget = EntityType.BROWSER;
                                    }
                                }
                                break;
                            }
                            case MERGE_OR_OS:
                            {
                                if (!forcedOS)
                                {
                                    if (major != null || nonMergeSep.indexOf(next) != -1)
                                    {
                                        ua.setOperatingSystem(entity, major, minor);
                                    } else
                                    {
                                        merge = entity;
                                        mergeTarget = EntityType.OS;
                                    }
                                }
                                break;
                            }
                            case OS:
                            {
                                if (ua.getOperatingSystem() == null || !forcedOS)
                                {
                                    ua.setOperatingSystem(entity, major, minor);
                                    maybeOS = false;
                                }
                                break;
                            }
                            case ROBOT:
                            {
                                ua.setDeviceType(DeviceType.ROBOT);
                                break;
                            }
                            default:
                            {
                                throw new VelocityException("BrowserTool: unhandled case: " + entityType);
                            }
                        }
                    }
                    else
                    {
                        if (entity.startsWith("Linux") && !forcedOS)
                        {
                            ua.setOperatingSystem("Linux", null, null);
                        }
                        else if (isRobotToken(entity))
                        {
                            ua.setDeviceType(DeviceType.ROBOT);
                        }
                        else if (entity.startsWith("MID") && !entity.startsWith("MIDP") && (ua.getDeviceType() == null || DeviceType.TABLET.compareTo(ua.getDeviceType()) > 0 ))
                        {
                            ua.setDeviceType(DeviceType.TABLET);
                        }
                        else if (entity.startsWith("CoolPad") && (ua.getDeviceType() == null || DeviceType.MOBILE.compareTo(ua.getDeviceType()) > 0 ))
                        {
                            ua.setDeviceType(DeviceType.MOBILE);
                        }
                        else if (entity.startsWith("LG-") && (ua.getDeviceType() == null || DeviceType.MOBILE.compareTo(ua.getDeviceType()) > 0 ))
                        {
                            ua.setDeviceType(DeviceType.MOBILE);
                        }
                        else if (entity.startsWith("SonyEricsson"))
                        {
                            ua.setDeviceType(DeviceType.MOBILE);
                        }
                    }
                }
            }
            if (ua.getOperatingSystem() != null && "Windows".equals(ua.getOperatingSystem().getName()) && (ua.getOperatingSystem().getMajorVersion() == 98 || ua.getOperatingSystem().getMajorVersion() == 2000))
            {
                if (ua.getOperatingSystem().getMajorVersion() == 98)
                {
                    ua.setOperatingSystem("Windows 98", "4", "90");
                }
                else if (ua.getOperatingSystem().getMajorVersion() == 2000)
                {
                    ua.setOperatingSystem("Windows 2000", "5", "0");
                }
            }
            if (ua.getBrowser() == null)
            {
                if (ua.getDeviceType() == DeviceType.ROBOT || maybeRobot)
                {
                    ua.setBrowser("robot", "0", "0");
                    ua.setDeviceType(DeviceType.ROBOT);
                }
                else if (ua.getOperatingSystem() != null && ua.getOperatingSystem().getName().equals("Symbian"))
                {
                    ua.setBrowser("Nokia Browser", String.valueOf(ua.getOperatingSystem().getMajorVersion()), String.valueOf(ua.getOperatingSystem().getMinorVersion()));
                }
                else
                {
                    ua.setBrowser("unknown", "0", "0");
                }
            }
            if (ua.getOperatingSystem() == null)
            {
                if (ua.getDeviceType() == DeviceType.ROBOT || maybeRobot)
                {
                    ua.setOperatingSystem("robot", "0", "0");
                    ua.setDeviceType(DeviceType.ROBOT);
                }
                else
                {
                    ua.setOperatingSystem("unknown", "0", "0");
                }
            }
            if (ua.getDeviceType() == null)
            {
                if (ua.getOperatingSystem() != null && "Android".equals(ua.getOperatingSystem().getName()))
                {
                    ua.setDeviceType(DeviceType.MOBILE);
                }
                else
                {
                    /* make Desktop the default device */
                    ua.setDeviceType(DeviceType.DESKTOP);
                }
            }
        }
        catch (Exception e)
        {
            log.error("Could not parse browser for User-Agent: {}", userAgentString, e);
            ua = null;
        }
        return ua;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy