前言

看师傅们的文章时发现,parse_url出现的次数较多,单纯parse_url解析漏洞的考题也有很多,在此研究一下源码(太菜了看不懂,待日后再补充Orz)

源码

ext/standard/url.c文件中

  1. PHPAPI php_url *php_url_parse_ex(char const *str, size_t length)
  2. {
  3. char port_buf[6];
  4. php_url *ret = ecalloc(1, sizeof(php_url));
  5. char const *s, *e, *p, *pp, *ue;
  6. s = str;
  7. ue = s + length;
  8. /* parse scheme */
  9. if ((e = memchr(s, ':', length)) && e != s) {
  10. /* validate scheme */
  11. p = s;
  12. while (p < e) {
  13. /* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */
  14. if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') {
  15. if (e + 1 < ue && e < s + strcspn(s, "?#")) {
  16. goto parse_port;
  17. } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
  18. s += 2;
  19. e = 0;
  20. goto parse_host;
  21. } else {
  22. goto just_path;
  23. }
  24. }
  25. p++;
  26. }
  27. if (e + 1 == ue) { /* only scheme is available */
  28. ret->scheme = estrndup(s, (e - s));
  29. php_replace_controlchars_ex(ret->scheme, (e - s));
  30. return ret;
  31. }
  32. /*
  33. * certain schemas like mailto: and zlib: may not have any / after them
  34. * this check ensures we support those.
  35. */
  36. if (*(e+1) != '/') {
  37. /* check if the data we get is a port this allows us to
  38. * correctly parse things like a.com:80
  39. */
  40. p = e + 1;
  41. while (p < ue && isdigit(*p)) {
  42. p++;
  43. }
  44. if ((p == ue || *p == '/') && (p - e) < 7) {
  45. goto parse_port;
  46. }
  47. ret->scheme = estrndup(s, (e-s));
  48. php_replace_controlchars_ex(ret->scheme, (e - s));
  49. s = e + 1;
  50. goto just_path;
  51. } else {
  52. ret->scheme = estrndup(s, (e-s));
  53. php_replace_controlchars_ex(ret->scheme, (e - s));
  54. if (e + 2 < ue && *(e + 2) == '/') {
  55. s = e + 3;
  56. if (!strncasecmp("file", ret->scheme, sizeof("file"))) {
  57. if (e + 3 < ue && *(e + 3) == '/') {
  58. /* support windows drive letters as in:
  59. file:///c:/somedir/file.txt
  60. */
  61. if (e + 5 < ue && *(e + 5) == ':') {
  62. s = e + 4;
  63. }
  64. goto just_path;
  65. }
  66. }
  67. } else {
  68. s = e + 1;
  69. goto just_path;
  70. }
  71. }
  72. } else if (e) { /* no scheme; starts with colon: look for port */
  73. parse_port:
  74. p = e + 1;
  75. pp = p;
  76. while (pp < ue && pp - p < 6 && isdigit(*pp)) {
  77. pp++;
  78. }
  79. if (pp - p > 0 && pp - p < 6 && (pp == ue || *pp == '/')) {
  80. zend_long port;
  81. memcpy(port_buf, p, (pp - p));
  82. port_buf[pp - p] = '\0';
  83. port = ZEND_STRTOL(port_buf, NULL, 10);
  84. if (port > 0 && port <= 65535) {
  85. ret->port = (unsigned short) port;
  86. if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
  87. s += 2;
  88. }
  89. } else {
  90. if (ret->scheme) efree(ret->scheme);
  91. efree(ret);
  92. return NULL;
  93. }
  94. } else if (p == pp && pp == ue) {
  95. if (ret->scheme) efree(ret->scheme);
  96. efree(ret);
  97. return NULL;
  98. } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
  99. s += 2;
  100. } else {
  101. goto just_path;
  102. }
  103. } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
  104. s += 2;
  105. } else {
  106. goto just_path;
  107. }
  108. parse_host:
  109. /* Binary-safe strcspn(s, "/?#") */
  110. e = ue;
  111. if ((p = memchr(s, '/', e - s))) {
  112. e = p;
  113. }
  114. if ((p = memchr(s, '?', e - s))) {
  115. e = p;
  116. }
  117. if ((p = memchr(s, '#', e - s))) {
  118. e = p;
  119. }
  120. /* check for login and password */
  121. if ((p = zend_memrchr(s, '@', (e-s)))) {
  122. if ((pp = memchr(s, ':', (p-s)))) {
  123. ret->user = estrndup(s, (pp-s));
  124. php_replace_controlchars_ex(ret->user, (pp - s));
  125. pp++;
  126. ret->pass = estrndup(pp, (p-pp));
  127. php_replace_controlchars_ex(ret->pass, (p-pp));
  128. } else {
  129. ret->user = estrndup(s, (p-s));
  130. php_replace_controlchars_ex(ret->user, (p-s));
  131. }
  132. s = p + 1;
  133. }
  134. /* check for port */
  135. if (s < ue && *s == '[' && *(e-1) == ']') {
  136. /* Short circuit portscan,
  137. we're dealing with an
  138. IPv6 embedded address */
  139. p = NULL;
  140. } else {
  141. p = zend_memrchr(s, ':', (e-s));
  142. }
  143. if (p) {
  144. if (!ret->port) {
  145. p++;
  146. if (e-p > 5) { /* port cannot be longer then 5 characters */
  147. if (ret->scheme) efree(ret->scheme);
  148. if (ret->user) efree(ret->user);
  149. if (ret->pass) efree(ret->pass);
  150. efree(ret);
  151. return NULL;
  152. } else if (e - p > 0) {
  153. zend_long port;
  154. memcpy(port_buf, p, (e - p));
  155. port_buf[e - p] = '\0';
  156. port = ZEND_STRTOL(port_buf, NULL, 10);
  157. if (port > 0 && port <= 65535) {
  158. ret->port = (unsigned short)port;
  159. } else {
  160. if (ret->scheme) efree(ret->scheme);
  161. if (ret->user) efree(ret->user);
  162. if (ret->pass) efree(ret->pass);
  163. efree(ret);
  164. return NULL;
  165. }
  166. }
  167. p--;
  168. }
  169. } else {
  170. p = e;
  171. }
  172. /* check if we have a valid host, if we don't reject the string as url */
  173. if ((p-s) < 1) {
  174. if (ret->scheme) efree(ret->scheme);
  175. if (ret->user) efree(ret->user);
  176. if (ret->pass) efree(ret->pass);
  177. efree(ret);
  178. return NULL;
  179. }
  180. ret->host = estrndup(s, (p-s));
  181. php_replace_controlchars_ex(ret->host, (p - s));
  182. if (e == ue) {
  183. return ret;
  184. }
  185. s = e;
  186. just_path:
  187. e = ue;
  188. p = memchr(s, '#', (e - s));
  189. if (p) {
  190. p++;
  191. if (p < e) {
  192. ret->fragment = estrndup(p, (e - p));
  193. php_replace_controlchars_ex(ret->fragment, (e - p));
  194. }
  195. e = p-1;
  196. }
  197. p = memchr(s, '?', (e - s));
  198. if (p) {
  199. p++;
  200. if (p < e) {
  201. ret->query = estrndup(p, (e - p));
  202. php_replace_controlchars_ex(ret->query, (e - p));
  203. }
  204. e = p-1;
  205. }
  206. if (s < e || s == ue) {
  207. ret->path = estrndup(s, (e - s));
  208. php_replace_controlchars_ex(ret->path, (e - s));
  209. }
  210. return ret;
  211. }
  1. /* {{{ proto mixed parse_url(string url, [int url_component])
  2. Parse a URL and return its components */
  3. PHP_FUNCTION(parse_url)
  4. {
  5. char *str;
  6. size_t str_len;
  7. php_url *resource;
  8. zend_long key = -1;
  9. if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
  10. return;
  11. }
  12. resource = php_url_parse_ex(str, str_len);
  13. if (resource == NULL) {
  14. /* @todo Find a method to determine why php_url_parse_ex() failed */
  15. RETURN_FALSE;
  16. }
  17. if (key > -1) {
  18. switch (key) {
  19. case PHP_URL_SCHEME:
  20. if (resource->scheme != NULL) RETVAL_STRING(resource->scheme);
  21. break;
  22. case PHP_URL_HOST:
  23. if (resource->host != NULL) RETVAL_STRING(resource->host);
  24. break;
  25. case PHP_URL_PORT:
  26. if (resource->port != 0) RETVAL_LONG(resource->port);
  27. break;
  28. case PHP_URL_USER:
  29. if (resource->user != NULL) RETVAL_STRING(resource->user);
  30. break;
  31. case PHP_URL_PASS:
  32. if (resource->pass != NULL) RETVAL_STRING(resource->pass);
  33. break;
  34. case PHP_URL_PATH:
  35. if (resource->path != NULL) RETVAL_STRING(resource->path);
  36. break;
  37. case PHP_URL_QUERY:
  38. if (resource->query != NULL) RETVAL_STRING(resource->query);
  39. break;
  40. case PHP_URL_FRAGMENT:
  41. if (resource->fragment != NULL) RETVAL_STRING(resource->fragment);
  42. break;
  43. default:
  44. php_error_docref(NULL, E_WARNING, "Invalid URL component identifier " ZEND_LONG_FMT, key);
  45. RETVAL_FALSE;
  46. }
  47. goto done;
  48. }
  49. /* allocate an array for return */
  50. array_init(return_value);
  51. /* add the various elements to the array */
  52. if (resource->scheme != NULL)
  53. add_assoc_string(return_value, "scheme", resource->scheme);
  54. if (resource->host != NULL)
  55. add_assoc_string(return_value, "host", resource->host);
  56. if (resource->port != 0)
  57. add_assoc_long(return_value, "port", resource->port);
  58. if (resource->user != NULL)
  59. add_assoc_string(return_value, "user", resource->user);
  60. if (resource->pass != NULL)
  61. add_assoc_string(return_value, "pass", resource->pass);
  62. if (resource->path != NULL)
  63. add_assoc_string(return_value, "path", resource->path);
  64. if (resource->query != NULL)
  65. add_assoc_string(return_value, "query", resource->query);
  66. if (resource->fragment != NULL)
  67. add_assoc_string(return_value, "fragment", resource->fragment);
  68. done:
  69. php_url_free(resource);
  70. }

