versioning.c 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Copyright (c) The PHP Group |
  4. +----------------------------------------------------------------------+
  5. | This source file is subject to version 3.01 of the PHP license, |
  6. | that is bundled with this package in the file LICENSE, and is |
  7. | available through the world-wide-web at the following url: |
  8. | https://www.php.net/license/3_01.txt |
  9. | If you did not receive a copy of the PHP license and are unable to |
  10. | obtain it through the world-wide-web, please send a note to |
  11. | license@php.net so we can mail you a copy immediately. |
  12. +----------------------------------------------------------------------+
  13. | Author: Stig Sæther Bakken <ssb@php.net> |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include <stdio.h>
  17. #include <sys/types.h>
  18. #include <ctype.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include "php.h"
  22. #include "php_versioning.h"
  23. /* {{{ php_canonicalize_version() */
  24. PHPAPI char *
  25. php_canonicalize_version(const char *version)
  26. {
  27. size_t len = strlen(version);
  28. char *buf = safe_emalloc(len, 2, 1), *q, lp, lq;
  29. const char *p;
  30. if (len == 0) {
  31. *buf = '\0';
  32. return buf;
  33. }
  34. p = version;
  35. q = buf;
  36. *q++ = lp = *p++;
  37. while (*p) {
  38. /* s/[-_+]/./g;
  39. * s/([^\d\.])([^\D\.])/$1.$2/g;
  40. * s/([^\D\.])([^\d\.])/$1.$2/g;
  41. */
  42. #define isdig(x) (isdigit(x)&&(x)!='.')
  43. #define isndig(x) (!isdigit(x)&&(x)!='.')
  44. #define isspecialver(x) ((x)=='-'||(x)=='_'||(x)=='+')
  45. lq = *(q - 1);
  46. if (isspecialver(*p)) {
  47. if (lq != '.') {
  48. *q++ = '.';
  49. }
  50. } else if ((isndig(lp) && isdig(*p)) || (isdig(lp) && isndig(*p))) {
  51. if (lq != '.') {
  52. *q++ = '.';
  53. }
  54. *q++ = *p;
  55. } else if (!isalnum(*p)) {
  56. if (lq != '.') {
  57. *q++ = '.';
  58. }
  59. } else {
  60. *q++ = *p;
  61. }
  62. lp = *p++;
  63. }
  64. *q++ = '\0';
  65. return buf;
  66. }
  67. /* }}} */
  68. /* {{{ compare_special_version_forms() */
  69. typedef struct {
  70. const char *name;
  71. int order;
  72. } special_forms_t;
  73. static int
  74. compare_special_version_forms(char *form1, char *form2)
  75. {
  76. int found1 = -1, found2 = -1;
  77. special_forms_t special_forms[11] = {
  78. {"dev", 0},
  79. {"alpha", 1},
  80. {"a", 1},
  81. {"beta", 2},
  82. {"b", 2},
  83. {"RC", 3},
  84. {"rc", 3},
  85. {"#", 4},
  86. {"pl", 5},
  87. {"p", 5},
  88. {NULL, 0},
  89. };
  90. special_forms_t *pp;
  91. for (pp = special_forms; pp && pp->name; pp++) {
  92. if (strncmp(form1, pp->name, strlen(pp->name)) == 0) {
  93. found1 = pp->order;
  94. break;
  95. }
  96. }
  97. for (pp = special_forms; pp && pp->name; pp++) {
  98. if (strncmp(form2, pp->name, strlen(pp->name)) == 0) {
  99. found2 = pp->order;
  100. break;
  101. }
  102. }
  103. return ZEND_NORMALIZE_BOOL(found1 - found2);
  104. }
  105. /* }}} */
  106. /* {{{ php_version_compare() */
  107. PHPAPI int
  108. php_version_compare(const char *orig_ver1, const char *orig_ver2)
  109. {
  110. char *ver1;
  111. char *ver2;
  112. char *p1, *p2, *n1, *n2;
  113. long l1, l2;
  114. int compare = 0;
  115. if (!*orig_ver1 || !*orig_ver2) {
  116. if (!*orig_ver1 && !*orig_ver2) {
  117. return 0;
  118. } else {
  119. return *orig_ver1 ? 1 : -1;
  120. }
  121. }
  122. if (orig_ver1[0] == '#') {
  123. ver1 = estrdup(orig_ver1);
  124. } else {
  125. ver1 = php_canonicalize_version(orig_ver1);
  126. }
  127. if (orig_ver2[0] == '#') {
  128. ver2 = estrdup(orig_ver2);
  129. } else {
  130. ver2 = php_canonicalize_version(orig_ver2);
  131. }
  132. p1 = n1 = ver1;
  133. p2 = n2 = ver2;
  134. while (*p1 && *p2 && n1 && n2) {
  135. if ((n1 = strchr(p1, '.')) != NULL) {
  136. *n1 = '\0';
  137. }
  138. if ((n2 = strchr(p2, '.')) != NULL) {
  139. *n2 = '\0';
  140. }
  141. if (isdigit(*p1) && isdigit(*p2)) {
  142. /* compare element numerically */
  143. l1 = strtol(p1, NULL, 10);
  144. l2 = strtol(p2, NULL, 10);
  145. compare = ZEND_NORMALIZE_BOOL(l1 - l2);
  146. } else if (!isdigit(*p1) && !isdigit(*p2)) {
  147. /* compare element names */
  148. compare = compare_special_version_forms(p1, p2);
  149. } else {
  150. /* mix of names and numbers */
  151. if (isdigit(*p1)) {
  152. compare = compare_special_version_forms("#N#", p2);
  153. } else {
  154. compare = compare_special_version_forms(p1, "#N#");
  155. }
  156. }
  157. if (compare != 0) {
  158. break;
  159. }
  160. if (n1 != NULL) {
  161. p1 = n1 + 1;
  162. }
  163. if (n2 != NULL) {
  164. p2 = n2 + 1;
  165. }
  166. }
  167. if (compare == 0) {
  168. if (n1 != NULL) {
  169. if (isdigit(*p1)) {
  170. compare = 1;
  171. } else {
  172. compare = php_version_compare(p1, "#N#");
  173. }
  174. } else if (n2 != NULL) {
  175. if (isdigit(*p2)) {
  176. compare = -1;
  177. } else {
  178. compare = php_version_compare("#N#", p2);
  179. }
  180. }
  181. }
  182. efree(ver1);
  183. efree(ver2);
  184. return compare;
  185. }
  186. /* }}} */
  187. /* {{{ Compares two "PHP-standardized" version number strings */
  188. PHP_FUNCTION(version_compare)
  189. {
  190. char *v1, *v2;
  191. zend_string *op = NULL;
  192. size_t v1_len, v2_len;
  193. int compare;
  194. ZEND_PARSE_PARAMETERS_START(2, 3)
  195. Z_PARAM_STRING(v1, v1_len)
  196. Z_PARAM_STRING(v2, v2_len)
  197. Z_PARAM_OPTIONAL
  198. Z_PARAM_STR_OR_NULL(op)
  199. ZEND_PARSE_PARAMETERS_END();
  200. compare = php_version_compare(v1, v2);
  201. if (!op) {
  202. RETURN_LONG(compare);
  203. }
  204. if (zend_string_equals_literal(op, "<") || zend_string_equals_literal(op, "lt")) {
  205. RETURN_BOOL(compare == -1);
  206. }
  207. if (zend_string_equals_literal(op, "<=") || zend_string_equals_literal(op, "le")) {
  208. RETURN_BOOL(compare != 1);
  209. }
  210. if (zend_string_equals_literal(op, ">") || zend_string_equals_literal(op, "gt")) {
  211. RETURN_BOOL(compare == 1);
  212. }
  213. if (zend_string_equals_literal(op, ">=") || zend_string_equals_literal(op, "ge")) {
  214. RETURN_BOOL(compare != -1);
  215. }
  216. if (zend_string_equals_literal(op, "==") || zend_string_equals_literal(op, "=") || zend_string_equals_literal(op, "eq")) {
  217. RETURN_BOOL(compare == 0);
  218. }
  219. if (zend_string_equals_literal(op, "!=") || zend_string_equals_literal(op, "<>") || zend_string_equals_literal(op, "ne")) {
  220. RETURN_BOOL(compare != 0);
  221. }
  222. zend_argument_value_error(3, "must be a valid comparison operator");
  223. }
  224. /* }}} */