PHP 對輸入變量名的自動轉換的問題與源碼分析

原文地址:https://www.hongweipeng.com/i...php

起步

表單提交到PHP腳本時,底層的PHP會作一層轉換。將一些符號轉成下劃線 _java

20190701132308.png

實際上這層轉換中會發生不少意想不到的狀況。git

列舉這些狀況

20190701134005.png

一個簡單的測試就出現了意外,一個是單個 [ 也會被替換,對於 array 的輸入, key 不會作轉換。因而我多多測了一下,得出以下列表:github

<input name="a.b" />        轉爲: $_REQUEST["a_b"]
<input name="a b" />        轉爲: $_REQUEST["a_b"]
<input name="a[b" />        轉爲: $_REQUEST["a_b"]
<input name="a]b" />        轉爲: $_REQUEST["a]b"]
<input name="a-b" />        轉爲: $_REQUEST["a-b"]
<input name=" ab" />        轉爲: $_REQUEST["ab"]
<input name="ab " />        轉爲: $_REQUEST["ab "]
<input name="arr[a.b]" />   轉爲: $_REQUEST["arr"]["a.b"]
<input name="ar.r[a.b]" />  轉爲: $_REQUEST["ar_r"]["a.b"]
<input name="arr[a[b]]" />  轉爲: $_REQUEST["arr"]["a[b"]
<input name="arr[a[]x]" />  轉爲: $_REQUEST["arr"]["a["]
<input name="arr[]ab" />    轉爲: $_REQUEST["arr"][0]
<input name="arr[a]b" />    轉爲: $_REQUEST["arr"]["a"]
<input name="arr[a.b" />    轉爲: $_REQUEST["arr_a.b"]
<input name="arr[[a.b" />   轉爲: $_REQUEST["arr_[a.b"]

這個轉換機制十分詭異是吧。查了一下,在 Bug#77172 convert error on receiving variables from external sources 中提出了 id[]_text 轉換成 id[] 的問題,採起的結果是補全文檔上的說明。c#

另外也有幾個討論是否關閉這層轉換:數組

這三個 Request 都仍是 open 狀態,尚未結果,其中關於關閉轉換的討論早在06年就提出來了。我不清楚 PHP 爲何會作這個轉換,目的是什麼。據我所知的 java,Django 都不會作轉換的。函數

PHP對於外部輸入的變量都會轉換的,這就涉及到了 $_POST, $_GET, $_FILES, $_COOKIE, $_REQUEST 這些變量了。源碼分析

源碼分析

雖然我沒有閱讀過php源碼,在朋友的幫助下,關於這部分的轉換代碼在 main/php_variables.cphp_register_variable_ex 函數中 php_variables.c#L68 ,源碼精簡了下流程:post

PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array)
{
    char *p = NULL;
    char *ip = NULL;        /* index pointer */
    char *index;
    char *var, *var_orig;

    /* ignore leading spaces in the variable name */
    while (*var_name==' ') { // 忽略前置空格
        var_name++;
    }

    for (p = var; *p; p++) {
        if (*p == ' ' || *p == '.') {   // 空格和點替換成下劃線
            *p='_';
        } else if (*p == '[') {
            is_array = 1;     // 若是遇到 [ 則視爲數組,is_array 設爲1
            ip = p;
            *p = 0;
            break;
        }
    }
    ...
}

這裏能夠看出,忽略前置空格是最早作的動做;當遇到第一個 [ 時,php則認爲數數組,再也不進行轉換,設置了 is_array = 1 就 break 了。測試

這個 is_array 有什麼用呢,往下看:

if (is_array) {
    int nest_level = 0;
    while (1) {
        char *index_s;
        size_t new_idx_len = 0;
        ip++;    // [ 的下一個字符
        index_s = ip;
        if (*ip==']') { // 若是下一個字符就已是],表示沒有設置key
            index_s = NULL;
        } else {
            ip = strchr(ip, ']');  // 查找剩餘字符串中的 ]
            if (!ip) {
                /* PHP variables cannot contain '[' in their names, so we replace the character with a '_' */
                *(index_s - 1) = '_'; // 若是沒找到,則將 [ 替換成下劃線

                index_len = 0;
                if (index) {
                    index_len = strlen(index);
                }
                goto plain_var;
                return;
            }
            *ip = 0;
            new_idx_len = strlen(index_s);  // key 的長度到第一個出現 ] 爲止
        }
    }
    ...
}

到此,轉化處理的過程就很清晰了,對於數組狀況的變量名,分爲兩種:

  1. 沒找到 ] 與其匹配,該變量名不是數組,將 [ 替換成下劃線,後續字符串不作處理;
  2. ] 與其匹配,取到第一個出現 ] 的位置做爲 key ,捨棄後面的字符。

對於狀況1 就很奇怪了,若是輸入是 arr[[a.b 那麼就會轉成成 arr_[a.b 了。

總結

鑑於當前的轉換規則總結的規律以下:

  1. 在第一個 [ 以前的字符中,忽略前置的空格,將 .空格 替換成下劃線 _
  2. 在第一個 [ 以後的字符,再也不進行替換處理:

    • 若後續字符中沒有 ] 時,第一個 [ 替換成 _ ,後續字符串不作轉換;
    • 若後續字符中 ] 時,取到第一次出現 ] 的位置做爲 key,捨棄後續字符。

另外,誰能告訴我PHP的這層轉換的設計初衷是什麼啊。

相關文章
相關標籤/搜索