前言

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

源码

ext/standard/url.c文件中

PHPAPI php_url *php_url_parse_ex(char const *str, size_t length)
{
char port_buf[6];
php_url *ret = ecalloc(1, sizeof(php_url));
char const *s, *e, *p, *pp, *ue; s = str;
ue = s + length; /* parse scheme */
if ((e = memchr(s, ':', length)) && e != s) {
/* validate scheme */
p = s;
while (p < e) {
/* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */
if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') {
if (e + 1 < ue && e < s + strcspn(s, "?#")) {
goto parse_port;
} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
e = 0;
goto parse_host;
} else {
goto just_path;
}
}
p++;
} if (e + 1 == ue) { /* only scheme is available */
ret->scheme = estrndup(s, (e - s));
php_replace_controlchars_ex(ret->scheme, (e - s));
return ret;
} /*
* certain schemas like mailto: and zlib: may not have any / after them
* this check ensures we support those.
*/
if (*(e+1) != '/') {
/* check if the data we get is a port this allows us to
* correctly parse things like a.com:80
*/
p = e + 1;
while (p < ue && isdigit(*p)) {
p++;
} if ((p == ue || *p == '/') && (p - e) < 7) {
goto parse_port;
} ret->scheme = estrndup(s, (e-s));
php_replace_controlchars_ex(ret->scheme, (e - s)); s = e + 1;
goto just_path;
} else {
ret->scheme = estrndup(s, (e-s));
php_replace_controlchars_ex(ret->scheme, (e - s)); if (e + 2 < ue && *(e + 2) == '/') {
s = e + 3;
if (!strncasecmp("file", ret->scheme, sizeof("file"))) {
if (e + 3 < ue && *(e + 3) == '/') {
/* support windows drive letters as in:
file:///c:/somedir/file.txt
*/
if (e + 5 < ue && *(e + 5) == ':') {
s = e + 4;
}
goto just_path;
}
}
} else {
s = e + 1;
goto just_path;
}
}
} else if (e) { /* no scheme; starts with colon: look for port */
parse_port:
p = e + 1;
pp = p; while (pp < ue && pp - p < 6 && isdigit(*pp)) {
pp++;
} if (pp - p > 0 && pp - p < 6 && (pp == ue || *pp == '/')) {
zend_long port;
memcpy(port_buf, p, (pp - p));
port_buf[pp - p] = '\0';
port = ZEND_STRTOL(port_buf, NULL, 10);
if (port > 0 && port <= 65535) {
ret->port = (unsigned short) port;
if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
}
} else {
if (ret->scheme) efree(ret->scheme);
efree(ret);
return NULL;
}
} else if (p == pp && pp == ue) {
if (ret->scheme) efree(ret->scheme);
efree(ret);
return NULL;
} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
} else {
goto just_path;
}
} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
} else {
goto just_path;
} parse_host:
/* Binary-safe strcspn(s, "/?#") */
e = ue;
if ((p = memchr(s, '/', e - s))) {
e = p;
}
if ((p = memchr(s, '?', e - s))) {
e = p;
}
if ((p = memchr(s, '#', e - s))) {
e = p;
} /* check for login and password */
if ((p = zend_memrchr(s, '@', (e-s)))) {
if ((pp = memchr(s, ':', (p-s)))) {
ret->user = estrndup(s, (pp-s));
php_replace_controlchars_ex(ret->user, (pp - s)); pp++;
ret->pass = estrndup(pp, (p-pp));
php_replace_controlchars_ex(ret->pass, (p-pp));
} else {
ret->user = estrndup(s, (p-s));
php_replace_controlchars_ex(ret->user, (p-s));
} s = p + 1;
} /* check for port */
if (s < ue && *s == '[' && *(e-1) == ']') {
/* Short circuit portscan,
we're dealing with an
IPv6 embedded address */
p = NULL;
} else {
p = zend_memrchr(s, ':', (e-s));
} if (p) {
if (!ret->port) {
p++;
if (e-p > 5) { /* port cannot be longer then 5 characters */
if (ret->scheme) efree(ret->scheme);
if (ret->user) efree(ret->user);
if (ret->pass) efree(ret->pass);
efree(ret);
return NULL;
} else if (e - p > 0) {
zend_long port;
memcpy(port_buf, p, (e - p));
port_buf[e - p] = '\0';
port = ZEND_STRTOL(port_buf, NULL, 10);
if (port > 0 && port <= 65535) {
ret->port = (unsigned short)port;
} else {
if (ret->scheme) efree(ret->scheme);
if (ret->user) efree(ret->user);
if (ret->pass) efree(ret->pass);
efree(ret);
return NULL;
}
}
p--;
}
} else {
p = e;
} /* check if we have a valid host, if we don't reject the string as url */
if ((p-s) < 1) {
if (ret->scheme) efree(ret->scheme);
if (ret->user) efree(ret->user);
if (ret->pass) efree(ret->pass);
efree(ret);
return NULL;
} ret->host = estrndup(s, (p-s));
php_replace_controlchars_ex(ret->host, (p - s)); if (e == ue) {
return ret;
} s = e; just_path: e = ue;
p = memchr(s, '#', (e - s));
if (p) {
p++;
if (p < e) {
ret->fragment = estrndup(p, (e - p));
php_replace_controlchars_ex(ret->fragment, (e - p));
}
e = p-1;
} p = memchr(s, '?', (e - s));
if (p) {
p++;
if (p < e) {
ret->query = estrndup(p, (e - p));
php_replace_controlchars_ex(ret->query, (e - p));
}
e = p-1;
} if (s < e || s == ue) {
ret->path = estrndup(s, (e - s));
php_replace_controlchars_ex(ret->path, (e - s));
} return ret;
}
/* {{{ proto mixed parse_url(string url, [int url_component])
Parse a URL and return its components */
PHP_FUNCTION(parse_url)
{
char *str;
size_t str_len;
php_url *resource;
zend_long key = -1; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
return;
} resource = php_url_parse_ex(str, str_len);
if (resource == NULL) {
/* @todo Find a method to determine why php_url_parse_ex() failed */
RETURN_FALSE;
} if (key > -1) {
switch (key) {
case PHP_URL_SCHEME:
if (resource->scheme != NULL) RETVAL_STRING(resource->scheme);
break;
case PHP_URL_HOST:
if (resource->host != NULL) RETVAL_STRING(resource->host);
break;
case PHP_URL_PORT:
if (resource->port != 0) RETVAL_LONG(resource->port);
break;
case PHP_URL_USER:
if (resource->user != NULL) RETVAL_STRING(resource->user);
break;
case PHP_URL_PASS:
if (resource->pass != NULL) RETVAL_STRING(resource->pass);
break;
case PHP_URL_PATH:
if (resource->path != NULL) RETVAL_STRING(resource->path);
break;
case PHP_URL_QUERY:
if (resource->query != NULL) RETVAL_STRING(resource->query);
break;
case PHP_URL_FRAGMENT:
if (resource->fragment != NULL) RETVAL_STRING(resource->fragment);
break;
default:
php_error_docref(NULL, E_WARNING, "Invalid URL component identifier " ZEND_LONG_FMT, key);
RETVAL_FALSE;
}
goto done;
} /* allocate an array for return */
array_init(return_value); /* add the various elements to the array */
if (resource->scheme != NULL)
add_assoc_string(return_value, "scheme", resource->scheme);
if (resource->host != NULL)
add_assoc_string(return_value, "host", resource->host);
if (resource->port != 0)
add_assoc_long(return_value, "port", resource->port);
if (resource->user != NULL)
add_assoc_string(return_value, "user", resource->user);
if (resource->pass != NULL)
add_assoc_string(return_value, "pass", resource->pass);
if (resource->path != NULL)
add_assoc_string(return_value, "path", resource->path);
if (resource->query != NULL)
add_assoc_string(return_value, "query", resource->query);
if (resource->fragment != NULL)
add_assoc_string(return_value, "fragment", resource->fragment);
done:
php_url_free(resource);
}

