diff --git a/hphp/runtime/ext/ext_string.cpp b/hphp/runtime/ext/ext_string.cpp index 093fff12678cb2..874ac405ff99c7 100644 --- a/hphp/runtime/ext/ext_string.cpp +++ b/hphp/runtime/ext/ext_string.cpp @@ -276,18 +276,49 @@ Variant f_hex2bin(const String& str) { return ret.detach(); } -extern const StaticString s_nl; - const StaticString - s_br("
\n"), - s_non_xhtml_br("
\n"); + s_br("
"), + s_non_xhtml_br("
"); String f_nl2br(const String& str, bool is_xhtml /* = true */) { + if (str.empty()) { + return str; + } + + String htmlType; if (is_xhtml) { - return string_replace(str, s_nl, s_br); + htmlType = s_br; } else { - return string_replace(str, s_nl, s_non_xhtml_br); + htmlType = s_non_xhtml_br; } + + return stringForEachBuffered(str.size(), str, + [&] (StringBuffer& ret, const char*& src, const char* end) { + // PHP treats a carriage return beside a newline as the same break + // no matter what order they're in. Don't do it for two of the same in + // a row, though... + switch (*src) { + case '\n': + ret.append(htmlType); + // skip next if carriage return + if (*(src + 1) == '\r') { + ret.append(*src); + ++src; + } + ret.append(*src); + break; + case '\r': + ret.append(htmlType); + // skip next if newline + if (*(src + 1) == '\n') { + ret.append(*src); + ++src; + } + /* fall through */ + default: + ret.append(*src); + } + }); } String f_quotemeta(const String& str) { diff --git a/hphp/test/quick/nl2br.php b/hphp/test/quick/nl2br.php new file mode 100644 index 00000000000000..50c730fe26b019 --- /dev/null +++ b/hphp/test/quick/nl2br.php @@ -0,0 +1,22 @@ +\nmy
\r\nfriend
\n\r" //case from issue +); +foreach ($stringList as $string) { + var_dump(escapeNewLine(nl2br($string, true))); + var_dump(escapeNewLine(nl2br($string, false))); +} diff --git a/hphp/test/quick/nl2br.php.expect b/hphp/test/quick/nl2br.php.expect new file mode 100644 index 00000000000000..ef07df642d4d84 --- /dev/null +++ b/hphp/test/quick/nl2br.php.expect @@ -0,0 +1,16 @@ +string(18) "Test
\nString" +string(16) "Test
\nString" +string(18) "Test
\rString" +string(16) "Test
\rString" +string(20) "Test
\n\rString" +string(18) "Test
\n\rString" +string(20) "Test
\r\nString" +string(18) "Test
\r\nString" +string(26) "Test
\n
\nString" +string(22) "Test
\n
\nString" +string(26) "Test
\r
\rString" +string(22) "Test
\r
\rString" +string(19) "Test String
\n" +string(17) "Test String
\n" +string(59) "Hello

\nmy

\r\nfriend

\n\r" +string(53) "Hello

\nmy

\r\nfriend

\n\r"