From 8e608d2439ac8b0ec784820c21f87bc8ead48019 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 17 Oct 2014 15:43:28 +0200 Subject: [PATCH] created small user-agent detection framework to choose the right encoding for basic authentication --- .../main/java/sonia/scm/web/UserAgent.java | 270 ++++++++++++++++++ .../java/sonia/scm/web/UserAgentParser.java | 155 ++++++++++ .../java/sonia/scm/web/UserAgentProvider.java | 55 ++++ .../web/filter/BasicAuthenticationFilter.java | 69 +++-- .../sonia/scm/web/UserAgentParserTest.java | 190 ++++++++++++ .../scm/web/GitBasicAuthenticationFilter.java | 5 +- .../sonia/scm/web/GitUserAgentProvider.java | 96 +++++++ .../scm/web/GitUserAgentProviderTest.java | 83 ++++++ .../scm/web/HgBasicAuthenticationFilter.java | 5 +- .../sonia/scm/web/HgUserAgentProvider.java | 83 ++++++ .../scm/web/HgUserAgentProviderTest.java | 91 ++++++ .../scm/web/SvnBasicAuthenticationFilter.java | 5 +- .../sonia/scm/web/SvnUserAgentProvider.java | 96 +++++++ .../scm/web/SvnUserAgentProviderTest.java | 98 +++++++ .../main/java/sonia/scm/ScmServletModule.java | 4 + .../sonia/scm/plugin/DefaultPluginLoader.java | 9 +- .../scm/web/BrowserUserAgentProvider.java | 128 +++++++++ .../scm/web/BrowserUserAgentProviderTest.java | 114 ++++++++ 18 files changed, 1527 insertions(+), 29 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/web/UserAgent.java create mode 100644 scm-core/src/main/java/sonia/scm/web/UserAgentParser.java create mode 100644 scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java create mode 100644 scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java create mode 100644 scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgUserAgentProviderTest.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java create mode 100644 scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/web/SvnUserAgentProviderTest.java create mode 100644 scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java create mode 100644 scm-webapp/src/test/java/sonia/scm/web/BrowserUserAgentProviderTest.java diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgent.java b/scm-core/src/main/java/sonia/scm/web/UserAgent.java new file mode 100644 index 0000000000..edefc16744 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/UserAgent.java @@ -0,0 +1,270 @@ +/** +* Copyright (c) 2010, Sebastian Sdorra +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* 2. 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. +* 3. Neither the name of SCM-Manager; 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 REGENTS 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. +* +* http://bitbucket.org/sdorra/scm-manager +* +*/ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.base.Objects; + +import static com.google.common.base.Preconditions.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.nio.charset.Charset; + +/** + * + * @author Sebastian Sdorra + * @since 1.42 + */ +public final class UserAgent +{ + + /** + * Constructs ... + * + * + * @param name + * @param browser + * @param basicAuthenticationCharset + */ + private UserAgent(String name, boolean browser, + Charset basicAuthenticationCharset) + { + this.name = checkNotNull(name); + this.browser = browser; + this.basicAuthenticationCharset = checkNotNull(basicAuthenticationCharset); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param name + * + * @return + */ + public static Builder builder(String name) + { + return new Builder(name); + } + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final UserAgent other = (UserAgent) obj; + + return Objects.equal(name, other.name) + && Objects.equal(browser, other.browser) + && Objects.equal(basicAuthenticationCharset, basicAuthenticationCharset); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(name, browser, basicAuthenticationCharset); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("name", name) + .add("browser", browser) + .add("basicAuthenticationCharset", basicAuthenticationCharset) + .toString(); + //J+ + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public Charset getBasicAuthenticationCharset() + { + return basicAuthenticationCharset; + } + + /** + * Method description + * + * + * @return + */ + public String getName() + { + return name; + } + + /** + * Method description + * + * + * @return + */ + public boolean isBrowser() + { + return browser; + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 14/10/15 + * @author Enter your name here... + */ + public static class Builder + { + + /** + * Constructs ... + * + * + * @param name + */ + public Builder(String name) + { + this.name = name; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @param basicAuthenticationCharset + * + * @return + */ + public Builder basicAuthenticationCharset( + Charset basicAuthenticationCharset) + { + this.basicAuthenticationCharset = + checkNotNull(basicAuthenticationCharset); + + return this; + } + + /** + * Method description + * + * + * @param browser + * + * @return + */ + public Builder browser(boolean browser) + { + this.browser = browser; + + return this; + } + + /** + * Method description + * + * + * @return + */ + public UserAgent build() + { + return new UserAgent(name, browser, basicAuthenticationCharset); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private final String name; + + /** Field description */ + private boolean browser = true; + + /** Field description */ + private Charset basicAuthenticationCharset = Charsets.ISO_8859_1; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final Charset basicAuthenticationCharset; + + /** Field description */ + private final boolean browser; + + /** Field description */ + private final String name; +} diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java b/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java new file mode 100644 index 0000000000..2cc1375d58 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java @@ -0,0 +1,155 @@ +/** +* Copyright (c) 2010, Sebastian Sdorra +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* 2. 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. +* 3. Neither the name of SCM-Manager; 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 REGENTS 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. +* +* http://bitbucket.org/sdorra/scm-manager +* +*/ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.util.HttpUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Locale; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public final class UserAgentParser +{ + + /** Field description */ + @VisibleForTesting + static final String CACHE_NAME = "sonia.scm.user-agent"; + + /** Field description */ + @VisibleForTesting + static final UserAgent UNKNOWN = UserAgent.builder("UNKNOWN").build(); + + /** Field description */ + private static final Logger logger = + LoggerFactory.getLogger(UserAgentParser.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param providers + * @param cacheManager + */ + @Inject + public UserAgentParser(Set providers, + CacheManager cacheManager) + { + this.providers = providers; + this.cache = cacheManager.getCache(String.class, UserAgent.class, + CACHE_NAME); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * + * @return + */ + public UserAgent parse(HttpServletRequest request) + { + return parse(request.getHeader(HttpUtil.HEADER_USERAGENT)); + } + + /** + * Method description + * + * + * @param userAgent + * + * @return + */ + public UserAgent parse(String userAgent) + { + String uas = Strings.nullToEmpty(userAgent).toLowerCase(Locale.ENGLISH); + UserAgent ua = cache.get(uas); + + if (ua == null) + { + for (UserAgentProvider provider : providers) + { + ua = provider.parseUserAgent(uas); + + if (ua != null) + { + break; + } + } + + if (ua == null) + { + ua = UNKNOWN; + } + + // cache.put(uas, ua); + } + + logger.trace("return user-agent {} for {}", ua, userAgent); + + return ua; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final Cache cache; + + /** Field description */ + private final Set providers; +} diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java b/scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java new file mode 100644 index 0000000000..f7645dca74 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/UserAgentProvider.java @@ -0,0 +1,55 @@ +/** +* Copyright (c) 2010, Sebastian Sdorra +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* 2. 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. +* 3. Neither the name of SCM-Manager; 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 REGENTS 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. +* +* http://bitbucket.org/sdorra/scm-manager +* +*/ + + + +package sonia.scm.web; + +import sonia.scm.plugin.ExtensionPoint; + +/** + * + * @author Sebastian Sdorra + */ +@ExtensionPoint(multi = true) +public interface UserAgentProvider +{ + + /** + * Method description + * + * + * @param userAgentString + * + * @return + */ + public UserAgent parseUserAgent(String userAgentString); +} diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java index ebafcb8059..2432888273 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java @@ -52,6 +52,7 @@ import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.user.User; import sonia.scm.util.HttpUtil; +import sonia.scm.web.UserAgentParser; import sonia.scm.util.Util; import sonia.scm.web.security.WebSecurityContext; @@ -70,6 +71,7 @@ import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import sonia.scm.web.UserAgent; /** * @@ -92,7 +94,7 @@ public class BasicAuthenticationFilter extends AutoLoginFilter private static final String ATTRIBUTE_FAILED_AUTH = "sonia.scm.auth.failed"; /** default encoding to decode basic authentication header */ - private static final Charset DEFAULT_ENCODING = Charsets.ISO_8859_1; + public static final Charset DEFAULT_ENCODING = Charsets.ISO_8859_1; /** the logger for BasicAuthenticationFilter */ private static final Logger logger = @@ -101,11 +103,13 @@ public class BasicAuthenticationFilter extends AutoLoginFilter //~--- constructors --------------------------------------------------------- /** - * Constructs ... + * Constructs a new basic authenticaton filter * * * @param securityContextProvider - * @deprecated use the constructor with out arguments instead. + * @deprecated use + * {@link #BasicAuthenticationFilter(ScmConfiguration, Set, UserAgentParser) + * instead */ @Deprecated public BasicAuthenticationFilter( @@ -118,21 +122,43 @@ public class BasicAuthenticationFilter extends AutoLoginFilter * @param autoLoginModules auto login modules * * @since 1.21 + * + * @deprecated use + * {@link #BasicAuthenticationFilter(ScmConfiguration, Set, UserAgentParser) + * instead */ - @Inject + @Deprecated public BasicAuthenticationFilter(ScmConfiguration configuration, Set autoLoginModules) + { + this(configuration, autoLoginModules, null); + } + + /** + * Constructs a new basic authentication filter + * + * @param configuration scm-manager global configuration + * @param autoLoginModules auto login modules + * @param userAgentParser parser for user-agent header + * + * @since 1.42 + */ + @Inject + public BasicAuthenticationFilter(ScmConfiguration configuration, + Set autoLoginModules, UserAgentParser userAgentParser) { super(autoLoginModules); this.configuration = configuration; + this.userAgentParser = userAgentParser; } //~--- methods -------------------------------------------------------------- /** * Decode base64 of the basic authentication header. The method will use - * ISO-8859-1 to encode the base64 authentication header. - * + * the charset provided by the {@link UserAgent}, if the + * {@link UserAgentParser} is not available the method will be fall back to + * ISO-8859-1. * * @param request http request * @param authentication base64 encoded basic authentication string @@ -148,9 +174,14 @@ public class BasicAuthenticationFilter extends AutoLoginFilter String authentication) throws UnsupportedEncodingException { - String token = authentication.substring(6); + Charset encoding = DEFAULT_ENCODING; - return new String(Base64.decode(token), DEFAULT_ENCODING); + if (userAgentParser != null) + { + encoding = userAgentParser.parse(request).getBasicAuthenticationCharset(); + } + + return new String(Base64.decode(authentication), encoding); } /** @@ -179,11 +210,7 @@ public class BasicAuthenticationFilter extends AutoLoginFilter if (Util.startWithIgnoreCase(authentication, AUTHORIZATION_BASIC_PREFIX)) { - if (logger.isTraceEnabled()) - { - logger.trace( - "found basic authorization header, start authentication"); - } + logger.trace("found basic authorization header, start authentication"); user = authenticate(request, response, subject, authentication); @@ -202,10 +229,7 @@ public class BasicAuthenticationFilter extends AutoLoginFilter else if ((configuration != null) && configuration.isAnonymousAccessEnabled()) { - if (logger.isTraceEnabled()) - { - logger.trace("anonymous access granted"); - } + logger.trace("anonymous access granted"); user = SCMContext.ANONYMOUS; } @@ -213,10 +237,7 @@ public class BasicAuthenticationFilter extends AutoLoginFilter if (user == null) { - if (logger.isTraceEnabled()) - { - logger.trace("could not find user send unauthorized"); - } + logger.trace("could not find user send unauthorized"); handleUnauthorized(request, response, chain); } @@ -309,7 +330,8 @@ public class BasicAuthenticationFilter extends AutoLoginFilter HttpServletResponse response, Subject subject, String authentication) throws IOException { - String token = decodeAuthenticationHeader(request, authentication); + String token = decodeAuthenticationHeader(request, + authentication.substring(6)); int index = token.indexOf(CREDENTIAL_SEPARATOR); User user = null; @@ -367,4 +389,7 @@ public class BasicAuthenticationFilter extends AutoLoginFilter /** scm main configuration */ protected ScmConfiguration configuration; + + /** Field description */ + protected UserAgentParser userAgentParser; } diff --git a/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java b/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java new file mode 100644 index 0000000000..acd73ca497 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java @@ -0,0 +1,190 @@ +/** +* Copyright (c) 2010, Sebastian Sdorra +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* 2. 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. +* 3. Neither the name of SCM-Manager; 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 REGENTS 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. +* +* http://bitbucket.org/sdorra/scm-manager +* +*/ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.util.HttpUtil; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class UserAgentParserTest +{ + + /** Field description */ + private static final String UA_1 = "mozilla/5.0"; + + /** Field description */ + private static final String UA_2 = "wget/1.5.3"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void prepare() + { + Set providers = Sets.newHashSet(provider1, provider2); + + when(cacheManager.getCache(String.class, UserAgent.class, + UserAgentParser.CACHE_NAME)).thenReturn(cache); + parser = new UserAgentParser(providers, cacheManager); + } + + /** + * Method description + * + */ + @Test + public void testDefaultValues() + { + UserAgent ua = parser.parse(UA_1); + + assertEquals(Charsets.ISO_8859_1, ua.getBasicAuthenticationCharset()); + assertTrue(ua.isBrowser()); + } + + /** + * Method description + * + */ + @Test + public void testParse() + { + UserAgent ua = UserAgent.builder("UA1").build(); + + when(provider1.parseUserAgent(UA_1)).thenReturn(ua); + + UserAgent ua2 = UserAgent.builder("UA2").build(); + + when(provider2.parseUserAgent(UA_2)).thenReturn(ua2); + + assertEquals(ua, parser.parse(UA_1)); + assertEquals(ua2, parser.parse(UA_2)); + } + + /** + * Method description + * + */ + @Test + public void testParseHttpServletRequest() + { + when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(UA_2); + + UserAgent ua = UserAgent.builder("UA2").build(); + + when(provider1.parseUserAgent(UA_2)).thenReturn(ua); + assertEquals(ua, parser.parse(request)); + } + + /** + * Method description + * + */ + @Test + public void testParseNotFound() + { + assertEquals(UserAgentParser.UNKNOWN, parser.parse(UA_1)); + assertEquals(UserAgentParser.UNKNOWN, parser.parse(UA_2)); + } + + /** + * Method description + * + */ + @Test + public void testParseWithCache() + { + UserAgent ua = UserAgent.builder("UA").build(); + + when(cache.get(UA_1)).thenReturn(ua); + assertEquals(ua, parser.parse(UA_1)); + assertEquals(UserAgentParser.UNKNOWN, parser.parse(UA_2)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Mock + private Cache cache; + + /** Field description */ + @Mock + private CacheManager cacheManager; + + /** Field description */ + private UserAgentParser parser; + + /** Field description */ + @Mock + private UserAgentProvider provider1; + + /** Field description */ + @Mock + private UserAgentProvider provider2; + + /** Field description */ + @Mock + private HttpServletRequest request; +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java index 35047fe1f6..b15f75f297 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitBasicAuthenticationFilter.java @@ -67,12 +67,13 @@ public class GitBasicAuthenticationFilter extends BasicAuthenticationFilter * * @param configuration * @param autoLoginModules + * @param userAgentParser */ @Inject public GitBasicAuthenticationFilter(ScmConfiguration configuration, - Set autoLoginModules) + Set autoLoginModules, UserAgentParser userAgentParser) { - super(configuration, autoLoginModules); + super(configuration, autoLoginModules, userAgentParser); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java new file mode 100644 index 0000000000..5960951f55 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; + +import sonia.scm.plugin.ext.Extension; + +/** + * + * @author Sebastian Sdorra + */ +@Extension +public class GitUserAgentProvider implements UserAgentProvider +{ + + /** Field description */ + @VisibleForTesting + static final UserAgent GIT = UserAgent.builder("Git").browser( + false).basicAuthenticationCharset( + Charsets.UTF_8).build(); + + /** Field description */ + @VisibleForTesting + static final UserAgent MSYSGIT = UserAgent.builder("msysGit").browser( + false).basicAuthenticationCharset( + Charsets.UTF_8).build(); + + /** Field description */ + private static final String PREFIX = "git/"; + + /** Field description */ + private static final String SUFFIX = "msysgit"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param userAgentString + * + * @return + */ + @Override + public UserAgent parseUserAgent(String userAgentString) + { + UserAgent ua = null; + + if (userAgentString.startsWith(PREFIX)) + { + if (userAgentString.contains(SUFFIX)) + { + ua = MSYSGIT; + } + else + { + ua = GIT; + } + } + + return ua; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java new file mode 100644 index 0000000000..b16580c4a5 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Strings; + +import org.junit.Test; + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Locale; + +/** + * + * @author Sebastian Sdorra + */ +public class GitUserAgentProviderTest +{ + + /** + * Method description + * + */ + @Test + public void testParseUserAgent() + { + assertEquals(GitUserAgentProvider.GIT, parse("git/1.7.9.5")); + assertEquals(GitUserAgentProvider.MSYSGIT, parse("git/1.8.3.msysgit.0")); + assertNull(parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36")); + } + + /** + * Method description + * + * + * @param v + * + * @return + */ + private UserAgent parse(String v) + { + return provider.parseUserAgent( + Strings.nullToEmpty(v).toLowerCase(Locale.ENGLISH)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final GitUserAgentProvider provider = new GitUserAgentProvider(); +} diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java index 6d3c819e13..744a82bace 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgBasicAuthenticationFilter.java @@ -63,12 +63,13 @@ public class HgBasicAuthenticationFilter extends BasicAuthenticationFilter * * @param configuration * @param autoLoginModules + * @param userAgentParser */ @Inject public HgBasicAuthenticationFilter(ScmConfiguration configuration, - Set autoLoginModules) + Set autoLoginModules, UserAgentParser userAgentParser) { - super(configuration, autoLoginModules); + super(configuration, autoLoginModules, userAgentParser); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java new file mode 100644 index 0000000000..e9b9ef2deb --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; + +import sonia.scm.plugin.ext.Extension; + +//~--- JDK imports ------------------------------------------------------------ + +import java.nio.charset.Charset; + +/** + * + * @author Sebastian Sdorra + */ +@Extension +public class HgUserAgentProvider implements UserAgentProvider +{ + + /** mercurial seems to use system encoding */ + @VisibleForTesting + static UserAgent HG = UserAgent.builder("Mercurial").browser( + false).basicAuthenticationCharset( + Charset.defaultCharset()).build(); + + /** Field description */ + private static final String PREFIX = "mercurial"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param userAgentString + * + * @return + */ + @Override + public UserAgent parseUserAgent(String userAgentString) + { + UserAgent ua = null; + + if (userAgentString.startsWith(PREFIX)) + { + ua = HG; + } + + return ua; + } +} diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgUserAgentProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgUserAgentProviderTest.java new file mode 100644 index 0000000000..0a3e1cd03c --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgUserAgentProviderTest.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Strings; + +import org.junit.Test; + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Locale; + +/** + * + * @author Sebastian Sdorra + */ +public class HgUserAgentProviderTest +{ + + /** Field description */ + private static final String UA_1 = "mercurial/proto-1.0"; + + /** Field description */ + private static final String UA_2 = + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Test + public void testParseUserAgent() + { + assertEquals(HgUserAgentProvider.HG, parse(UA_1)); + assertNull(parse(UA_2)); + } + + /** + * Method description + * + * + * @param v + * + * @return + */ + private UserAgent parse(String v) + { + return provider.parseUserAgent( + Strings.nullToEmpty(v).toLowerCase(Locale.ENGLISH)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final HgUserAgentProvider provider = new HgUserAgentProvider(); +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java index b94b6b5d56..67d1306719 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnBasicAuthenticationFilter.java @@ -65,12 +65,13 @@ public class SvnBasicAuthenticationFilter extends BasicAuthenticationFilter * * @param configuration * @param autoLoginModules + * @param userAgentParser */ @Inject public SvnBasicAuthenticationFilter(ScmConfiguration configuration, - Set autoLoginModules) + Set autoLoginModules, UserAgentParser userAgentParser) { - super(configuration, autoLoginModules); + super(configuration, autoLoginModules, userAgentParser); } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java new file mode 100644 index 0000000000..1d34d000a2 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; + +import sonia.scm.plugin.ext.Extension; + +/** + * + * @author Sebastian Sdorra + */ +@Extension +public final class SvnUserAgentProvider implements UserAgentProvider +{ + + /** ua prefix */ + private static final String PREFIX = "svn"; + + /** ua suffix */ + private static final String SUFFIX = "tortoisesvn"; + + /** TortoiseSVN */ + @VisibleForTesting + static final UserAgent TORTOISE_SVN = + UserAgent.builder("TortoiseSVN").browser(false) + .basicAuthenticationCharset(Charsets.UTF_8).build(); + + /** Subversion cli client */ + @VisibleForTesting + static final UserAgent SVN = + UserAgent.builder("Subversion").browser(false) + .basicAuthenticationCharset(Charsets.UTF_8).build(); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param userAgentString + * + * @return + */ + @Override + public UserAgent parseUserAgent(String userAgentString) + { + UserAgent ua = null; + + if (userAgentString.startsWith(PREFIX)) + { + if (userAgentString.contains(SUFFIX)) + { + ua = TORTOISE_SVN; + } + else + { + ua = SVN; + } + } + + return ua; + } +} diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/web/SvnUserAgentProviderTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/web/SvnUserAgentProviderTest.java new file mode 100644 index 0000000000..2604079e64 --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/web/SvnUserAgentProviderTest.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Test; + + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Locale; + +/** + * + * @author Sebastian Sdorra + */ +public class SvnUserAgentProviderTest +{ + + /** Field description */ + private static final String UA_1 = + "SVN/1.8.8 (x64-microsoft-windows) serf/1.3.4 TortoiseSVN-1.8.6.25419"; + + /** Field description */ + private static final String UA_2 = "SVN/1.5.4 (r33841) neon/0.28.3"; + + /** Field description */ + private static final String UA_3 = "SVN/1.6.3 (r38063) neon/0.28.4"; + + /** Field description */ + private static final String UA_4 = + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0;Google Wireless Transcoder;)"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Test + public void testParseUserAgent() + { + assertEquals(SvnUserAgentProvider.TORTOISE_SVN, parse(UA_1)); + assertEquals(SvnUserAgentProvider.SVN, parse(UA_2)); + assertEquals(SvnUserAgentProvider.SVN, parse(UA_3)); + assertNull(parse(UA_4)); + } + + /** + * Method description + * + * + * @param ua + * + * @return + */ + private UserAgent parse(String ua) + { + return suap.parseUserAgent(ua.toLowerCase(Locale.ENGLISH)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final SvnUserAgentProvider suap = new SvnUserAgentProvider(); +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 0f7fbbf822..30e8a2e1e8 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -156,6 +156,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import sonia.scm.web.UserAgentParser; /** * @@ -341,6 +342,9 @@ public class ScmServletModule extends ServletModule // bind new hook api bind(HookContextFactory.class); bind(HookEventFacade.class); + + // bind user-agent parser + bind(UserAgentParser.class); // bind debug logging filter if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP))) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java index 2d8e3fbc07..e28c4d8584 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginLoader.java @@ -79,6 +79,7 @@ import javax.servlet.ServletContext; import javax.servlet.ServletContextListener; import javax.xml.bind.JAXB; +import sonia.scm.web.BrowserUserAgentProvider; /** * @@ -575,7 +576,13 @@ public class DefaultPluginLoader implements PluginLoader new AnnotatedClass( Extensions.createExtension(), DefaultAuthenticationHandler.class - ) + ) + ); + extensions.add( + new AnnotatedClass( + Extensions.createExtension(), + BrowserUserAgentProvider.class + ) ); //J+ } diff --git a/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java b/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java new file mode 100644 index 0000000000..48a13e9760 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of SCM-Manager; 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; + +import sonia.scm.plugin.ext.Extension; + +/** + * + * @author Sebastian Sdorra + */ +@Extension +public class BrowserUserAgentProvider implements UserAgentProvider +{ + + /** Field description */ + @VisibleForTesting + static final UserAgent CHROME = UserAgent.builder( + "Chrome").basicAuthenticationCharset( + Charsets.UTF_8).build(); + + /** Field description */ + private static final String CHROME_PATTERN = "chrome"; + + /** Field description */ + @VisibleForTesting + static final UserAgent FIREFOX = UserAgent.builder("Firefox").build(); + + /** Field description */ + private static final String FIREFOX_PATTERN = "firefox"; + + /** Field description */ + @VisibleForTesting + static final UserAgent MSIE = UserAgent.builder("Internet Explorer").build(); + + /** Field description */ + private static final String MSIE_PATTERN = "msie"; + + /** Field description */ + @VisibleForTesting // todo check charset + static final UserAgent SAFARI = UserAgent.builder("Safari").build(); + + /** Field description */ + private static final String OPERA_PATTERN = "opera"; + + /** Field description */ + private static final String SAFARI_PATTERN = "safari"; + + /** Field description */ + @VisibleForTesting // todo check charset + static final UserAgent OPERA = UserAgent.builder( + "Opera").basicAuthenticationCharset( + Charsets.UTF_8).build(); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param userAgentString + * + * @return + */ + @Override + public UserAgent parseUserAgent(String userAgentString) + { + UserAgent ua = null; + + if (userAgentString.contains(CHROME_PATTERN)) + { + ua = CHROME; + } + else if (userAgentString.contains(FIREFOX_PATTERN)) + { + ua = FIREFOX; + } + else if (userAgentString.contains(OPERA_PATTERN)) + { + ua = OPERA; + } + else if (userAgentString.contains(MSIE_PATTERN)) + { + ua = MSIE; + } + else if (userAgentString.contains(SAFARI_PATTERN)) + { + ua = SAFARI; + } + + return ua; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/web/BrowserUserAgentProviderTest.java b/scm-webapp/src/test/java/sonia/scm/web/BrowserUserAgentProviderTest.java new file mode 100644 index 0000000000..c01b76a313 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/web/BrowserUserAgentProviderTest.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of SCM-Manager; + * 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 REGENTS 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. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Strings; + +import org.junit.Test; + + +import static org.junit.Assert.*; + + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Locale; + +/** + * + * @author Sebastian Sdorra + */ +public class BrowserUserAgentProviderTest +{ + + /** Field description */ + private static final String CHROME = + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36"; + + /** Field description */ + private static final String FIREFOX = + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.8.1.18) Gecko/20081029 Firefox/2.0.0.18"; + + /** Field description */ + private static final String MSIE = + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; )"; + + /** Field description */ + private static final String OPERA = + "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.00"; + + /** Field description */ + private static final String SAFARI = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/6.1.3 Safari/537.75.14"; + + /** Field description */ + private static final String WGET = "Wget/1.5.3"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Test + public void testParseUserAgent() + { + assertEquals(BrowserUserAgentProvider.MSIE, parse(MSIE)); + assertEquals(BrowserUserAgentProvider.FIREFOX, parse(FIREFOX)); + assertEquals(BrowserUserAgentProvider.OPERA, parse(OPERA)); + assertEquals(BrowserUserAgentProvider.CHROME, parse(CHROME)); + assertEquals(BrowserUserAgentProvider.SAFARI, parse(SAFARI)); + assertNull(parse(WGET)); + } + + /** + * Method description + * + * + * @param v + * + * @return + */ + private UserAgent parse(String v) + { + return provider.parseUserAgent( + Strings.nullToEmpty(v).toLowerCase(Locale.ENGLISH)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final BrowserUserAgentProvider provider = + new BrowserUserAgentProvider(); +}