교체를 중지하려면 PHP를 얻으십시오. $ _get 또는 $ _post 배열의 문자?

StackOverflow https://stackoverflow.com/questions/68651

  •  09-06-2019
  •  | 
  •  

문제

PHP 변수를 전달하면 . $_GET PHP를 통해 이름을 다음으로 자동 대체합니다. _ 문자.예를 들어:

<?php
echo "url is ".$_SERVER['REQUEST_URI']."<p>";
echo "x.y is ".$_GET['x.y'].".<p>";
echo "x_y is ".$_GET['x_y'].".<p>";

...다음을 출력합니다:

url is /SpShipTool/php/testGetUrl.php?x.y=a.b
x.y is .
x_y is a.b.

...내 질문은 이것입니다 :거기 있어요? 어느 어떻게 하면 이걸 멈출 수 있을까?내가 무슨 짓을 해서 이런 일을 당할 자격이 있는지 평생 생각해 볼 수가 없어

내가 실행 중인 PHP 버전은 5.2.4-2ubuntu5.3입니다.

도움이 되었습니까?

해결책

왜 그렇게 하는지에 대한 PHP.net의 설명은 다음과 같습니다.

들어오는 변수 이름의 점

일반적으로 PHP는 변수가 스크립트에 전달 될 때 변수 이름을 변경하지 않습니다.그러나 DOT (주기, 전체 정지)는 PHP 변수 이름의 유효한 문자가 아닙니다.그 이유가 있으므로, 그것을보십시오.

<?php
$varname.ext;  /* invalid variable name */
?>

이제 파서가 보는 것은 $ varname이라는 변수이며, 문자열 연결 연산자가 뒤 따른다.알려진 키나 예약 된 단어와 일치하지 않는 인용되지 않은 문자열 'ext'.분명히 이것은 의도 된 결과가 없습니다.

이러한 이유로 PHP는 수신 변수 이름의 모든 점을 밑줄로 자동 교체한다는 점에 유의해야합니다.

그거에서 온거야 http://ca.php.net/variables.external.

또한에 따르면 이 댓글 다른 문자는 밑줄로 변환됩니다.

PHP가 _(밑줄)로 변환하는 필드 이름 문자의 전체 목록은 다음과 같습니다(단순한 점이 아님).

  • chr(32) ( ) (공백)
  • chr(46) (.) (점)
  • chr(91) ([) (여는 대괄호)
  • chr(128) - chr(159) (다양함)

따라서 문제가 있는 것 같으므로 다음을 사용하여 스크립트에서 밑줄을 다시 점으로 변환해야 합니다. 새벽의 제안 (저는 그냥 사용하겠습니다. str_replace 그렇지만.)

다른 팁

오랫동안 답변된 질문이지만 실제로 더 나은 답변(또는 해결 방법)이 있습니다.PHP를 사용하면 원시 입력 스트림, 다음과 같이 할 수 있습니다.

$query_string = file_get_contents('php://input');

쿼리 문자열 형식의 $_POST 배열과 마침표가 제공됩니다.

그런 다음 필요한 경우 구문 분석할 수 있습니다. 포스터 코멘트)

<?php
// Function to fix up PHP's messing up input containing dots, etc.
// `$source` can be either 'POST' or 'GET'
function getRealInput($source) {
    $pairs = explode("&", $source == 'POST' ? file_get_contents("php://input") : $_SERVER['QUERY_STRING']);
    $vars = array();
    foreach ($pairs as $pair) {
        $nv = explode("=", $pair);
        $name = urldecode($nv[0]);
        $value = urldecode($nv[1]);
        $vars[$name] = $value;
    }
    return $vars;
}

// Wrapper functions specifically for GET and POST:
function getRealGET() { return getRealInput('GET'); }
function getRealPOST() { return getRealInput('POST'); }
?>

''둘 다를 포함하는 OpenID 매개 변수에 매우 유용합니다. 그리고 각각 특정한 의미를 가진 '_'!

위의 댓글에서 Johan의 실제 답변을 강조했습니다. 저는 전체 게시물을 과도한 처리 없이 문제를 완전히 우회하는 최상위 배열로 래핑했습니다.

당신이하는 형태로

<input name="data[database.username]">  
<input name="data[database.password]">  
<input name="data[something.else.really.deep]">  

대신에

<input name="database.username"> 
<input name="database.password"> 
<input name="something.else.really.deep">  

포스트 핸들러에서 그냥 풀어보세요:

$posdata = $_POST['data'];

내 견해가 완전히 템플릿화되었기 때문에 이것은 두 줄의 변경이었습니다.

