在 PHP 中禁用 echo 和 print

Disable echo and print in PHP

提问人:RobertPitt 提问时间:7/2/2011 最后编辑:PaulGRobertPitt 更新时间:8/4/2014 访问量:20165

问:

这似乎是一个有趣的问题,但实际上并非如此,我想禁用 ,以及其他可能输出到缓冲区的函数,例如 .echoprintreadfile

我想这样做的原因是为了防止客户端使用或超出我的应用程序的规则,迫使他们编译其内容并将其发送到输出类,以便管理整个缓冲区。echoprint

现在我知道我可以在脚本开始时设置一个输出缓冲区并丢弃任何内容,但这不包括 和 之类的内容,所以我的问题可以解释为如何控制响应头的缓冲区headerset_cookie

有没有可能的方法来管理PHP输出的所有方面,例如将回调分配给主缓冲区,而不仅仅是响应正文?

PHP 打印 缓冲区 回显

评论

0赞 Abdullah Jibaly 7/2/2011
这几乎就像您想为这些用户页面创建代理一样,但这可能会产生太多开销。在 Java 中,我会有一个过滤器拦截请求并替换响应对象。
0赞 bcoughlan 7/2/2011
它可能可以使用override_function来完成,但它需要 PECL apd 扩展。我说可能是因为我不确定 echo 是一个关键字还是一个函数。php.net/manual/en/function.override-function.php
1赞 icktoofay 7/2/2011
@waitinforatrain:这是一个关键词。
0赞 bcoughlan 7/2/2011
哦,好吧,PECL扩展和输出缓冲的组合就可以完成这项工作。
1赞 deceze 7/2/2011
我认为输出东西的方法比你能想到的和拦截的要多得多,所以任何解决方案无论如何都是一个不完美的解决方案。对我来说,丢弃输出缓冲区似乎是最好的策略。

答:

6赞 KingCrunch 7/2/2011 #1

最后,没有有效的方法可以实现这一点,因为至少不是一个函数,而是一个语言结构,不能被禁用。您可以使用输出缓冲(等),但这不会阻止其他代码再次禁用输出缓冲。echoob_start()

在我看来,没有办法,只能确保只有“好的代码”。我不知道,你说的“阻止客户端”是什么意思,但无论如何我都不会执行任意代码。如果它是由纪律严明的开发人员编写并经过测试的,那么应该没有问题。

评论

0赞 RobertPitt 7/2/2011
当我的应用程序发布时,开发人员将发布我无法控制的模块、库等,以迫使开发人员不要在网站所有者的上下文中造成错误,我正在考虑完全管理输出缓冲区,但似乎没有很好的方法来处理这个问题。
0赞 deceze 7/2/2011
@Robert 除非发明你自己的有限 DSL,否则你可能在任何情况下都无法限制开发人员。
0赞 KingCrunch 7/2/2011
@RobertPitt:听起来应该有更有效的质量管理。当他们无法阻止自己使用时,即使模块的开发人员指南告诉他们不要使用它,我也不知道这些模块有多好(不仅仅是东西)......一旦模块抛出错误(等等),它们就会被视为“有问题”。echoechoheaders already sent
1赞 Sabeen Malik 7/2/2011
@RobertPitt我不了解全部情况,但可以引入自动化模块质量控制系统。他们必须在模块上线之前获得批准。这个自动测试脚本可以检查脚本中的某些关键字,并告诉他们在将其标记为“已启用”之前修复问题,从而在一定程度上确保了“好代码”的想法
3赞 Bryan Agee 7/2/2011 #2

除了编辑和重新编译之外,我不相信您可以禁用输出的函数。对于绕过输出缓冲的函数,您的 SOL.

但是,您可以使用内联输出缓冲来控制非标头输出。最好的部分是它的嵌套功能:

ob_start();
echo 'The first output!',"\n";

ob_start();
echo 'The second output.';

$output2 = ob_get_clean();

ob_flush();

echo $output2;

将输出:

The first output!
The second output.

评论

0赞 RobertPitt 7/2/2011
如果您阅读我的问题,我确实说过我知道这一点,但这对“set_cookie”等功能没有影响
0赞 Bryan Agee 7/2/2011
如果你读了我的答案,你对这部分的唯一选择就是编辑和重新编译源代码。嵌套缓冲说明了其余部分的可能改进。我会澄清的。
2赞 MightyE 2/27/2012 #3