代码中遇到的问题解决

函数定义部分

PHP_FUNCTION(parse_url)
{
char *str;
size_t str_len;
php_url *resource;
zend_long key = -1; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
return;
} resource = php_url_parse_ex(str, str_len);
if (resource == NULL) {
/* @todo Find a method to determine why php_url_parse_ex() failed */
RETURN_FALSE;
}

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

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

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

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

typedef struct php_url {
char *scheme;
char *user;
char *pass;
char *host;
unsigned short port;
char *path;
char *query;
char *fragment;
} php_url;

问题

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

函数内部实现部分

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

if ((e = memchr(s, ':', length)) && e != s) {
/* validate scheme */
p = s;
while (p < e) {
/* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */
if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') {
if (e + 1 < ue && e < s + strcspn(s, "?#")) {
goto parse_port;
} else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
e = 0;
goto parse_host;
} else {
goto just_path;
}
}
p++;
} if (e + 1 == ue) { /* only scheme is available */
ret->scheme = estrndup(s, (e - s));
php_replace_controlchars_ex(ret->scheme, (e - s));
return ret;
} /*
* certain schemas like mailto: and zlib: may not have any / after them
* this check ensures we support those.
*/
if (*(e+1) != '/') {
/* check if the data we get is a port this allows us to
* correctly parse things like a.com:80
*/
p = e + 1;
while (p < ue && isdigit(*p)) {
p++;
} if ((p == ue || *p == '/') && (p - e) < 7) {
goto parse_port;
} ret->scheme = estrndup(s, (e-s));
php_replace_controlchars_ex(ret->scheme, (e - s)); s = e + 1;
goto just_path;
} else {
ret->scheme = estrndup(s, (e-s));
php_replace_controlchars_ex(ret->scheme, (e - s)); if (e + 2 < ue && *(e + 2) == '/') {
s = e + 3;
if (!strncasecmp("file", ret->scheme, sizeof("file"))) {
if (e + 3 < ue && *(e + 3) == '/') {
/* support windows drive letters as in:
file:///c:/somedir/file.txt
*/
if (e + 5 < ue && *(e + 5) == ':') {
s = e + 4;
}
goto just_path;
}
}
} else {
s = e + 1;
goto just_path;
}
}
} 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. NOIP临考经验【转】

    NOIP临考经验 1.提前15分钟入场,此时静坐调整心态,适当的深呼吸 2.打开编辑器并调整为自己喜欢的界面 3.熟悉文件目录,写好准确无误的代码模板 4.压缩包或许还不能解压,但是文件名已经可以知道 ...

  2. python数据绘图常用方法总结

    挖坑,以后还会更新吧 做数学建模画图使用了matplotlib和numpy,这里简单总结一下常用的用法 一.数据拟合 1.np.polyfit(x, y, n) 使用n次多项式去拟合x,y散点图,返回 ...

  3. 【算法】CDQ分治 -- 三维偏序 & 动态逆序对

    初次接触CDQ分治,感觉真的挺厉害的.整体思路即分而治之,再用之前处理出来的答案统计之后的答案. 大概流程是(对于区间 l ~ r): 1.处理 l ~mid, mid + 1 ~ r 的答案: 2. ...

  4. C&C++——基本说明

    预处理器(Preprocessor) 1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define SECONDS_PER_YEAR (60 * 60 * ...

  5. 【HDU 4300 Clairewd’s message】

    Clairewd is a member of FBI. After several years concealing in BUPT, she intercepted some important ...

  6. ext radiogroup如何取值和设值

    var radios = Ext.create('Ext.form.Panel', { title: 'RadioGroup Example', width: 300, height: 125, bo ...

  7. Codeforces Round #525 (Div. 2) F. Ehab and a weird weight formula

    F. Ehab and a weird weight formula 题目链接:https://codeforces.com/contest/1088/problem/F 题意: 给出一颗点有权值的树 ...

  8. BZOJ 4823: [Cqoi2017]老C的方块

    分析: 我觉得我的网络流白学了...QAQ... 其实数据范围本是无法用网络流跑过去的,然而出题者想让他跑过去,也就跑过去了... 看到题目其实感觉很麻烦,不知道从哪里入手,那么仔细观察所给出的有用信 ...

  9. szoj461【四校联考0430】挑战

    传送门:(涉及版权忽略) [题解] 我们发现n的范围很小,提示我们可以折半,然后我们就会了O(T2^(n/2)*n)的做法,然而会T. 考虑如何优化.直接排序会多一个log(2^(n/2))也就是n, ...

  10. bzoj 1067 特判

    这道题的大题思路就是模拟 假设给定的年份是x,y,首先分为4个大的情况,分别是 x的信息已知,y的信息已知 x的信息已知,y的信息未知 x的信息未知,y的情况已知 x的信息未知,y的情况未知 然后对于 ...