Repository: servo/rust-url (rust)

Issue:
The program crashed after using the "join" function.
- [ ] Note that this crate implements the [URL Standard](https://url.spec.whatwg.org/) not RFC 1738 or RFC 3986

**Describe the bug**
A clear and concise description of what the bug is. Include code snippets if possible.

This is the version of url:
```toml
[dependencies]
url = "=2.3.1"
```

This is the code that trigger bugs:
```rust
fn main() {
    let s1 = "fIlE:p:?../";
    let s2 = "../";
    let url = url::Url::parse(s1).unwrap();
    let _ = url::Url::join(&url, s2);
}
```

Run this code, we will get the error message below:
```
thread 'main' panicked at 'assertion failed: self.serialization.as_bytes()[segment_start - 1] == b\'/\'', /home/yxz/.cargo/registry/src/mirrors.ustc.edu.cn-61ef6e0cd06fb9b8/url-2.3.1/src/parser.rs:1227:21
```




Discussion:

Patch:
diff --git a/url/src/parser.rs b/url/src/parser.rs
--- a/url/src/parser.rs
+++ b/url/src/parser.rs
@@ -178,7 +178,7 @@ pub fn default_port(scheme: &str) -> Option<u16> {
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct Input<'i> {
     chars: str::Chars<'i>,
 }
@@ -1173,7 +1173,7 @@ impl<'a> Parser<'a> {
     ) -> Input<'i> {
         // Relative path state
         loop {
-            let segment_start = self.serialization.len();
+            let mut segment_start = self.serialization.len();
             let mut ends_with_slash = false;
             loop {
                 let input_before_c = input.clone();
@@ -1202,6 +1202,14 @@ impl<'a> Parser<'a> {
                     }
                     _ => {
                         self.check_url_code_point(c, &input);
+                        if scheme_type.is_file()
+                            && is_normalized_windows_drive_letter(
+                                &self.serialization[path_start + 1..],
+                            )
+                        {
+                            self.serialization.push('/');
+                            segment_start += 1;
+                        }
                         if self.context == Context::PathSegmentSetter {
                             if scheme_type.is_special() {
                                 self.serialization
@@ -1249,7 +1257,10 @@ impl<'a> Parser<'a> {
                 }
                 _ => {
                     // If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then
-                    if scheme_type.is_file() && is_windows_drive_letter(segment_before_slash) {
+                    if scheme_type.is_file()
+                        && segment_start == path_start + 1
+                        && is_windows_drive_letter(segment_before_slash)
+                    {
                         // Replace the second code point in buffer with U+003A (:).
                         if let Some(c) = segment_before_slash.chars().next() {
                             self.serialization.truncate(segment_start);


Test patch:
diff --git a/url/tests/unit.rs b/url/tests/unit.rs
--- a/url/tests/unit.rs
+++ b/url/tests/unit.rs
@@ -1262,3 +1262,39 @@ fn test_authority() {
         "%C3%A0lex:%C3%A0lex@xn--lex-8ka.xn--p1ai.example.com"
     );
 }
+
+#[test]
+/// https://github.com/servo/rust-url/issues/838
+fn test_file_with_drive() {
+    let s1 = "fIlE:p:?../";
+    let url = url::Url::parse(s1).unwrap();
+    assert_eq!(url.to_string(), "file:///p:?../");
+    assert_eq!(url.path(), "/p:");
+
+    let testcases = [
+        ("a", "file:///p:/a"),
+        ("", "file:///p:?../"),
+        ("?x", "file:///p:?x"),
+        (".", "file:///p:/"),
+        ("..", "file:///p:/"),
+        ("../", "file:///p:/"),
+    ];
+
+    for case in &testcases {
+        let url2 = url::Url::join(&url, case.0).unwrap();
+        assert_eq!(url2.to_string(), case.1);
+    }
+}
+
+#[test]
+/// Similar to test_file_with_drive, but with a path
+/// that could be confused for a drive.
+fn test_file_with_drive_and_path() {
+    let s1 = "fIlE:p:/x|?../";
+    let url = url::Url::parse(s1).unwrap();
+    assert_eq!(url.to_string(), "file:///p:/x|?../");
+    assert_eq!(url.path(), "/p:/x|");
+    let s2 = "a";
+    let url2 = url::Url::join(&url, s2).unwrap();
+    assert_eq!(url2.to_string(), "file:///p:/a");
+}