您想要做的事情 - 防止在运行时输出 - 是不可能的。它只是不会发生。不过,如果您执行以下两项操作,则可以非常接近:审核代码中是否有可以产生输出的关键字,以及缓冲输出,同时阻止访问输出缓冲区控制函数。

  1. 以编程方式审核代码,以确保不存在某些无法捕获的情况(由您来有效地缓存审核结果,或者只是在每次页面视图上承担审核成本 - 不推荐)。

您可以使用 token_get_all() 来审核输出关键字,例如 and(此处是所有可能的标记的列表)。不要试图在这里禁用对关键字以外的任何内容的访问,有太多方法可以欺骗它(变量、data:// 流等)您只在此处屏蔽了某些关键字。T_ECHOT_PRINTeval()

不要忘记 、 、 和 。这些不仅可以用于包含未经审计的代码(例如写入临时文件的 PHP 代码),而且使用一些更高级的文件包装器,这些代码本身可用于生成输出。T_INCLUDET_INCLUDE_ONCET_REQUIRET_REQUIRE_ONCE

  1. 使用 PHP ADB 扩展通过重命名某些方法来禁用对它们的访问。不要尝试禁用输出函数,它只是行不通,生成输出的方法太多了。挑选出像 和 这样的特殊方法,但对于实际输出,有无数种方法可以产生输出。阻止这种情况的唯一可靠方法是使用输出缓冲,但禁用对输出缓冲器控制方法的访问,以便它们无法绕过缓冲。set_cookie()header()

    class YourApplicationControllingClass {
    
        final protected function callUserCode($pathToUserCodeFile) {
            // We want this to be a local variable so there's no way to get to it
            // with PHP Reflection
            $suspender = new SuspendFunctions();
    
            ob_start();
    
            // You may need to add more here, this is just my superficial pass
            $suspender->suspend("ob_clean", "ob_end_clean", "ob_end_flush", "ob_flush", 
                    "ob_get_clean", "ob_get_contents", "ob_get_flush", "ob_get_length",
                    "ob_get_level", "ob_get_status", "ob_implicit_flush", "ob_list_handlers",
                    "ob_start", "output_add_rewrite_var", "output_reset_rewrite_vars",
                    "set_cookie", "set_raw_cookie", "header_register_callback", "header",
                    "header_remove", "http_response_code", "register_shutdown_function",
                    "register_tick_function", "unregister_tick_function", "set_error_handler",
                    "restore_error_handler", "set_exception_handler", "restore_exception_handler"
                    );
    
            $this->callUserCodeSandbox($pathToUserCodeFile);
            // Restore our original execution environment
            $suspender->resume();
    
            $content = ob_get_clean();
            // If you want to be aggressive, check to see if they produced any output
            // and blacklist them if they even try.
            if ($content !== '') $this->blacklistUserCode($pathToUserCodeFile);
        }
    
        private function callUserCodeSandbox($pathToUserCodeFile) {
            require($pathToUserCodeFile);
        }
    }
    
    final class SuspendFunctions {
        private $suspendedFunctions = array();
    
        /**
        * Suspends certain functions from being executable.
        * @param string $function,... Names of functions to suspend, you may pass multiple
        * @return void
        */
        function suspend($function) {
            $functions = func_get_args();
            foreach($functions as $function) {
                // Make sure we don't double-suspend a function
                if (isset($this->suspendedFunctions[$function])) continue;
    
                // Make new names unguessable, and make it very unlikely that we end up with a collision.
                $newName = '_'.md5($function.microtime(true).mt_random());
    
                // Rename to the unguessable name
                rename_function($function, $newName);
    
                // Keep a record for ourselves what this new name is so we can resume later
                $this->suspendedFunctions[$function] = $newName;
            }
        }
    
        /**
        * Resumes functions for calling
        */
        function resume() {
            foreach($this->suspendedFunctions as $function=>$newName) {
                rename($newName, $function);
                unset($this->suspendedFunctions[$function]);
            }
        }
    }
    

请注意,无论您在这方面有多擅长,几乎肯定会有一种方法可以绕过(例如,也许他们的代码会修补您的应用程序文件之一的内容以再次允许输出)。PHP太灵活了,无法牢牢地锁定它。PHP有一个类似的项目,叫做安全模式,他们最终放弃了这个项目,因为不可能完全安全地锁定所有东西。只要用户有一个完整的执行环境,你可以做任何事情来阻止他们,他们都可以撤消。一般来说,在不手动审核每一行的情况下执行用户贡献的代码是非常不明智的(甚至很危险)。

1赞 ob1y2k 8/4/2014 #4

这样做:

    function trace($message) {
      $echo = true;
      if ($echo)
      echo $message . "<br>";
    }

然后只需调用trace(“your message”);在需要时随时,$echo 切换到 false 以全局禁用它