11c6c73c5SShawn O. Pearce /* 2*5c5f7c6bSMatthias Sohn * Copyright (C) 2012, Google Inc. and others 31c6c73c5SShawn O. Pearce * 4*5c5f7c6bSMatthias Sohn * This program and the accompanying materials are made available under the 5*5c5f7c6bSMatthias Sohn * terms of the Eclipse Distribution License v. 1.0 which is available at 6*5c5f7c6bSMatthias Sohn * https://www.eclipse.org/org/documents/edl-v10.php. 71c6c73c5SShawn O. Pearce * 8*5c5f7c6bSMatthias Sohn * SPDX-License-Identifier: BSD-3-Clause 91c6c73c5SShawn O. Pearce */ 101c6c73c5SShawn O. Pearce 111c6c73c5SShawn O. Pearce package org.eclipse.jgit.http.server; 121c6c73c5SShawn O. Pearce 131c6c73c5SShawn O. Pearce import javax.servlet.http.HttpServletRequest; 141c6c73c5SShawn O. Pearce 153b00041cSMatthias Sohn /** 163b00041cSMatthias Sohn * Parses Git client User-Agent strings. 173b00041cSMatthias Sohn */ 181c6c73c5SShawn O. Pearce public class ClientVersionUtil { 193b00041cSMatthias Sohn /** 203b00041cSMatthias Sohn * An invalid version of Git 213b00041cSMatthias Sohn * 223b00041cSMatthias Sohn * @return maximum version array, indicating an invalid version of Git. 233b00041cSMatthias Sohn */ invalidVersion()241c6c73c5SShawn O. Pearce public static int[] invalidVersion() { 251c6c73c5SShawn O. Pearce return new int[] { Integer.MAX_VALUE }; 261c6c73c5SShawn O. Pearce } 271c6c73c5SShawn O. Pearce 281c6c73c5SShawn O. Pearce /** 291c6c73c5SShawn O. Pearce * Parse a Git client User-Agent header value. 301c6c73c5SShawn O. Pearce * 311c6c73c5SShawn O. Pearce * @param version 321c6c73c5SShawn O. Pearce * git client version string, of the form "git/1.7.9". 331c6c73c5SShawn O. Pearce * @return components of the version string. {@link #invalidVersion()} if 341c6c73c5SShawn O. Pearce * the version string cannot be parsed. 351c6c73c5SShawn O. Pearce */ parseVersion(String version)361c6c73c5SShawn O. Pearce public static int[] parseVersion(String version) { 371c6c73c5SShawn O. Pearce if (version != null && version.startsWith("git/")) 381c6c73c5SShawn O. Pearce return splitVersion(version.substring("git/".length())); 391c6c73c5SShawn O. Pearce return invalidVersion(); 401c6c73c5SShawn O. Pearce } 411c6c73c5SShawn O. Pearce splitVersion(String versionString)421c6c73c5SShawn O. Pearce private static int[] splitVersion(String versionString) { 431c6c73c5SShawn O. Pearce char[] str = versionString.toCharArray(); 441c6c73c5SShawn O. Pearce int[] ver = new int[4]; 451c6c73c5SShawn O. Pearce int end = 0; 461c6c73c5SShawn O. Pearce int acc = 0; 471c6c73c5SShawn O. Pearce for (int i = 0; i < str.length; i++) { 481c6c73c5SShawn O. Pearce char c = str[i]; 491c6c73c5SShawn O. Pearce if ('0' <= c && c <= '9') { 501c6c73c5SShawn O. Pearce acc *= 10; 511c6c73c5SShawn O. Pearce acc += c - '0'; 521c6c73c5SShawn O. Pearce } else if (c == '.') { 531c6c73c5SShawn O. Pearce if (end == ver.length) 541c6c73c5SShawn O. Pearce ver = grow(ver); 551c6c73c5SShawn O. Pearce ver[end++] = acc; 561c6c73c5SShawn O. Pearce acc = 0; 571c6c73c5SShawn O. Pearce } else if (c == 'g' && 0 < i && str[i - 1] == '.' && 0 < end) { 581c6c73c5SShawn O. Pearce // Non-tagged builds may contain a mangled git describe output. 591c6c73c5SShawn O. Pearce // "1.7.6.1.45.gbe0cc". The 45 isn't a valid component. Drop it. 601c6c73c5SShawn O. Pearce ver[end - 1] = 0; 611c6c73c5SShawn O. Pearce acc = 0; 621c6c73c5SShawn O. Pearce break; 631c6c73c5SShawn O. Pearce } else if (c == '-' && (i + 2) < str.length 641c6c73c5SShawn O. Pearce && str[i + 1] == 'r' && str[i + 2] == 'c') { 651c6c73c5SShawn O. Pearce // Release candidates aren't the same as a final release. 661c6c73c5SShawn O. Pearce if (acc > 0) 671c6c73c5SShawn O. Pearce acc--; 681c6c73c5SShawn O. Pearce break; 691c6c73c5SShawn O. Pearce } else 701c6c73c5SShawn O. Pearce break; 711c6c73c5SShawn O. Pearce } 721c6c73c5SShawn O. Pearce if (acc != 0) { 731c6c73c5SShawn O. Pearce if (end == ver.length) 741c6c73c5SShawn O. Pearce ver = grow(ver); 751c6c73c5SShawn O. Pearce ver[end++] = acc; 761c6c73c5SShawn O. Pearce } else { 771c6c73c5SShawn O. Pearce while (0 < end && ver[end - 1] == 0) 781c6c73c5SShawn O. Pearce end--; 791c6c73c5SShawn O. Pearce } 801c6c73c5SShawn O. Pearce if (end < ver.length) { 811c6c73c5SShawn O. Pearce int[] n = new int[end]; 821c6c73c5SShawn O. Pearce System.arraycopy(ver, 0, n, 0, end); 831c6c73c5SShawn O. Pearce ver = n; 841c6c73c5SShawn O. Pearce } 851c6c73c5SShawn O. Pearce return ver; 861c6c73c5SShawn O. Pearce } 871c6c73c5SShawn O. Pearce grow(int[] tmp)881c6c73c5SShawn O. Pearce private static int[] grow(int[] tmp) { 891c6c73c5SShawn O. Pearce int[] n = new int[tmp.length + 1]; 901c6c73c5SShawn O. Pearce System.arraycopy(tmp, 0, n, 0, tmp.length); 911c6c73c5SShawn O. Pearce return n; 921c6c73c5SShawn O. Pearce } 931c6c73c5SShawn O. Pearce 941c6c73c5SShawn O. Pearce /** 951c6c73c5SShawn O. Pearce * Compare two version strings for natural ordering. 961c6c73c5SShawn O. Pearce * 971c6c73c5SShawn O. Pearce * @param a 981c6c73c5SShawn O. Pearce * first parsed version string. 991c6c73c5SShawn O. Pearce * @param b 1001c6c73c5SShawn O. Pearce * second parsed version string. 10132ff57a2SRobin Rosenberg * @return < 0 if a is before b; 0 if a equals b; >0 if a is after b. 1021c6c73c5SShawn O. Pearce */ compare(int[] a, int[] b)1031c6c73c5SShawn O. Pearce public static int compare(int[] a, int[] b) { 1041c6c73c5SShawn O. Pearce for (int i = 0; i < a.length && i < b.length; i++) { 1051c6c73c5SShawn O. Pearce int cmp = a[i] - b[i]; 1061c6c73c5SShawn O. Pearce if (cmp != 0) 1071c6c73c5SShawn O. Pearce return cmp; 1081c6c73c5SShawn O. Pearce } 1091c6c73c5SShawn O. Pearce return a.length - b.length; 1101c6c73c5SShawn O. Pearce } 1111c6c73c5SShawn O. Pearce 1121c6c73c5SShawn O. Pearce /** 1131c6c73c5SShawn O. Pearce * Convert a parsed version back to a string. 1141c6c73c5SShawn O. Pearce * 1151c6c73c5SShawn O. Pearce * @param ver 1161c6c73c5SShawn O. Pearce * the parsed version array. 1171c6c73c5SShawn O. Pearce * @return a string, e.g. "1.6.6.0". 1181c6c73c5SShawn O. Pearce */ toString(int[] ver)1191c6c73c5SShawn O. Pearce public static String toString(int[] ver) { 1201c6c73c5SShawn O. Pearce StringBuilder b = new StringBuilder(); 1211c6c73c5SShawn O. Pearce for (int v : ver) { 1221c6c73c5SShawn O. Pearce if (b.length() > 0) 1231c6c73c5SShawn O. Pearce b.append('.'); 1241c6c73c5SShawn O. Pearce b.append(v); 1251c6c73c5SShawn O. Pearce } 1261c6c73c5SShawn O. Pearce return b.toString(); 1271c6c73c5SShawn O. Pearce } 1281c6c73c5SShawn O. Pearce 1291c6c73c5SShawn O. Pearce /** 1301c6c73c5SShawn O. Pearce * Check if a Git client has the known push status bug. 1311c6c73c5SShawn O. Pearce * <p> 1321c6c73c5SShawn O. Pearce * These buggy clients do not display the status report from a failed push 1331c6c73c5SShawn O. Pearce * over HTTP. 1341c6c73c5SShawn O. Pearce * 1351c6c73c5SShawn O. Pearce * @param version 1361c6c73c5SShawn O. Pearce * parsed version of the Git client software. 1371c6c73c5SShawn O. Pearce * @return true if the bug is present. 13849befd84SMasaya Suzuki * @deprecated no widely used Git versions need this any more 1391c6c73c5SShawn O. Pearce */ 14049befd84SMasaya Suzuki @Deprecated hasPushStatusBug(int[] version)1411c6c73c5SShawn O. Pearce public static boolean hasPushStatusBug(int[] version) { 14249befd84SMasaya Suzuki return false; 1431c6c73c5SShawn O. Pearce } 1441c6c73c5SShawn O. Pearce 1451c6c73c5SShawn O. Pearce /** 1461c6c73c5SShawn O. Pearce * Check if a Git client has the known chunked request body encoding bug. 1471c6c73c5SShawn O. Pearce * <p> 1481c6c73c5SShawn O. Pearce * Git 1.7.5 contains a unique bug where chunked requests are malformed. 1491c6c73c5SShawn O. Pearce * This applies to both fetch and push. 1501c6c73c5SShawn O. Pearce * 1511c6c73c5SShawn O. Pearce * @param version 1521c6c73c5SShawn O. Pearce * parsed version of the Git client software. 1531c6c73c5SShawn O. Pearce * @param request 1541c6c73c5SShawn O. Pearce * incoming HTTP request. 1551c6c73c5SShawn O. Pearce * @return true if the client has the chunked encoding bug. 15649befd84SMasaya Suzuki * @deprecated no widely used Git versions need this any more 1571c6c73c5SShawn O. Pearce */ 15849befd84SMasaya Suzuki @Deprecated hasChunkedEncodingRequestBug( int[] version, HttpServletRequest request)1591c6c73c5SShawn O. Pearce public static boolean hasChunkedEncodingRequestBug( 1601c6c73c5SShawn O. Pearce int[] version, HttpServletRequest request) { 16149befd84SMasaya Suzuki return false; 1621c6c73c5SShawn O. Pearce } 1631c6c73c5SShawn O. Pearce ClientVersionUtil()1641c6c73c5SShawn O. Pearce private ClientVersionUtil() { 1651c6c73c5SShawn O. Pearce } 1661c6c73c5SShawn O. Pearce } 167