참고하세요.그룹화된 데이터 트리를 편집하기 위해 필드 이름에 점을 사용하고 있습니다.

이 기능의 작동 방식은 제가 2013년 여름방학 때 생각해낸 천재적인 해킹이었습니다.언젠가 이에 대한 블로그 게시물을 쓰겠습니다.

이 수정 사항은 보편적으로 작동하며 심층적인 배열 지원을 제공합니다. 예를 들어 a.a[x][b.a]=10.그것은 사용한다 parse_str() 일부 전처리를 통해 무대 뒤에서.

function fix($source) {
    $source = preg_replace_callback(
        '/(^|(?<=&))[^=[&]+/',
        function($key) { return bin2hex(urldecode($key[0])); },
        $source
    );

    parse_str($source, $post);

    $result = array();
    foreach ($post as $key => $val) {
        $result[hex2bin($key)] = $val;
    }
    return $result;
}

그런 다음 소스에 따라 이 함수를 다음과 같이 호출할 수 있습니다.

$_POST   = fix(file_get_contents('php://input'));
$_GET    = fix($_SERVER['QUERY_STRING']);
$_COOKIE = fix($_SERVER['HTTP_COOKIE']);

PHP 5.4 이하의 경우: 사용 base64_encode 대신에 bin2hex 그리고 base64_decode 대신에 hex2bin.

이는 마침표가 변수 이름에 유효하지 않은 문자이기 때문에 발생합니다. 이유 이는 PHP 구현에 매우 깊은 관련이 있으므로 (아직) 쉬운 수정은 없습니다.

그 동안에는 다음 방법으로 이 문제를 해결할 수 있습니다.

  1. 다음 중 하나를 통해 원시 쿼리 데이터에 액세스 php://input POST 데이터의 경우 또는 $_SERVER['QUERY_STRING'] GET 데이터의 경우
  2. 변환 기능을 사용합니다.

아래 변환 함수(PHP >= 5.4)는 각 키-값 쌍의 이름을 16진수 표현으로 인코딩한 다음 일반 작업을 수행합니다. parse_str();완료되면 16진수 이름을 원래 형식으로 되돌립니다.

function parse_qs($data)
{
    $data = preg_replace_callback('/(?:^|(?<=&))[^=[]+/', function($match) {
        return bin2hex(urldecode($match[0]));
    }, $data);

    parse_str($data, $values);

    return array_combine(array_map('hex2bin', array_keys($values)), $values);
}

// work with the raw query string
$data = parse_qs($_SERVER['QUERY_STRING']);

또는:

// handle posted data (this only works with application/x-www-form-urlencoded)
$data = parse_qs(file_get_contents('php://input'));

이 접근 방식은 Rok Kralj의 변경된 버전이지만 작동하고 효율성을 향상시키며(영향을 받지 않는 키에 대한 불필요한 콜백, 인코딩 및 디코딩 방지) 배열 키를 올바르게 처리하기 위해 약간의 조정이 필요합니다.

테스트의 요점 이용 가능하며 피드백이나 제안이 있으면 언제든지 환영합니다.

