Просмотр исходного кода

More setters

Fix #154 (by adding `Url::push_path_segment`)
Simon Sapin 10 лет назад
Родитель
Сommit
f8f9176bd7
3 измененных файлов с 124 добавлено и 15 удалено
  1. 103 0
      src/lib.rs
  2. 19 13
      src/parser.rs
  3. 2 2
      src/percent_encoding.rs

+ 103 - 0
src/lib.rs

@@ -471,6 +471,109 @@ impl Url {
         }
         }
     }
     }
 
 
+    /// Remove the last segment of this URL’s path.
+    ///
+    /// If this URL is non-relative, do nothing and return `Err`.
+    pub fn pop_path_segment(&mut self) -> Result<(), ()> {
+        if self.non_relative() {
+            return Err(())
+        }
+        let last_slash;
+        let path_len;
+        {
+            let path = self.path();
+            last_slash = path.rfind('/').unwrap();
+            path_len = path.len();
+        };
+        if last_slash > 0 {
+            // Found a slash other than the initial one
+            let last_slash = last_slash + self.path_start as usize;
+            let path_end = path_len + self.path_start as usize;
+            unsafe {
+                self.serialization.as_mut_vec().drain(last_slash..path_end);
+            }
+            let offset = (path_end - last_slash) as u32;
+            if let Some(ref mut index) = self.query_start { *index -= offset }
+            if let Some(ref mut index) = self.fragment_start { *index -= offset }
+        }
+        Ok(())
+    }
+
+    /// Add a segment at the end of this URL’s path.
+    ///
+    /// If this URL is non-relative, do nothing and return `Err`.
+    pub fn push_path_segment(&mut self, segment: &str) -> Result<(), ()> {
+        if self.non_relative() {
+            return Err(())
+        }
+        let after_path = match (self.query_start, self.fragment_start) {
+            (Some(i), _) | (None, Some(i)) => {
+                let s = self.slice(i..).to_owned();
+                self.serialization.truncate(i as usize);
+                Some(s)
+            },
+            (None, None) => None
+        };
+        let scheme_type = parser::SchemeType::from(self.scheme());
+        let path_start = self.path_start as usize;
+        self.serialization.push('/');
+        self.mutate(|parser| {
+            parser.context = parser::Context::PathSegmentSetter;
+            let mut has_host = true;  // FIXME account for this?
+            parser.parse_path(scheme_type, &mut has_host, path_start, segment)
+        });
+        let offset = to_u32(self.serialization.len()).unwrap() - self.path_start;
+        if let Some(ref mut index) = self.query_start { *index += offset }
+        if let Some(ref mut index) = self.fragment_start { *index += offset }
+        if let Some(ref after_path) = after_path {
+            self.serialization.push_str(after_path)
+        }
+        Ok(())
+    }
+
+    /// Change this URL’s port number.
+    ///
+    /// If this URL is non-relative, does not have a host, or has the `file` scheme;
+    /// do nothing and return `Err`.
+    pub fn set_port(&mut self, mut port: Option<u16>) -> Result<(), ()> {
+        if self.non_relative() {
+            return Err(())
+        }
+        if port.is_some() && port == parser::default_port(self.scheme()) {
+            port = None
+        }
+        match (self.port, port) {
+            (None, None) => {}
+            (Some(_), None) => {
+                unsafe {
+                    self.serialization.as_mut_vec().drain(
+                        self.host_end as usize .. self.path_start as usize);
+                }
+                let offset = self.path_start - self.host_end;
+                self.path_start = self.host_end;
+                if let Some(ref mut index) = self.query_start { *index -= offset }
+                if let Some(ref mut index) = self.fragment_start { *index -= offset }
+            }
+            (Some(old), Some(new)) if old == new => {}
+            (_, Some(new)) => {
+                let path_and_after = self.slice(self.path_start..).to_owned();
+                self.serialization.truncate(self.host_end as usize);
+                write!(&mut self.serialization, ":{}", new).unwrap();
+                let old_path_start = self.path_start;
+                let new_path_start = to_u32(self.serialization.len()).unwrap();
+                self.path_start = new_path_start;
+                let adjust = |index: &mut u32| {
+                    *index -= old_path_start;
+                    *index += new_path_start;
+                };
+                if let Some(ref mut index) = self.query_start { adjust(index) }
+                if let Some(ref mut index) = self.fragment_start { adjust(index) }
+                self.serialization.push_str(&path_and_after);
+            }
+        }
+        Ok(())
+    }
+
     /// Change this URL’s host.
     /// Change this URL’s host.
     ///
     ///
     /// If this URL is non-relative, do nothing and return `Err`.
     /// If this URL is non-relative, do nothing and return `Err`.