代码中遇到的问题解决

函数定义部分

  1. PHP_FUNCTION(parse_url)
  2. {
  3. char *str;
  4. size_t str_len;
  5. php_url *resource;
  6. zend_long key = -1;
  7. if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
  8. return;
  9. }
  10. resource = php_url_parse_ex(str, str_len);
  11. if (resource == NULL) {
  12. /* @todo Find a method to determine why php_url_parse_ex() failed */
  13. RETURN_FALSE;
  14. }

引用这篇文章的内容http://www.nowamagic.net/librarys/veda/detail/1467

  1. b Boolean
  2. l Integer 整型
  3. d Floating point 浮点型
  4. s String 字符串
  5. r Resource 资源
  6. a Array 数组
  7. o Object instance 对象
  8. O Object instance of a specified type 特定类型的对象
  9. z Non-specific zval 任意类型
  10. Z zval**类型
  11. f 表示函数、方法名称

那么其中的"s|l"表示parse_url需要两个参数,一个字符串型,一个整型

php_url类型的声明在ext/standard/url.h

  1. typedef struct php_url {
  2. char *scheme;
  3. char *user;
  4. char *pass;
  5. char *host;
  6. unsigned short port;
  7. char *path;
  8. char *query;
  9. char *fragment;
  10. } php_url;

