【问题标题】:curl follow location error卷曲跟随位置错误
【发布时间】:2011-01-31 11:16:39
【问题描述】:

我收到此错误消息:

当处于安全模式或设置了 open_basedir 时,无法激活 CURLOPT_FOLLOWLOCATION。

我的虚拟主机上的安全模式已关闭。

open_basedir 是 ""。

我该如何解决这个问题?

【问题讨论】:

标签: curl php


【解决方案1】:

解决方法是在 PHP 代码中实现重定向。

这是我自己的实现。它有两个已知的限制:

  1. 它将强制CURLOPT_RETURNTRANSFER
  2. CURLOPT_HEADERFUNCTION不兼容

代码:

function curl_exec_follow(/*resource*/ &$ch, /*int*/ $redirects = 20, /*bool*/ $curlopt_header = false) {
    if ((!ini_get('open_basedir') && !ini_get('safe_mode')) || $redirects < 1) {
        curl_setopt($ch, CURLOPT_HEADER, $curlopt_header);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $redirects > 0);
        curl_setopt($ch, CURLOPT_MAXREDIRS, $redirects);
        return curl_exec($ch);
    } else {
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FORBID_REUSE, false);

        do {
            $data = curl_exec($ch);
            if (curl_errno($ch))
                break;
            $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if ($code != 301 && $code != 302)
                break;
            $header_start = strpos($data, "\r\n")+2;
            $headers = substr($data, $header_start, strpos($data, "\r\n\r\n", $header_start)+2-$header_start);
            if (!preg_match("!\r\n(?:Location|URI): *(.*?) *\r\n!", $headers, $matches))
                break;
            curl_setopt($ch, CURLOPT_URL, $matches[1]);
        } while (--$redirects);
        if (!$redirects)
            trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING);
        if (!$curlopt_header)
            $data = substr($data, strpos($data, "\r\n\r\n")+4);
        return $data;
    }
}

【讨论】:

【解决方案2】:

打印此警告消息的唯一位置是 ext/curl/interface.c

if ((PG(open_basedir) && *PG(open_basedir)) || PG(safe_mode)) {
  if (Z_LVAL_PP(zvalue) != 0) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "CURLOPT_FOLLOWLOCATION cannot be activated when in safe_mode or an open_basedir is set");
    RETVAL_FALSE;
    return 1;
  }
}

从 if 条件中可以看出,必须启用 open_basedir 或 safe_mode。

【讨论】:

  • 有什么办法可以克服这个问题吗?像自定义跟随位置功能或类似的东西
  • 您可以使用curl_getinfo($ch, CURLINFO_HTTP_CODE),如果它返回 301 或 302,则获取 Location: 标头。
  • 这是 zsalab 的实现
【解决方案3】:

不久前我遇到了类似的情况,并在下面找到了解决方案。如果您通常知道您将被重定向到哪里,这可能对您有用。

    function curl($url, $postVars)
{
    $go = curl_init($url);
    curl_setopt ($go, CURLOPT_URL, $url);
    curl_setopt($go, CURLOPT_VERBOSE, 1);

    //follow on location problems
    if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off'))
    {
        curl_setopt ($go, CURLOPT_FOLLOWLOCATION, $l);
        $syn = curl_exec($go);
        if(curl_error($go))
            return false;
    }
    else
        $syn = curl_redir_exec($go, $postVars);
    curl_close($go);
    return $syn;
}

function curl_redir_exec($ch, $postVars)
{
    static $curl_loops = 0;
    static $curl_max_loops = 20;
    if ($curl_loops++>= $curl_max_loops)
    {
        $curl_loops = 0;
        return FALSE;
    }
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postVars);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);

    $data = curl_exec($ch);
    if(curl_error($ch))
        return false;
    list($header, $data) = explode("\n\r", $data, 2);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    $redirect_page = "[0-9]*.html";
    $base_redirect = "http://example.com/";

    if ($http_code == 301 || $http_code == 302)
    {
        $matches = array();
        $pregs = eregi($redirect_page, $data, $matches);
        $new_url = $base_redirect . $matches[0];
        if (!$new_url)
        {
            //couldn't process the url to redirect to
            $curl_loops = 0;
            return $data;
        }
        curl_setopt($ch, CURLOPT_URL, $new_url);

        return curl_redir_exec($ch, $postVars);
    }
    else
    {
        $curl_loops=0;
        return $data;
    }
}

