/* Copyright (C) 1995 Ian Jackson Copyright (C) 2001 Anthony Towns Copyright (C) 2008-2018 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "lib/strutil.h" /*** global variables ****************************************************************************/ /*** file scope macro definitions ****************************************************************/ /*** file scope type declarations ****************************************************************/ /*** file scope variables ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ /* Match a file suffix defined by this regular expression: /(\.[A-Za-z~][A-Za-z0-9~]*)*$/ * * @str pointer to string to scan. * * @return pointer to the matching suffix, or NULL if not found. * Upon return, @str points to terminating NUL. */ static const char * match_suffix (const char **str) { const char *match = NULL; gboolean read_alpha = FALSE; while (**str != '\0') { if (read_alpha) { read_alpha = FALSE; if (!g_ascii_isalpha (**str) && **str != '~') match = NULL; } else if (**str == '.') { read_alpha = TRUE; if (match == NULL) match = *str; } else if (!g_ascii_isalnum (**str) && **str != '~') match = NULL; (*str)++; } return match; } /* --------------------------------------------------------------------------------------------- */ /* verrevcmp helper function */ static int order (unsigned char c) { if (g_ascii_isdigit (c)) return 0; if (g_ascii_isalpha (c)) return c; if (c == '~') return -1; return (int) c + UCHAR_MAX + 1; } /* --------------------------------------------------------------------------------------------- */ /* Slightly modified verrevcmp function from dpkg * * This implements the algorithm for comparison of version strings * specified by Debian and now widely adopted. The detailed * specification can be found in the Debian Policy Manual in the * section on the 'Version' control field. This version of the code * implements that from s5.6.12 of Debian Policy v3.8.0.1 * https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version * * @s1 first string to compare * @s1_len length of @s1 * @s2 second string to compare * @s2_len length of @s2 * * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2. */ static int verrevcmp (const char *s1, size_t s1_len, const char *s2, size_t s2_len) { size_t s1_pos = 0; size_t s2_pos = 0; while (s1_pos < s1_len || s2_pos < s2_len) { int first_diff = 0; while ((s1_pos < s1_len && !g_ascii_isdigit (s1[s1_pos])) || (s2_pos < s2_len && !g_ascii_isdigit (s2[s2_pos]))) { int s1_c = 0; int s2_c = 0; if (s1_pos != s1_len) s1_c = order (s1[s1_pos]); if (s2_pos != s2_len) s2_c = order (s2[s2_pos]); if (s1_c != s2_c) return (s1_c - s2_c); s1_pos++; s2_pos++; } while (s1[s1_pos] == '0') s1_pos++; while (s2[s2_pos] == '0') s2_pos++; while (g_ascii_isdigit (s1[s1_pos]) && g_ascii_isdigit (s2[s2_pos])) { if (first_diff == 0) first_diff = s1[s1_pos] - s2[s2_pos]; s1_pos++; s2_pos++; } if (g_ascii_isdigit (s1[s1_pos])) return 1; if (g_ascii_isdigit (s2[s2_pos])) return -1; if (first_diff != 0) return first_diff; } return 0; } /* --------------------------------------------------------------------------------------------- */ /*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ /* Compare version strings. * * @s1 first string to compare * @s2 second string to compare * * @return an integer less than, equal to, or greater than zero, if @s1 is <, == or > than @s2. */ int filevercmp (const char *s1, const char *s2) { const char *s1_pos, *s2_pos; const char *s1_suffix, *s2_suffix; size_t s1_len, s2_len; int simple_cmp, result; /* easy comparison to see if strings are identical */ simple_cmp = strcmp (s1, s2); if (simple_cmp == 0) return 0; /* special handle for "", "." and ".." */ if (*s1 == '\0') return -1; if (*s2 == '\0') return 1; if (DIR_IS_DOT (s1)) return -1; if (DIR_IS_DOT (s2)) return 1; if (DIR_IS_DOTDOT (s1)) return -1; if (DIR_IS_DOTDOT (s2)) return 1; /* special handle for other hidden files */ if (*s1 == '.' && *s2 != '.') return -1; if (*s1 != '.' && *s2 == '.') return 1; if (*s1 == '.' && *s2 == '.') { s1++; s2++; } /* "cut" file suffixes */ s1_pos = s1; s2_pos = s2; s1_suffix = match_suffix (&s1_pos); s2_suffix = match_suffix (&s2_pos); s1_len = (s1_suffix != NULL ? s1_suffix : s1_pos) - s1; s2_len = (s2_suffix != NULL ? s2_suffix : s2_pos) - s2; /* restore file suffixes if strings are identical after "cut" */ if ((s1_suffix != NULL || s2_suffix != NULL) && (s1_len == s2_len) && strncmp (s1, s2, s1_len) == 0) { s1_len = s1_pos - s1; s2_len = s2_pos - s2; } result = verrevcmp (s1, s1_len, s2, s2_len); return result == 0 ? simple_cmp : result; } /* --------------------------------------------------------------------------------------------- */