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"