1 // Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9 
10 //! Immutable strings.
11 
12 pub use core_foundation_sys::string::*;
13 
14 use base::{CFIndexConvertible, TCFType};
15 
16 use core_foundation_sys::base::{Boolean, CFIndex, CFRange};
17 use core_foundation_sys::base::{kCFAllocatorDefault, kCFAllocatorNull};
18 use std::borrow::Cow;
19 use std::fmt;
20 use std::str::{self, FromStr};
21 use std::ptr;
22 use std::ffi::CStr;
23 
24 
25 declare_TCFType!{
26     /// An immutable string in one of a variety of encodings.
27     CFString, CFStringRef
28 }
29 impl_TCFType!(CFString, CFStringRef, CFStringGetTypeID);
30 
31 impl FromStr for CFString {
32     type Err = ();
33 
34     /// See also CFString::new for a variant of this which does not return a Result
35     #[inline]
from_str(string: &str) -> Result<CFString, ()>36     fn from_str(string: &str) -> Result<CFString, ()> {
37         Ok(CFString::new(string))
38     }
39 }
40 
41 impl<'a> From<&'a str> for CFString {
42     #[inline]
from(string: &'a str) -> CFString43     fn from(string: &'a str) -> CFString {
44         CFString::new(string)
45     }
46 }
47 
48 impl<'a> From<&'a CFString> for Cow<'a, str> {
from(cf_str: &'a CFString) -> Cow<'a, str>49     fn from(cf_str: &'a CFString) -> Cow<'a, str> {
50         unsafe {
51             // Do this without allocating if we can get away with it
52             let c_string = CFStringGetCStringPtr(cf_str.0, kCFStringEncodingUTF8);
53             if !c_string.is_null() {
54                 let c_str = CStr::from_ptr(c_string);
55                 Cow::Borrowed(str::from_utf8_unchecked(c_str.to_bytes()))
56             } else {
57                 let char_len = cf_str.char_len();
58 
59                 // First, ask how big the buffer ought to be.
60                 let mut bytes_required: CFIndex = 0;
61                 CFStringGetBytes(cf_str.0,
62                                  CFRange { location: 0, length: char_len },
63                                  kCFStringEncodingUTF8,
64                                  0,
65                                  false as Boolean,
66                                  ptr::null_mut(),
67                                  0,
68                                  &mut bytes_required);
69 
70                 // Then, allocate the buffer and actually copy.
71                 let mut buffer = vec![b'\x00'; bytes_required as usize];
72 
73                 let mut bytes_used: CFIndex = 0;
74                 let chars_written = CFStringGetBytes(cf_str.0,
75                                                      CFRange { location: 0, length: char_len },
76                                                      kCFStringEncodingUTF8,
77                                                      0,
78                                                      false as Boolean,
79                                                      buffer.as_mut_ptr(),
80                                                      buffer.len().to_CFIndex(),
81                                                      &mut bytes_used);
82                 assert_eq!(chars_written, char_len);
83 
84                 // This is dangerous; we over-allocate and null-terminate the string (during
85                 // initialization).
86                 assert_eq!(bytes_used, buffer.len().to_CFIndex());
87                 Cow::Owned(String::from_utf8_unchecked(buffer))
88             }
89         }
90     }
91 }
92 
93 impl fmt::Display for CFString {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result94     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
95         fmt.write_str(&Cow::from(self))
96     }
97 }
98 
99 impl fmt::Debug for CFString {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result100     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101         write!(f, "\"{}\"", self)
102     }
103 }
104 
105 
106 impl CFString {
107     /// Creates a new `CFString` instance from a Rust string.
108     #[inline]
new(string: &str) -> CFString109     pub fn new(string: &str) -> CFString {
110         unsafe {
111             let string_ref = CFStringCreateWithBytes(kCFAllocatorDefault,
112                                                      string.as_ptr(),
113                                                      string.len().to_CFIndex(),
114                                                      kCFStringEncodingUTF8,
115                                                      false as Boolean);
116             CFString::wrap_under_create_rule(string_ref)
117         }
118     }
119 
120     /// Like `CFString::new`, but references a string that can be used as a backing store
121     /// by virtue of being statically allocated.
122     #[inline]
from_static_string(string: &'static str) -> CFString123     pub fn from_static_string(string: &'static str) -> CFString {
124         unsafe {
125             let string_ref = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
126                                                            string.as_ptr(),
127                                                            string.len().to_CFIndex(),
128                                                            kCFStringEncodingUTF8,
129                                                            false as Boolean,
130                                                            kCFAllocatorNull);
131             TCFType::wrap_under_create_rule(string_ref)
132         }
133     }
134 
135     /// Returns the number of characters in the string.
136     #[inline]
char_len(&self) -> CFIndex137     pub fn char_len(&self) -> CFIndex {
138         unsafe {
139             CFStringGetLength(self.0)
140         }
141     }
142 }
143 
144 impl<'a> PartialEq<&'a str> for CFString {
eq(&self, other: &&str) -> bool145     fn eq(&self, other: &&str) -> bool {
146         unsafe {
147             let temp = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault,
148                                                            other.as_ptr(),
149                                                            other.len().to_CFIndex(),
150                                                            kCFStringEncodingUTF8,
151                                                            false as Boolean,
152                                                            kCFAllocatorNull);
153             self.eq(&CFString::wrap_under_create_rule(temp))
154         }
155     }
156 }
157 
158 impl<'a> PartialEq<CFString> for &'a str {
159     #[inline]
eq(&self, other: &CFString) -> bool160     fn eq(&self, other: &CFString) -> bool {
161         other.eq(self)
162     }
163 }
164 
165 impl PartialEq<CFString> for String {
166     #[inline]
eq(&self, other: &CFString) -> bool167     fn eq(&self, other: &CFString) -> bool {
168         other.eq(&self.as_str())
169     }
170 }
171 
172 impl PartialEq<String> for CFString {
173     #[inline]
eq(&self, other: &String) -> bool174     fn eq(&self, other: &String) -> bool {
175         self.eq(&other.as_str())
176     }
177 }
178 
179 #[test]
str_cmp()180 fn str_cmp() {
181     let cfstr = CFString::new("hello");
182     assert_eq!("hello", cfstr);
183     assert_eq!(cfstr, "hello");
184     assert_ne!(cfstr, "wrong");
185     assert_ne!("wrong", cfstr);
186     let hello = String::from("hello");
187     assert_eq!(hello, cfstr);
188     assert_eq!(cfstr, hello);
189 }
190 
191 #[test]
string_and_back()192 fn string_and_back() {
193     let original = "The quick brown fox jumped over the slow lazy dog.";
194     let cfstr = CFString::from_static_string(original);
195     let converted = cfstr.to_string();
196     assert_eq!(converted, original);
197 }
198