Repository: tafia/quick-xml (rust)

Issue:
Incorrect example of temporary disabling `check_end_names`
The example
https://github.com/tafia/quick-xml/blob/92de42ee15149a683357e5e690bc01229c6b82ec/src/reader/slice_reader.rs#L203-L217
for [`read_text`](https://docs.rs/quick-xml/latest/quick_xml/reader/struct.Reader.html#method.read_text) is incorrect -- doing that will leave reader in incorrect state and you'll get a wrong `EndEventMismatch` error next time when you'll read surrounding `End` event:

```xml
<some-tag>
  <event>
    <!-- You decided to .check_end_names(false) after seeing "<event>" -->
    ...
  </event>
  <!-- You decided to .check_end_names(true) after seeing "</event>" -->
</some-tag><!-- EndEventMismatch { expected "event", found: "some-tag" } -->
```

Pull request description:
Fix incorrect behaviour of `сheck_end_names` after disabling and enabling
Since now are always track stack of names, regardless of `сheck_end_names` setting. This is also fixes an error when you start from disabled setting, read some open tag and then enable reporting. After this PR that error will be correctly reported if closed tag won't match opened.

Fixes #514

Patch:
diff --git a/Changelog.md b/Changelog.md
index 0e66701..b205c9a 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -26,6 +26,8 @@
   <B/>
   <C/>
   ```
+- [#514]: Fix wrong reporting `Error::EndEventMismatch` after disabling and enabling
+  `.check_end_names`
 
 ### Misc Changes
 
@@ -53,6 +55,7 @@
 
 [#490]: https://github.com/tafia/quick-xml/pull/490
 [#500]: https://github.com/tafia/quick-xml/issues/500
+[#514]: https://github.com/tafia/quick-xml/issues/514
 [XML name]: https://www.w3.org/TR/xml11/#NT-Name
 [documentation]: https://docs.rs/quick-xml/0.27.0/quick_xml/de/index.html#difference-between-text-and-value-special-names
 
diff --git a/src/reader/parser.rs b/src/reader/parser.rs
index dc5e37a..38b6f49 100644
--- a/src/reader/parser.rs
+++ b/src/reader/parser.rs
@@ -142,31 +142,40 @@ impl Parser {
         } else {
             &buf[1..]
         };
-        if self.check_end_names {
-            let decoder = self.decoder();
-            let mismatch_err = |expected: String, found: &[u8], offset: &mut usize| {
-                *offset -= buf.len();
-                Err(Error::EndEventMismatch {
-                    expected,
-                    found: decoder.decode(found).unwrap_or_default().into_owned(),
-                })
-            };
-            match self.opened_starts.pop() {
-                Some(start) => {
+
+        let decoder = self.decoder();
+        let mismatch_err = |expected: String, found: &[u8], offset: &mut usize| {
+            *offset -= buf.len();
+            Err(Error::EndEventMismatch {
+                expected,
+                found: decoder.decode(found).unwrap_or_default().into_owned(),
+            })
+        };
+
+        // Get the index in self.opened_buffer of the name of the last opened tag
+        match self.opened_starts.pop() {
+            Some(start) => {
+                if self.check_end_names {
                     let expected = &self.opened_buffer[start..];
                     if name != expected {
                         let expected = decoder.decode(expected).unwrap_or_default().into_owned();
-                        mismatch_err(expected, name, &mut self.offset)
-                    } else {
+                        // #513: In order to allow error recovery we should drop content of the buffer
                         self.opened_buffer.truncate(start);
-                        Ok(Event::End(BytesEnd::wrap(name.into())))
+
+                        return mismatch_err(expected, name, &mut self.offset);
                     }
                 }
-                None => mismatch_err("".to_string(), &buf[1..], &mut self.offset),
+
+                self.opened_buffer.truncate(start);
+            }
+            None => {
+                if self.check_end_names {
+                    return mismatch_err("".to_string(), &buf[1..], &mut self.offset);
+                }
             }
-        } else {
-            Ok(Event::End(BytesEnd::wrap(name.into())))
         }
+
+        Ok(Event::End(BytesEnd::wrap(name.into())))
     }
 
     /// reads `BytesElement` starting with a `?`,
@@ -212,10 +221,11 @@ impl Parser {
                 Ok(Event::Empty(BytesStart::wrap(&buf[..len - 1], end)))
             }
         } else {
-            if self.check_end_names {
-                self.opened_starts.push(self.opened_buffer.len());
-                self.opened_buffer.extend(&buf[..name_end]);
-            }
+            // #514: Always store names event when .check_end_names == false,
+            // because checks can be temporary disabled and when they would be
+            // enabled, we should have that information
+            self.opened_starts.push(self.opened_buffer.len());
+            self.opened_buffer.extend(&buf[..name_end]);
             Ok(Event::Start(BytesStart::wrap(buf, name_end)))
         }
     }
diff --git a/src/se/element.rs b/src/se/element.rs
index 5703329..995fa30 100644
--- a/src/se/element.rs
+++ b/src/se/element.rs
@@ -339,7 +339,7 @@ impl<'k, W: Write> Struct<'k, W> {
     /// If `key` has a magic value [`TEXT_KEY`], then `value` serialized as a
     /// [simple type].
     ///
-    /// If `key` has a magic value [`CONTENT_KEY`], then `value` serialized as a
+    /// If `key` has a magic value [`VALUE_KEY`], then `value` serialized as a
     /// [content] without wrapping in tags, otherwise it is wrapped in
     /// `<${key}>...</${key}>`.
     ///


Test patch:
diff --git a/tests/issues.rs b/tests/issues.rs
new file mode 100644
index 0000000..9e4230d
--- /dev/null
+++ b/tests/issues.rs
@@ -0,0 +1,69 @@
+//! Regression tests found in various issues
+//!
+//! Name each test as `issue<GH number>`
+
+use quick_xml::events::{BytesStart, Event};
+use quick_xml::reader::Reader;
+use quick_xml::Error;
+
+/// Regression test for https://github.com/tafia/quick-xml/issues/514
+mod issue514 {
+    use super::*;
+    use pretty_assertions::assert_eq;
+
+    /// Check that there is no unexpected error
+    #[test]
+    fn no_mismatch() {
+        let mut reader = Reader::from_str("<some-tag><html>...</html></some-tag>");
+
+        let outer_start = BytesStart::new("some-tag");
+        let outer_end = outer_start.to_end().into_owned();
+
+        let html_start = BytesStart::new("html");
+        let html_end = html_start.to_end().into_owned();
+
+        assert_eq!(reader.read_event().unwrap(), Event::Start(outer_start));
+        assert_eq!(reader.read_event().unwrap(), Event::Start(html_start));
+
+        reader.check_end_names(false);
+
+        assert_eq!(reader.read_text(html_end.name()).unwrap(), "...");
+
+        reader.check_end_names(true);
+
+        assert_eq!(reader.read_event().unwrap(), Event::End(outer_end));
+        assert_eq!(reader.read_event().unwrap(), Event::Eof);
+    }
+
+    /// Canary check that legitimate error is reported
+    #[test]
+    fn mismatch() {
+        let mut reader = Reader::from_str("<some-tag><html>...</html></other-tag>");
+
+        let outer_start = BytesStart::new("some-tag");
+
+        let html_start = BytesStart::new("html");
+        let html_end = html_start.to_end().into_owned();
+
+        assert_eq!(reader.read_event().unwrap(), Event::Start(outer_start));
+        assert_eq!(reader.read_event().unwrap(), Event::Start(html_start));
+
+        reader.check_end_names(false);
+
+        assert_eq!(reader.read_text(html_end.name()).unwrap(), "...");
+
+        reader.check_end_names(true);
+
+        match reader.read_event() {
+            Err(Error::EndEventMismatch { expected, found }) => {
+                assert_eq!(expected, "some-tag");
+                assert_eq!(found, "other-tag");
+            }
+            x => panic!(
+                r#"Expected `Err(EndEventMismatch("some-tag", "other-tag")))`, but found {:?}"#,
+                x
+            ),
+        }
+        assert_eq!(reader.read_event().unwrap(), Event::Eof);
+    }
+}