PHP Lex Engine Sourcecode Analysis(undone)

时间:2023-03-09 19:32:22
PHP Lex Engine Sourcecode Analysis(undone)

catalog

. PHP词法解析引擎Lex简介
. PHP标签解析

1. PHP词法解析引擎Lex简介

Relevant Link:

2. PHP标签解析

\php-5.4.41\Zend\zend_language_scanner.l

int lex_scan(zval *zendlval TSRMLS_DC)
{
restart:
//设置当前token的首位置为当前位置
SCNG(yy_text) = YYCURSOR; yymore_restart:
//这段注释定义了各个类型的正则表达式匹配,在词法解析程序(如bison、re2c等)程序将本文件转化为c代码时会用到
/*!re2c
re2c:yyfill:check = 0;
LNUM [0-9]+
DNUM ([0-9]*"."[0-9]+)|([0-9]+"."[0-9]*)
EXPONENT_DNUM (({LNUM}|{DNUM})[eE][+-]?{LNUM})
HNUM "0x"[0-9a-fA-F]+
BNUM "0b"[01]+
LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
WHITESPACE [ \n\r\t]+
TABS_AND_SPACES [ \t]*
TOKENS [;:,.\[\]()|^&+-/*=%!~$<>?@]
ANY_CHAR [^]
NEWLINE ("\r"|"\n"|"\r\n") /* compute yyleng before each rule */
<!*> := yyleng = YYCURSOR - SCNG(yy_text);
。。

0x1: 匹配PHP标签

1.1 <script language=php>、<script language='php'>、<script language="php">

//首先是匹配<script language=php>标签,源码如下,无论这里面有多少个空白字符全部无视,最后php也可以加上单引号或双引号
<INITIAL>"<script"{WHITESPACE}+"language"{WHITESPACE}*"="{WHITESPACE}*("php"|"\"php\""|"'php'"){WHITESPACE}*">"
{
YYCTYPE *bracket = (YYCTYPE*)zend_memrchr(yytext, '<', yyleng - (sizeof("script language=php>") - )); //因为<script>标签本身是在html中的,所以判断当前是否在扫描html,如果是的话就跳转到inline_html去
if (bracket != SCNG(yy_text))
{
/* Handle previously scanned HTML, as possible <script> tags found are assumed to not be PHP's */
YYCURSOR = bracket;
goto inline_html;
} //不然就将当前状态改为ST_IN_SCRIPTING并返回T_OPEN_TAG,表示这是一个php的标签。
HANDLE_NEWLINES(yytext, yyleng);
zendlval->value.str.val = yytext; /* no copying - intentional */
zendlval->value.str.len = yyleng;
zendlval->type = IS_STRING;
BEGIN(ST_IN_SCRIPTING);
return T_OPEN_TAG;
}

1.2 <%=、<%

<INITIAL>"<%"
{
//检查php.ini里面的asp_tags标签是否为On,如果是则表示进入脚本并返回T_OPEN_TAG
if (CG(asp_tags))
{
zendlval->value.str.val = yytext; /* no copying - intentional */
zendlval->value.str.len = yyleng;
zendlval->type = IS_STRING;
BEGIN(ST_IN_SCRIPTING);
return T_OPEN_TAG;
}
else
{
//否则就转到inline_char_handler去
goto inline_char_handler;
}
}

1.3 <?=、<?

//短标签<?=和<?
<INITIAL>"<?"
{
//判断short_open_tag是否为On
if (CG(short_tags))
{
zendlval->value.str.val = yytext; /* no copying - intentional */
zendlval->value.str.len = yyleng;
zendlval->type = IS_STRING;
BEGIN(ST_IN_SCRIPTING);
return T_OPEN_TAG;
}
else
{
goto inline_char_handler;
}
} //"<?=" 不需要short_open_tag标志打开
<INITIAL>"<?="
{
zendlval->value.str.val = yytext; /* no copying - intentional */
zendlval->value.str.len = yyleng;
zendlval->type = IS_STRING;
BEGIN(ST_IN_SCRIPTING);
return T_OPEN_TAG_WITH_ECHO;
}

1.4 <?php

<INITIAL>"<?php"([ \t]|{NEWLINE})
{
zendlval->value.str.val = yytext; /* no copying - intentional */
zendlval->value.str.len = yyleng;
zendlval->type = IS_STRING;
HANDLE_NEWLINE(yytext[yyleng-]);
BEGIN(ST_IN_SCRIPTING);
return T_OPEN_TAG;
}

1.5 inline_char_handler

如果以上的标签匹配都失败,就会匹配ANY_CHAR,判断是否扫描完了,是的话直接返回0,不是就接下去执行inline_char_handler和inline_html段的代码

<INITIAL>{ANY_CHAR}
{
if (YYCURSOR > YYLIMIT)
{
return ;
} inline_char_handler:
while ()
{
/*
inline_char_handler中的代码是对整个字符串扫描,memchr表示的是从YYCURSOR开始的YYLIMIT - YYCURSOR长度内的字符串中搜索'<'字符
1. 如果找到则匹配'?'、'%'、's'等字符,如果满足条件则结束循环
2. 而匹配到's'或'S'则将YYCURSOR往回退一格并重新开始php标签的匹配
*/
YYCTYPE *ptr = memchr(YYCURSOR, '<', YYLIMIT - YYCURSOR); YYCURSOR = ptr ? ptr + : YYLIMIT; if (YYCURSOR < YYLIMIT)
{
switch (*YYCURSOR)
{
case '?':
if (CG(short_tags) || !strncasecmp((char*)YYCURSOR + , "php", ) || (*(YYCURSOR + ) == '='))
{ /* Assume [ \t\n\r] follows "php" */
break;
}
continue;
case '%':
if (CG(asp_tags))
{
break;
}
continue;
case 's':
case 'S':
/* Probably NOT an opening PHP <script> tag, so don't end the HTML chunk yet
* If it is, the PHP <script> tag rule checks for any HTML scanned before it */
YYCURSOR--;
yymore();
default:
continue;
} YYCURSOR--;
} break;
}
..

1.6 inline_html

如果是inline_html的代码,直接复制这段代码(PHP引擎对HTML代码原样输出),随后返回T_INLINE_HTML

..
//inline_html扫描的是不在php标签里面的的代码,也就是说这些php代码可能夹杂在诸如html等代码中
inline_html:
yyleng = YYCURSOR - SCNG(yy_text); if (SCNG(output_filter))
{
int readsize;
size_t sz = ;
readsize = SCNG(output_filter)((unsigned char **)&(zendlval->value.str.val), &sz, (unsigned char *)yytext, (size_t)yyleng TSRMLS_CC);
zendlval->value.str.len = sz;
if (readsize < yyleng)
{
yyless(readsize);
}
}
else
{
zendlval->value.str.val = (char *) estrndup(yytext, yyleng);
zendlval->value.str.len = yyleng;
}
zendlval->type = IS_STRING;
HANDLE_NEWLINES(yytext, yyleng);
return T_INLINE_HTML;
}

从PHP源代码中我们也可以看到,PHP对结束闭合标签是可选的,解析器并不会强制要求一定要有结束闭合标签,PHP官方文档解释如下

The closing tag of a PHP block at the end of a file is optional, and in some cases omitting it is helpful when using include or require, so unwanted whitespace will not occur at the end of files, and you will still be able to add headers to the response later. It is also handy if you use output buffering, and would not like to see added unwanted whitespace at the end of the parts generated by the included files.

不使用结束闭合标签的好处有以下几个

. 如果这个是一个被别人包含的程序,没有这个结束符,可以减少很多很多问题,比如说:header, setcookie, session_start这些动作之前不能有输出,如果不小心在?> 后边加了不可见字符(多余的空格、换行符)等破坏页面显示,就会报"Header already sent"错误,不写的话不会有此问题。另,可以直接把光标移到最后,接着编程
. PHP闭合标签"?>"在PHP中对PHP的分析器是可选的。但是,如果使用闭合标签,任何由开发者,用户,或者FTP应用程序插入闭合标签后面的空格都有可能会引起多余的输出、php错误、之后的输出无法显示、空白页。因此,所有的php文件应该省略这个php闭合标签,并插入一段注释来标明这是文件的底部并定位这个文件在这个应用的相对路径。这样有利于你确定这个文件已经结束而不是被删节的

Relevant Link:

http://php.net/manual/en/language.basic-syntax.instruction-separation.php
http://doophp.sinaapp.com/archives/php/end-symbol.html
http://blog.csdn.net/yanhui_wei/article/details/7951424
http://blog.csdn.net/wuyangbotianshi/article/details/41728091

Copyright (c) 2014 LittleHann All rights reserved