问题

  1. parse_url只有两个参数,不知道strlen这个参数哪里去了……?还有他的值到底是怎么获得的……

函数内部实现部分

使用php_url_parse_ex函数来处理我们传过去的url,先暂定str_len为str的长度……

  1. if ((e = memchr(s, ':', length)) && e != s) {
  2. /* validate scheme */
  3. p = s;
  4. while (p < e) {
  5. /* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */
  6. if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') {
  7. if (e + 1 < ue && e < s + strcspn(s, "?#")) {
  8. goto parse_port;
  9. } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
  10. s += 2;
  11. e = 0;
  12. goto parse_host;
  13. } else {
  14. goto just_path;
  15. }
  16. }
  17. p++;
  18. }
  19. if (e + 1 == ue) { /* only scheme is available */
  20. ret->scheme = estrndup(s, (e - s));
  21. php_replace_controlchars_ex(ret->scheme, (e - s));
  22. return ret;
  23. }
  24. /*
  25. * certain schemas like mailto: and zlib: may not have any / after them
  26. * this check ensures we support those.
  27. */
  28. if (*(e+1) != '/') {
  29. /* check if the data we get is a port this allows us to
  30. * correctly parse things like a.com:80
  31. */
  32. p = e + 1;
  33. while (p < ue && isdigit(*p)) {
  34. p++;
  35. }
  36. if ((p == ue || *p == '/') && (p - e) < 7) {
  37. goto parse_port;
  38. }
  39. ret->scheme = estrndup(s, (e-s));
  40. php_replace_controlchars_ex(ret->scheme, (e - s));
  41. s = e + 1;
  42. goto just_path;
  43. } else {
  44. ret->scheme = estrndup(s, (e-s));
  45. php_replace_controlchars_ex(ret->scheme, (e - s));
  46. if (e + 2 < ue && *(e + 2) == '/') {
  47. s = e + 3;
  48. if (!strncasecmp("file", ret->scheme, sizeof("file"))) {
  49. if (e + 3 < ue && *(e + 3) == '/') {
  50. /* support windows drive letters as in:
  51. file:///c:/somedir/file.txt
  52. */
  53. if (e + 5 < ue && *(e + 5) == ':') {
  54. s = e + 4;
  55. }
  56. goto just_path;
  57. }
  58. }
  59. } else {
  60. s = e + 1;
  61. goto just_path;
  62. }
  63. }
  64. } else if (e) { /* no scheme; starts with colon: look for port */

如果s中含有冒号则e指向冒号

且同时如果冒号不在s的开头,p指向s

当p不指向冒号向循环,p指向下一位

如果p指向的值是字母或者数字或者是+,-,.则指针指向下一位,这就代表冒号前面的值其实是任意的字母、数字、+、-、.

如果冒号所在位置小于str,且?#在冒号后面(如果有的话),就跳转到port解析部分

如果str的长度大于1且str的前两个字符是//,s指向//后面的一个字符,e变为0,跳转到host解析

如果冒号是最后一位字符,则冒号前面的东西会当作scheme返回

如果冒号后面不是/,则p指向冒号后面一位

当p小于str且p指向的为数字字符,p一直指向后一位,直到p指向str末尾或者p指向的字符为/,同时冒号后面的数字位数小于6位,跳转到port解析

如果冒号后面不是纯数字或数字后面有一个/,那么冒号前面的内容就当作scheme,放在ret的scheme参数中,s指向冒号后一位,跳转到path解析

如果冒号后面是/,那么冒号前面的内容就当作scheme,放在ret的scheme参数中。如果下面一位也是/,那么s指向//后面一位,如果scheme为file,那么判断接下来一位是不是/,如果是,判断冒号后是否有五个字符,如果有那么第五个字符是不是冒号(为了处理file:///c:),s指向///后的一位字符,跳转到path解析

如果冒号后面不是三个/,s指向冒号后面一位,之后跳转到path解析

如果冒号在str开头,那么进行port解析

姿势

  1. 只要请求的url里不含有冒号(:)就会被当成path解析

php中parse_url函数的源码及分析(scheme部分)的更多相关文章

  1. php中parse_url函数的源码及分析

    前言 看师傅们的文章时发现,parse_url出现的次数较多,单纯parse_url解析漏洞的考题也有很多,在此研究一下源码(太菜了看不懂,待日后再补充Orz) 源码 PHPAPI php_url * ...

  2. Apache Spark源码走读之23 -- Spark MLLib中拟牛顿法L-BFGS的源码实现

    欢迎转载,转载请注明出处,徽沪一郎. 概要 本文就拟牛顿法L-BFGS的由来做一个简要的回顾,然后就其在spark mllib中的实现进行源码走读. 拟牛顿法 数学原理 代码实现 L-BFGS算法中使 ...

  3. redis中set命令的源码分析

    首先在源码中的redis.c文件中有一个结构体:redisCommand redisCommandTable[],这个结构体中定义了每个命令对应的函数,源码中的set命令对应的函数是setComman ...

  4. sleep函数——Gevent源码分析

    gevent是一个异步I/O框架,当遇到I/O操作的时候,会自动切换任务,从而能异步地完成I/O操作 但是在测试的情况下,可以使用sleep函数来让gevent进行任务切换.示例如下: import ...

  5. Matlab.NET混合编程技巧之——直接调用Matlab内置函数(附源码)

    原文:[原创]Matlab.NET混合编程技巧之--直接调用Matlab内置函数(附源码) 在我的上一篇文章[原创]Matlab.NET混编技巧之——找出Matlab内置函数中,已经大概的介绍了mat ...

  6. Generator函数执行器-co函数库源码解析

    一.co函数是什么 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行.短小精悍只有短短200余行,就可以免去手动编写G ...

  7. caffe-windows中classification.cpp的源码阅读

    caffe-windows中classification.cpp的源码阅读 命令格式: usage: classification string(模型描述文件net.prototxt) string( ...

  8. Django框架rest_framework中APIView的as_view()源码解析、认证、权限、频率控制

    在上篇我们对Django原生View源码进行了局部解析:https://www.cnblogs.com/dongxixi/p/11130976.html 在前后端分离项目中前面我们也提到了各种认证需要 ...

  9. RocketMQ中Broker的启动源码分析(一)

    在RocketMQ中,使用BrokerStartup作为启动类,相较于NameServer的启动,Broker作为RocketMQ的核心可复杂得多 [RocketMQ中NameServer的启动源码分 ...

随机推荐

  1. 感觉自己应该重新读一次Javascript

    我自己也有一本Javascript书籍,是自己上大学的时候学校给提供的,现在,我依旧带着这本书.我决定要把这本书在重新温习一下.然后,开启下面的Javascript之旅.这是我看到博客园一位园友写的, ...

  2. 51nod 1851俄罗斯方块(trick)

    题目大意:给出一个黑白图,你可以选定一个俄罗斯方块的区域,黑白翻转,问能否变成白图 比较trick的题目, 首先可以想到,奇数个1肯定是无解的,所以考虑偶数个1 可以先讨论n是2的情况 当n为2时,其 ...

  3. HDU - 1880 魔咒词典~哈希入门

    哈利波特在魔法学校的必修课之一就是学习魔咒.据说魔法世界有100000种不同的魔咒,哈利很难全部记住,但是为了对抗强敌,他必须在危急时刻能够调用任何一个需要的魔咒,所以他需要你的帮助. 给你一部魔咒词 ...

  4. eclipse console输出有长度限制

    抓取一个网页内容,然后打印到控制台,发现内容首部都没有了. String content = getResponseText("http://xxx.html"); System. ...

  5. 转:RBAC权限控制

    名词解释: RBAC:Role-Based Access Control,基于角色的访问控制   关键词: RBAC,Java Shiro,Spring Security,   一. RBAC 要解决 ...

  6. centos7下yum快速安装 mariadb(mysql)

    从最新版本的centos系统开始,默认的是 Mariadb而不是mysql! 使用系统自带的repos安装很简单: yum install mariadb mariadb-server systemc ...

  7. POJ 2395 Out of Hay (prim)

    题目链接 Description The cows have run out of hay, a horrible event that must be remedied immediately. B ...

  8. bzoj 1433 二分图匹配

    裸地匈牙利或者最大流,直接匹配就行了 需要注意的是(我就没注意细节WA了好多次...) 每个人和自己之间的边是0,但是应该是1 不是在校生是没有床的.... /******************** ...

  9. linux下面which whereis find locate的使用

    我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索.这些是从网上找到的资料,因为有时很长时间不会用到,当要用的时候经常弄混了,所以放到这里方便使用. which    ...

  10. Unordered load/store queue

    A method and processor for providing full load/store queue functionality to an unordered load/store  ...