public function fix(&$target, $source, $keep = false) {                        
    if (!$source) {                                                            
        return;                                                                
    }                                                                          
    $keys = array();                                                           

    $source = preg_replace_callback(                                           
        '/                                                                     
        # Match at start of string or &                                        
        (?:^|(?<=&))                                                           
        # Exclude cases where the period is in brackets, e.g. foo[bar.blarg]
        [^=&\[]*                                                               
        # Affected cases: periods and spaces                                   
        (?:\.|%20)                                                             
        # Keep matching until assignment, next variable, end of string or   
        # start of an array                                                    
        [^=&\[]*                                                               
        /x',                                                                   
        function ($key) use (&$keys) {                                         
            $keys[] = $key = base64_encode(urldecode($key[0]));                
            return urlencode($key);                                            
        },                                                                     
    $source                                                                    
    );                                                                         

    if (!$keep) {                                                              
        $target = array();                                                     
    }                                                                          

    parse_str($source, $data);                                                 
    foreach ($data as $key => $val) {                                          
        // Only unprocess encoded keys                                      
        if (!in_array($key, $keys)) {                                          
            $target[$key] = $val;                                              
            continue;                                                          
        }                                                                      

        $key = base64_decode($key);                                            
        $target[$key] = $val;                                                  

        if ($keep) {                                                           
            // Keep a copy in the underscore key version                       
            $key = preg_replace('/(\.| )/', '_', $key);                        
            $target[$key] = $val;                                              
        }                                                                      
    }                                                                          
}                                                                              

이런 일이 발생하는 이유는 PHP의 이전 Register_globals 기능 때문입니다..문자는 변수 이름의 유효한 문자가 아니므로 PHP는 호환성을 확인하기 위해 밑줄로 변환합니다.

즉, URL 변수에 마침표를 사용하는 것은 좋은 습관이 아닙니다.

찾고 있다면 어느 로가는 길 문자 그대로 교체를 중지하려면 PHP를 얻으십시오. $ _get 또는 $ _post 배열의 문자는 그러한 방법 중 하나는 PHP의 소스를 수정하는 것입니다 (이 경우 비교적 간단합니다).

경고:PHP C 소스 수정은 고급 옵션입니다!

이것도 보세요 PHP 버그 보고서 이는 동일한 수정을 제안합니다.

탐색하려면 다음을 수행해야 합니다.

  • 다운로드 PHP의 C 소스 코드
  • 비활성화 . 교체 수표
  • ./구성, 만들다 사용자 정의된 PHP 빌드를 배포합니다.

소스 변경 자체는 사소하며 업데이트만 포함됩니다. 한 줄의 절반 ~에 main/php_variables.c:

....
/* ensure that we don't have spaces or dots in the variable name (not binary safe) */
for (p = var; *p; p++) {
    if (*p == ' ' /*|| *p == '.'*/) {
        *p='_';
....

메모:원본에 비해 || *p == '.' 주석 처리되었습니다.


예제 출력:

QUERY_STRING이 주어짐 a.a[]=bb&a.a[]=BB&c%20c=dd, 달리기 <?php print_r($_GET); 이제 다음을 생산합니다.

Array
(
    [a.a] => Array
        (
            [0] => bb
            [1] => BB
        )

    [c_c] => dd
)

노트:

  • 이 패치는 원래 질문만 해결합니다(공백이 아닌 점 교체를 중지함).
  • 이 패치에서 실행하는 것은 스크립트 수준 솔루션보다 빠르지만 순수 .php 답변은 여전히 ​​일반적으로 선호됩니다(PHP 자체를 변경하지 않기 때문입니다).
  • 이론적으로 여기에서는 폴리필 접근 방식이 가능하며 접근 방식을 결합할 수 있습니다. 다음을 사용하여 C 수준 변경을 테스트합니다. parse_str() (사용할 수 없는 경우) 더 느린 방법으로 대체합니다.

이 문제에 대한 내 해결책은 빠르고 지저분했지만 여전히 마음에 듭니다.나는 단지 양식에서 확인된 파일 이름 목록을 게시하고 싶었습니다.나는 사용했다 base64_encode 마크업 내에서 파일 이름을 인코딩한 다음 다음과 같이 디코딩했습니다. base64_decode 사용하기 전에.

Rok의 솔루션을 살펴본 후 아래 답변, 위의 crb 및 Rok의 솔루션의 제한 사항을 해결하는 버전을 생각해 냈습니다.참조 내 개선된 버전.


@crb의 답변 ~ 위에 좋은 시작이지만 몇 가지 문제가 있습니다.

  • 그것은 모든 것을 재처리하는데, 이는 과잉이다.""그 필드만이 ""만 있습니다. 이름으로 재 처리해야합니다.
  • 기본 PHP 처리와 동일한 방식으로 배열을 처리하지 못합니다."foo.bar[]"와 같은 키의 경우.

아래 솔루션은 이제 이러한 문제를 모두 해결합니다(원래 게시된 이후 업데이트되었습니다).이는 테스트에서 위의 답변보다 약 50% 빠르지만 데이터에 동일한 키(또는 동일하게 추출되는 키가 있는 상황은 처리하지 않습니다.foo.bar와 foo_bar는 모두 foo_bar로 추출됩니다.

<?php

public function fix2(&$target, $source, $keep = false) {                       
    if (!$source) {                                                            
        return;                                                                
    }                                                                          
    preg_match_all(                                                            
        '/                                                                     
        # Match at start of string or &                                        
        (?:^|(?<=&))                                                           
        # Exclude cases where the period is in brackets, e.g. foo[bar.blarg]
        [^=&\[]*                                                               
        # Affected cases: periods and spaces                                   
        (?:\.|%20)                                                             
        # Keep matching until assignment, next variable, end of string or   
        # start of an array                                                    
        [^=&\[]*                                                               
        /x',                                                                   
        $source,                                                               
        $matches                                                               
    );                                                                         

    foreach (current($matches) as $key) {                                      
        $key    = urldecode($key);                                             
        $badKey = preg_replace('/(\.| )/', '_', $key);                         

        if (isset($target[$badKey])) {                                         
            // Duplicate values may have already unset this                    
            $target[$key] = $target[$badKey];                                  

            if (!$keep) {                                                      
                unset($target[$badKey]);                                       
            }                                                                  
        }                                                                      
    }                                                                          
}                                                                              

아래에 포함된 함수 "getRealPostArray()"는 그다지 좋은 해결책은 아니지만 배열을 처리하고 두 이름을 모두 지원합니다."alpha_beta" 및 "alpha.beta":

  <input type='text' value='First-.' name='alpha.beta[a.b][]' /><br>
  <input type='text' value='Second-.' name='alpha.beta[a.b][]' /><br>
  <input type='text' value='First-_' name='alpha_beta[a.b][]' /><br>
  <input type='text' value='Second-_' name='alpha_beta[a.b][]' /><br>

반면 var_dump($_POST)는 다음을 생성합니다.

  'alpha_beta' => 
    array (size=1)
      'a.b' => 
        array (size=4)
          0 => string 'First-.' (length=7)
          1 => string 'Second-.' (length=8)
          2 => string 'First-_' (length=7)
          3 => string 'Second-_' (length=8)

var_dump( getRealPostArray())는 다음을 생성합니다.

  'alpha.beta' => 
    array (size=1)
      'a.b' => 
        array (size=2)
          0 => string 'First-.' (length=7)
          1 => string 'Second-.' (length=8)
  'alpha_beta' => 
    array (size=1)
      'a.b' => 
        array (size=2)
          0 => string 'First-_' (length=7)
          1 => string 'Second-_' (length=8)

그 가치에 대한 기능은 다음과 같습니다.

function getRealPostArray() {
  if ($_SERVER['REQUEST_METHOD'] !== 'POST') {#Nothing to do
      return null;
  }
  $neverANamePart = '~#~'; #Any arbitrary string never expected in a 'name'
  $postdata = file_get_contents("php://input");
  $post = [];
  $rebuiltpairs = [];
  $postraws = explode('&', $postdata);
  foreach ($postraws as $postraw) { #Each is a string like: 'xxxx=yyyy'
    $keyvalpair = explode('=',$postraw);
    if (empty($keyvalpair[1])) {
      $keyvalpair[1] = '';
    }
    $pos = strpos($keyvalpair[0],'%5B');
    if ($pos !== false) {
      $str1 = substr($keyvalpair[0], 0, $pos);
      $str2 = substr($keyvalpair[0], $pos);
      $str1 = str_replace('.',$neverANamePart,$str1);
      $keyvalpair[0] = $str1.$str2;
    } else {
      $keyvalpair[0] = str_replace('.',$neverANamePart,$keyvalpair[0]);
    }
    $rebuiltpair = implode('=',$keyvalpair);
    $rebuiltpairs[]=$rebuiltpair;
  }
  $rebuiltpostdata = implode('&',$rebuiltpairs);
  parse_str($rebuiltpostdata, $post);
  $fixedpost = [];
  foreach ($post as $key => $val) {
    $fixedpost[str_replace($neverANamePart,'.',$key)] = $val;
  }
  return $fixedpost;
}

crb를 사용하여 나는 $_POST 전체적으로 배열을 사용하더라도 클라이언트와 서버 모두에서 올바르게 인코딩하고 디코딩하고 있는지 확인해야 한다는 점을 명심하세요.캐릭터가 언제인지 이해하는 것이 중요합니다. 진심으로 유효하지 않으며 정말 그렇습니다 유효한.또한 사람들은 아직 그리고 언제나 클라이언트 데이터를 사용하기 전에 이스케이프 처리 어느 데이터베이스 명령 예외없이.

<?php
unset($_POST);
$_POST = array();
$p0 = explode('&',file_get_contents('php://input'));
foreach ($p0 as $key => $value)
{
 $p1 = explode('=',$value);
 $_POST[$p1[0]] = $p1[1];
 //OR...
 //$_POST[urldecode($p1[0])] = urldecode($p1[1]);
}
print_r($_POST);
?>

나는 이것을 개별적인 경우에만 사용하는 것이 좋습니다. 이것을 기본 헤더 파일의 맨 위에 두는 것의 부정적인 점에 대해 잘 모르겠습니다.

내 현재 솔루션(이전 주제 답변을 기반으로 함):

function parseQueryString($data)
{
    $data = rawurldecode($data);   
    $pattern = '/(?:^|(?<=&))[^=&\[]*[^=&\[]*/';       
    $data = preg_replace_callback($pattern, function ($match){
        return bin2hex(urldecode($match[0]));
    }, $data);
    parse_str($data, $values);

    return array_combine(array_map('hex2bin', array_keys($values)), $values);
}

$_GET = parseQueryString($_SERVER['QUERY_STRING']);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top