+ 19 - 13
src/parser.rs

@@ -14,7 +14,8 @@ use super::{Url, EncodingOverride};
 use host::{self, HostInternal};
 use host::{self, HostInternal};
 use percent_encoding::{
 use percent_encoding::{
     utf8_percent_encode, percent_encode,
     utf8_percent_encode, percent_encode,
-    SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET
+    SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET,
+    PATH_SEGMENT_ENCODE_SET
 };
 };
 
 
 pub type ParseResult<T> = Result<T, ParseError>;
 pub type ParseResult<T> = Result<T, ParseError>;
@@ -79,7 +80,7 @@ impl SchemeType {
         matches!(*self, SchemeType::File)
         matches!(*self, SchemeType::File)
     }
     }
 
 
-    fn from(s: &str) -> Self {
+    pub fn from(s: &str) -> Self {
         match s {
         match s {
             "http" | "https" | "ws" | "wss" | "ftp" | "gopher" => SchemeType::SpecialNotFile,
             "http" | "https" | "ws" | "wss" | "ftp" | "gopher" => SchemeType::SpecialNotFile,
             "file" => SchemeType::File,
             "file" => SchemeType::File,
@@ -110,6 +111,7 @@ pub struct Parser<'a> {
 pub enum Context {
 pub enum Context {
     UrlParser,
     UrlParser,
     Setter,
     Setter,
+    PathSegmentSetter,
 }
 }
 
 
 impl<'a> Parser<'a> {
 impl<'a> Parser<'a> {
@@ -185,12 +187,11 @@ impl<'a> Parser<'a> {
             }
             }
         }
         }
         // EOF before ':'
         // EOF before ':'
-        match self.context {
-            Context::Setter => Ok(""),
-            Context::UrlParser => {
-                self.serialization.clear();
-                Err(())
-            }
+        if self.context == Context::Setter {
+            Ok("")
+        } else {
+            self.serialization.clear();
+            Err(())
         }
         }
     }
     }
 
 
@@ -746,9 +747,9 @@ impl<'a> Parser<'a> {
         self.parse_path(scheme_type, has_host, path_start, input)
         self.parse_path(scheme_type, has_host, path_start, input)
     }
     }
 
 
-    fn parse_path<'i>(&mut self, scheme_type: SchemeType, has_host: &mut bool,
-                      path_start: usize, input: &'i str)
-                      -> &'i str {
+    pub fn parse_path<'i>(&mut self, scheme_type: SchemeType, has_host: &mut bool,
+                          path_start: usize, input: &'i str)
+                          -> &'i str {
         // Relative path state
         // Relative path state
         debug_assert!(self.serialization.ends_with("/"));
         debug_assert!(self.serialization.ends_with("/"));
         let mut iter = input.char_ranges();
         let mut iter = input.char_ranges();
@@ -786,8 +787,13 @@ impl<'a> Parser<'a> {
                             }
                             }
                             iter = after_percent_sign
                             iter = after_percent_sign
                         }
                         }
-                        self.serialization.extend(utf8_percent_encode(
-                            &input[i..next_i], DEFAULT_ENCODE_SET));
+                        if self.context == Context::PathSegmentSetter {
+                            self.serialization.extend(utf8_percent_encode(
+                                &input[i..next_i], PATH_SEGMENT_ENCODE_SET));
+                        } else {
+                            self.serialization.extend(utf8_percent_encode(
+                                &input[i..next_i], DEFAULT_ENCODE_SET));
+                        }
                     }
                     }
                 }
                 }
             }
             }

+ 2 - 2
src/percent_encoding.rs

@@ -97,8 +97,8 @@ define_encode_set! {
 }
 }
 
 
 define_encode_set! {
 define_encode_set! {
-    /// This encode set is used for username and password.
-    pub PATH_SEGMENT_ENCODE_SET = [DEFAULT_ENCODE_SET] | {'%'}
+    /// This encode set is used for on '/'-separated path segment
+    pub PATH_SEGMENT_ENCODE_SET = [DEFAULT_ENCODE_SET] | {'%', '/'}
 }
 }
 
 
 define_encode_set! {
 define_encode_set! {