【讨论】:

    【解决方案4】:

    请注意:

    此处代码的所有答案都手动解析 curl 请求中的标头以找到 Location: 标头。

    但是,从 PHP 5.3.7 开始,有一个选项 CURLINFO_REDIRECT_URL 可以与 curl_getinfo() 一起使用。无需两次请求,如果您不想要它们,则无需启用标头,无需正则表达式。

    【讨论】:

      【解决方案5】:

      从未在真实环境中测试过,但 curl_exec 具有更高的透明度(标题和返回传输选项没有问题)。

      function curl_exec_follow(/*resource*/ $ch, /*int*/ $maxredirect = 5) {
          if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
              curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
          } else {
              curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
              $newurl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
      
              $rch = curl_copy_handle($ch);
              curl_setopt($rch, CURLOPT_HEADER, true);
              curl_setopt($rch, CURLOPT_NOBODY, true);
              curl_setopt($rch, CURLOPT_RETURNTRANSFER, true);
              do {
                  curl_setopt($rch, CURLOPT_URL, $newurl);
                  $header = curl_exec($rch);
                  if (curl_errno($rch)) {
                      $code = 0;
                  } else {
                      $code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
                      if ($code == 301 || $code == 302) {
                          preg_match('/Location:(.*?)\n/', $header, $matches);
                          $newurl = trim(array_pop($matches));
                      } else {
                          $code = 0;
                      }
                  }
              } while ($code && $maxredirect--);
              curl_close($rch);
              curl_setopt($ch, CURLOPT_URL, $newurl);
          }
          return curl_exec($ch);
      }
      

      【讨论】:

      • 这个实现有两个问题:1.如果出错,会丢失; 2.最终URL的请求做了两次(一次只针对headers,一次完整)
      • ini_get('safe_mode' == 'Off') 错字(括号应该包含 'safe_mode',并且不能在任何地方使用)
      【解决方案6】:

      如果您已经配置了 $curl 实例,并且只想在启用 FOLLOWLOCATION 的情况下模拟 curl_exec,则可以使用此实例:

      function curl_follow_exec($curl, $url = null)
      {
          curl_setopt($curl, CURLOPT_HEADER, true);
          if (!is_null($url))
          {
              $opts = array (
                  CURLOPT_URL => $url,
                  CURLOPT_POST => false,
                  CURLOPT_PUT => false,
              );
              curl_setopt_array($curl, $opts);
          }
          $data = curl_exec($curl);
          $status = curl_getinfo($curl);
          $arr = explode("\r\n\r\n", $data);
          while (strpos(reset($arr), 'HTTP/1.1 100 Continue') !== false)
          {
              array_shift($arr);
          }
          $header = $arr[0];
          $body = implode("\r\n", array_slice($arr, 1));
          if ($status['http_code'] == 301 || $status['http_code'] == 302)
          {
              $matches = array ();
              preg_match("/(Location:|URI:)[^(\n)]*/", $header, $matches);
              $url = trim(str_replace($matches[1], "", $matches[0]));
              return curl_follow_exec($curl, $url);
          }
          return $body;
      }
      

      注意:如果你已经指定了选项,调用这个函数时不要提供URL,它只是用于递归目的。

      我从接受的答案中启发了这段代码,并添加了一些东西来管理多个标题。

      这个功能就像 ie6 的一个丑陋的 hack:如果可以的话,改变你的主机 :-)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-29
        • 2017-07-06
        • 2015-04-18
        • 2018-11-27
        • 1970-01-01
        • 2016-06-30
        相关资源
        最近更新 更多