fluent_uri/component.rs
1//! URI/IRI components.
2
3use crate::{
4 imp::{AuthMeta, HostMeta},
5 pct_enc::{
6 encoder::{IRegName, IUserinfo, Port, RegName, Userinfo},
7 table, EStr, Encoder,
8 },
9};
10use core::{hash, iter, marker::PhantomData, num::ParseIntError};
11use ref_cast::{ref_cast_custom, RefCastCustom};
12
13#[cfg(feature = "net")]
14use crate::net::{Ipv4Addr, Ipv6Addr};
15
16#[cfg(all(feature = "net", feature = "std"))]
17use std::{
18 io,
19 net::{SocketAddr, ToSocketAddrs},
20};
21
22/// An authority component for IRI.
23pub type IAuthority<'a> = Authority<'a, IUserinfo, IRegName>;
24
25/// A parsed host component for IRI.
26pub type IHost<'a> = Host<'a, IRegName>;
27
28/// A [scheme] component.
29///
30/// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
31///
32/// # Comparison
33///
34/// `Scheme`s are compared case-insensitively. You should do a case-insensitive
35/// comparison if the scheme specification allows both letter cases in the scheme name.
36///
37/// # Examples
38///
39/// ```
40/// use fluent_uri::{component::Scheme, Uri};
41///
42/// const SCHEME_HTTP: &Scheme = Scheme::new_or_panic("http");
43///
44/// let scheme = Uri::parse("HTTP://EXAMPLE.COM/")?.scheme();
45///
46/// // Case-insensitive comparison.
47/// assert_eq!(scheme, SCHEME_HTTP);
48/// // Case-sensitive comparison.
49/// assert_eq!(scheme.as_str(), "HTTP");
50/// # Ok::<_, fluent_uri::ParseError>(())
51/// ```
52#[derive(RefCastCustom)]
53#[repr(transparent)]
54pub struct Scheme {
55 inner: str,
56}
57
58const ASCII_CASE_MASK: u8 = 0b0010_0000;
59
60impl Scheme {
61 #[ref_cast_custom]
62 #[inline]
63 pub(crate) const fn new_validated(scheme: &str) -> &Self;
64
65 /// Converts a string slice to `&Scheme`.
66 ///
67 /// # Panics
68 ///
69 /// Panics if the string is not a valid scheme name according to
70 /// [Section 3.1 of RFC 3986][scheme]. For a non-panicking variant,
71 /// use [`new`](Self::new).
72 ///
73 /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
74 #[inline]
75 #[must_use]
76 pub const fn new_or_panic(s: &str) -> &Self {
77 match Self::new(s) {
78 Some(scheme) => scheme,
79 None => panic!("invalid scheme"),
80 }
81 }
82
83 /// Converts a string slice to `&Scheme`, returning `None` if the conversion fails.
84 #[inline]
85 #[must_use]
86 pub const fn new(s: &str) -> Option<&Self> {
87 if matches!(s.as_bytes(), [first, rem @ ..]
88 if first.is_ascii_alphabetic() && table::SCHEME.validate(rem))
89 {
90 Some(Self::new_validated(s))
91 } else {
92 None
93 }
94 }
95
96 /// Returns the scheme component as a string slice.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use fluent_uri::Uri;
102 ///
103 /// let uri = Uri::parse("http://example.com/")?;
104 /// assert_eq!(uri.scheme().as_str(), "http");
105 /// let uri = Uri::parse("HTTP://EXAMPLE.COM/")?;
106 /// assert_eq!(uri.scheme().as_str(), "HTTP");
107 /// # Ok::<_, fluent_uri::ParseError>(())
108 /// ```
109 #[inline]
110 #[must_use]
111 pub fn as_str(&self) -> &str {
112 &self.inner
113 }
114}
115
116macro_rules! default_port {
117 ($($name:literal, $bname:literal => $port:literal, rfc($rfc:literal))*) => {
118 impl Scheme {
119 /// Returns the optional default port of the scheme if it is
120 /// registered [at IANA][iana] with a permanent status.
121 ///
122 /// [iana]: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
123 ///
124 /// The following table lists all schemes concerned, their default ports, and references:
125 ///
126 /// | Scheme | Port | Reference |
127 /// | - | - | - |
128 $(#[doc = concat!("| ", $name, " | ", $port, " | [RFC ", $rfc, "](https://datatracker.ietf.org/doc/html/rfc", $rfc, ") |")])*
129 #[must_use]
130 pub fn default_port(&self) -> Option<u16> {
131 const MAX_LEN: usize = {
132 let mut res = 0;
133 $(
134 if $name.len() > res {
135 res = $name.len();
136 }
137 )*
138 res
139 };
140
141 let len = self.inner.len();
142 if len > MAX_LEN {
143 return None;
144 }
145
146 let mut buf = [0; MAX_LEN];
147 for (i, b) in self.inner.bytes().enumerate() {
148 buf[i] = b | ASCII_CASE_MASK;
149 }
150
151 match &buf[..len] {
152 $($bname => Some($port),)*
153 _ => None,
154 }
155 }
156 }
157 };
158}
159
160default_port! {
161 "aaa", b"aaa" => 3868, rfc(6733)
162 "aaas", b"aaas" => 5658, rfc(6733)
163 "acap", b"acap" => 674, rfc(2244)
164 "cap", b"cap" => 1026, rfc(4324)
165 "coap", b"coap" => 5683, rfc(7252)
166 "coap+tcp", b"coap+tcp" => 5683, rfc(8323)
167 "coap+ws", b"coap+ws" => 80, rfc(8323)
168 "coaps", b"coaps" => 5684, rfc(7252)
169 "coaps+tcp", b"coaps+tcp" => 5684, rfc(8323)
170 "coaps+ws", b"coaps+ws" => 443, rfc(8323)
171 "dict", b"dict" => 2628, rfc(2229)
172 "dns", b"dns" => 53, rfc(4501)
173 "ftp", b"ftp" => 21, rfc(1738)
174 "go", b"go" => 1096, rfc(3368)
175 "gopher", b"gopher" => 70, rfc(4266)
176 "http", b"http" => 80, rfc(9110)
177 "https", b"https" => 443, rfc(9110)
178 "icap", b"icap" => 1344, rfc(3507)
179 "imap", b"imap" => 143, rfc(5092)
180 "ipp", b"ipp" => 631, rfc(3510)
181 "ipps", b"ipps" => 631, rfc(7472)
182 "ldap", b"ldap" => 389, rfc(4516)
183 "mtqp", b"mtqp" => 1038, rfc(3887)
184 "mupdate", b"mupdate" => 3905, rfc(3656)
185 "nfs", b"nfs" => 2049, rfc(2224)
186 "nntp", b"nntp" => 119, rfc(5538)
187 "pop", b"pop" => 110, rfc(2384)
188 "rtsp", b"rtsp" => 554, rfc(7826)
189 "rtsps", b"rtsps" => 322, rfc(7826)
190 "rtspu", b"rtspu" => 554, rfc(2326)
191 "snmp", b"snmp" => 161, rfc(4088)
192 "stun", b"stun" => 3478, rfc(7064)
193 "stuns", b"stuns" => 5349, rfc(7064)
194 "telnet", b"telnet" => 23, rfc(4248)
195 "tip", b"tip" => 3372, rfc(2371)
196 "tn3270", b"tn3270" => 23, rfc(6270)
197 "turn", b"turn" => 3478, rfc(7065)
198 "turns", b"turns" => 5349, rfc(7065)
199 "vemmi", b"vemmi" => 575, rfc(2122)
200 "vnc", b"vnc" => 5900, rfc(7869)
201 "ws", b"ws" => 80, rfc(6455)
202 "wss", b"wss" => 443, rfc(6455)
203 "z39.50r", b"z39.50r" => 210, rfc(2056)
204 "z39.50s", b"z39.50s" => 210, rfc(2056)
205}
206
207impl PartialEq for Scheme {
208 #[inline]
209 fn eq(&self, other: &Self) -> bool {
210 let (a, b) = (self.inner.as_bytes(), other.inner.as_bytes());
211 // The only characters allowed in a scheme are alphabets, digits, '+', '-' and '.'.
212 // Their ASCII codes allow us to simply set the sixth bits and compare.
213 a.len() == b.len()
214 && iter::zip(a, b).all(|(x, y)| x | ASCII_CASE_MASK == y | ASCII_CASE_MASK)
215 }
216}
217
218impl Eq for Scheme {}
219
220impl hash::Hash for Scheme {
221 fn hash<H: hash::Hasher>(&self, state: &mut H) {
222 let mut buf = [0; 8];
223 for chunk in self.inner.as_bytes().chunks(8) {
224 let len = chunk.len();
225 for i in 0..len {
226 buf[i] = chunk[i] | ASCII_CASE_MASK;
227 }
228 state.write(&buf[..len]);
229 }
230 }
231}
232
233#[derive(Clone, Copy)]
234struct AuthorityInner<'a> {
235 val: &'a str,
236 meta: AuthMeta,
237}
238
239impl<'a> AuthorityInner<'a> {
240 fn userinfo(&self) -> Option<&'a EStr<IUserinfo>> {
241 let host_start = self.meta.host_bounds.0;
242 (host_start != 0).then(|| EStr::new_validated(&self.val[..host_start - 1]))
243 }
244
245 fn host(&self) -> &'a str {
246 let (start, end) = self.meta.host_bounds;
247 &self.val[start..end]
248 }
249
250 fn port(&self) -> Option<&'a EStr<Port>> {
251 let host_end = self.meta.host_bounds.1;
252 (host_end != self.val.len()).then(|| EStr::new_validated(&self.val[host_end + 1..]))
253 }
254
255 fn port_to_u16(&self) -> Result<Option<u16>, ParseIntError> {
256 self.port()
257 .filter(|s| !s.is_empty())
258 .map(|s| s.as_str().parse())
259 .transpose()
260 }
261
262 #[cfg(all(feature = "net", feature = "std"))]
263 fn socket_addrs(&self, default_port: u16) -> io::Result<impl Iterator<Item = SocketAddr>> {
264 use std::vec;
265
266 let port = self
267 .port_to_u16()
268 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid port value"))?
269 .unwrap_or(default_port);
270
271 match self.meta.host_meta {
272 HostMeta::Ipv4(addr) => Ok(vec![(addr, port).into()].into_iter()),
273 HostMeta::Ipv6(addr) => Ok(vec![(addr, port).into()].into_iter()),
274 HostMeta::IpvFuture => Err(io::Error::new(
275 io::ErrorKind::InvalidInput,
276 "address mechanism not supported",
277 )),
278 HostMeta::RegName => {
279 let name = EStr::<IRegName>::new_validated(self.host());
280 let name = name.decode().to_string().map_err(|_| {
281 io::Error::new(
282 io::ErrorKind::InvalidInput,
283 "registered name does not decode to valid UTF-8",
284 )
285 })?;
286 (&name[..], port).to_socket_addrs()
287 }
288 }
289 }
290}
291
292/// An [authority] component.
293///
294/// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
295#[derive(Clone, Copy)]
296pub struct Authority<'a, UserinfoE = Userinfo, RegNameE = RegName> {
297 inner: AuthorityInner<'a>,
298 _marker: PhantomData<(UserinfoE, RegNameE)>,
299}
300
301impl<'a, T, U> Authority<'a, T, U> {
302 pub(crate) fn cast<V, W>(self) -> Authority<'a, V, W> {
303 Authority {
304 inner: self.inner,
305 _marker: PhantomData,
306 }
307 }
308}
309
310impl<'a, UserinfoE: Encoder, RegNameE: Encoder> Authority<'a, UserinfoE, RegNameE> {
311 pub(crate) const fn new(val: &'a str, meta: AuthMeta) -> Self {
312 Self {
313 inner: AuthorityInner { val, meta },
314 _marker: PhantomData,
315 }
316 }
317
318 /// An empty authority component.
319 pub const EMPTY: Authority<'static, UserinfoE, RegNameE> = Authority::new("", AuthMeta::EMPTY);
320
321 #[cfg(feature = "alloc")]
322 pub(crate) fn meta(&self) -> AuthMeta {
323 self.inner.meta
324 }
325
326 /// Returns the authority component as a string slice.
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// use fluent_uri::Uri;
332 ///
333 /// let uri = Uri::parse("http://user@example.com:8080/")?;
334 /// let auth = uri.authority().unwrap();
335 /// assert_eq!(auth.as_str(), "user@example.com:8080");
336 /// # Ok::<_, fluent_uri::ParseError>(())
337 /// ```
338 #[inline]
339 #[must_use]
340 pub fn as_str(&self) -> &'a str {
341 self.inner.val
342 }
343
344 /// Returns the optional [userinfo] subcomponent.
345 ///
346 /// [userinfo]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1
347 ///
348 /// # Examples
349 ///
350 /// ```
351 /// use fluent_uri::{pct_enc::EStr, Uri};
352 ///
353 /// let uri = Uri::parse("http://user@example.com/")?;
354 /// let auth = uri.authority().unwrap();
355 /// assert_eq!(auth.userinfo(), Some(EStr::new_or_panic("user")));
356 ///
357 /// let uri = Uri::parse("http://example.com/")?;
358 /// let auth = uri.authority().unwrap();
359 /// assert_eq!(auth.userinfo(), None);
360 /// # Ok::<_, fluent_uri::ParseError>(())
361 /// ```
362 #[must_use]
363 pub fn userinfo(&self) -> Option<&'a EStr<UserinfoE>> {
364 self.inner.userinfo().map(EStr::cast)
365 }
366
367 /// Returns the [host] subcomponent as a string slice.
368 ///
369 /// The host subcomponent is always present, although it may be empty.
370 ///
371 /// The square brackets enclosing an IPv6 or IPvFuture address are included.
372 ///
373 /// Note that ASCII characters within a host are *case-insensitive*.
374 ///
375 /// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// use fluent_uri::Uri;
381 ///
382 /// let uri = Uri::parse("http://user@example.com:8080/")?;
383 /// let auth = uri.authority().unwrap();
384 /// assert_eq!(auth.host(), "example.com");
385 ///
386 /// let uri = Uri::parse("file:///path/to/file")?;
387 /// let auth = uri.authority().unwrap();
388 /// assert_eq!(auth.host(), "");
389 ///
390 /// let uri = Uri::parse("http://[::1]")?;
391 /// let auth = uri.authority().unwrap();
392 /// assert_eq!(auth.host(), "[::1]");
393 /// # Ok::<_, fluent_uri::ParseError>(())
394 /// ```
395 #[must_use]
396 pub fn host(&self) -> &'a str {
397 self.inner.host()
398 }
399
400 /// Returns the parsed [host] subcomponent.
401 ///
402 /// Note that ASCII characters within a host are *case-insensitive*.
403 ///
404 /// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
405 ///
406 /// # Examples
407 ///
408 /// ```
409 /// use fluent_uri::{component::Host, pct_enc::EStr, Uri};
410 #[cfg_attr(feature = "net", doc = "use std::net::{Ipv4Addr, Ipv6Addr};")]
411 ///
412 /// let uri = Uri::parse("foo://127.0.0.1")?;
413 /// let auth = uri.authority().unwrap();
414 #[cfg_attr(
415 feature = "net",
416 doc = "assert!(matches!(auth.host_parsed(), Host::Ipv4(Ipv4Addr::LOCALHOST)));"
417 )]
418 #[cfg_attr(
419 not(feature = "net"),
420 doc = "assert!(matches!(auth.host_parsed(), Host::Ipv4 { .. }));"
421 )]
422 ///
423 /// let uri = Uri::parse("foo://[::1]")?;
424 /// let auth = uri.authority().unwrap();
425 #[cfg_attr(
426 feature = "net",
427 doc = "assert!(matches!(auth.host_parsed(), Host::Ipv6(Ipv6Addr::LOCALHOST)));"
428 )]
429 #[cfg_attr(
430 not(feature = "net"),
431 doc = "assert!(matches!(auth.host_parsed(), Host::Ipv6 { .. }));"
432 )]
433 ///
434 /// let uri = Uri::parse("foo://[v1.addr]")?;
435 /// let auth = uri.authority().unwrap();
436 /// // The API design for IPvFuture addresses is to be determined.
437 /// assert!(matches!(auth.host_parsed(), Host::IpvFuture { .. }));
438 ///
439 /// let uri = Uri::parse("foo://localhost")?;
440 /// let auth = uri.authority().unwrap();
441 /// assert!(matches!(auth.host_parsed(), Host::RegName(name) if name == "localhost"));
442 /// # Ok::<_, fluent_uri::ParseError>(())
443 /// ```
444 #[must_use]
445 pub fn host_parsed(&self) -> Host<'a, RegNameE> {
446 match self.inner.meta.host_meta {
447 #[cfg(feature = "net")]
448 HostMeta::Ipv4(addr) => Host::Ipv4(addr),
449 #[cfg(feature = "net")]
450 HostMeta::Ipv6(addr) => Host::Ipv6(addr),
451
452 #[cfg(not(feature = "net"))]
453 HostMeta::Ipv4() => Host::Ipv4(),
454 #[cfg(not(feature = "net"))]
455 HostMeta::Ipv6() => Host::Ipv6(),
456
457 HostMeta::IpvFuture => Host::IpvFuture,
458 HostMeta::RegName => Host::RegName(EStr::new_validated(self.host())),
459 }
460 }
461
462 /// Returns the optional [port] subcomponent.
463 ///
464 /// A scheme may define a [default port] to use when the port is
465 /// not present or is empty.
466 ///
467 /// Note that the port may be empty, with leading zeros, or larger than [`u16::MAX`].
468 /// It is up to you to decide whether to deny such ports, fallback to the scheme's
469 /// default if it is empty, ignore the leading zeros, or use a special addressing
470 /// mechanism that allows ports larger than [`u16::MAX`].
471 ///
472 /// [port]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
473 /// [default port]: Scheme::default_port
474 ///
475 /// # Examples
476 ///
477 /// ```
478 /// use fluent_uri::{pct_enc::EStr, Uri};
479 ///
480 /// let uri = Uri::parse("foo://localhost:4673/")?;
481 /// let auth = uri.authority().unwrap();
482 /// assert_eq!(auth.port(), Some(EStr::new_or_panic("4673")));
483 ///
484 /// let uri = Uri::parse("foo://localhost:/")?;
485 /// let auth = uri.authority().unwrap();
486 /// assert_eq!(auth.port(), Some(EStr::EMPTY));
487 ///
488 /// let uri = Uri::parse("foo://localhost/")?;
489 /// let auth = uri.authority().unwrap();
490 /// assert_eq!(auth.port(), None);
491 ///
492 /// let uri = Uri::parse("foo://localhost:123456/")?;
493 /// let auth = uri.authority().unwrap();
494 /// assert_eq!(auth.port(), Some(EStr::new_or_panic("123456")));
495 /// # Ok::<_, fluent_uri::ParseError>(())
496 /// ```
497 #[must_use]
498 pub fn port(&self) -> Option<&'a EStr<Port>> {
499 self.inner.port()
500 }
501
502 /// Converts the [port] subcomponent to `u16`, if present and nonempty.
503 ///
504 /// Returns `Ok(None)` if the port is not present or is empty. Leading zeros are ignored.
505 ///
506 /// [port]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
507 ///
508 /// # Errors
509 ///
510 /// Returns `Err` if the port cannot be parsed into `u16`.
511 ///
512 /// # Examples
513 ///
514 /// ```
515 /// use fluent_uri::Uri;
516 ///
517 /// let uri = Uri::parse("foo://localhost:4673/")?;
518 /// let auth = uri.authority().unwrap();
519 /// assert_eq!(auth.port_to_u16(), Ok(Some(4673)));
520 ///
521 /// let uri = Uri::parse("foo://localhost/")?;
522 /// let auth = uri.authority().unwrap();
523 /// assert_eq!(auth.port_to_u16(), Ok(None));
524 ///
525 /// let uri = Uri::parse("foo://localhost:/")?;
526 /// let auth = uri.authority().unwrap();
527 /// assert_eq!(auth.port_to_u16(), Ok(None));
528 ///
529 /// let uri = Uri::parse("foo://localhost:123456/")?;
530 /// let auth = uri.authority().unwrap();
531 /// assert!(auth.port_to_u16().is_err());
532 /// # Ok::<_, fluent_uri::ParseError>(())
533 /// ```
534 pub fn port_to_u16(&self) -> Result<Option<u16>, ParseIntError> {
535 self.inner.port_to_u16()
536 }
537
538 /// Converts the host and the port subcomponent to an iterator of resolved [`SocketAddr`]s.
539 ///
540 /// The default port is used if the port component is not present or is empty.
541 /// A registered name is first [decoded] and then resolved with [`ToSocketAddrs`].
542 /// Punycode encoding is **not** performed prior to resolution.
543 ///
544 /// [decoded]: EStr::decode
545 ///
546 /// # Errors
547 ///
548 /// Returns `Err` if any of the following is true.
549 ///
550 /// - The port cannot be parsed into `u16`.
551 /// - The host is an IPvFuture address.
552 /// - A registered name does not decode to valid UTF-8 or fails to resolve.
553 #[cfg(all(feature = "net", feature = "std"))]
554 pub fn socket_addrs(&self, default_port: u16) -> io::Result<impl Iterator<Item = SocketAddr>> {
555 self.inner.socket_addrs(default_port)
556 }
557
558 /// Checks whether a userinfo subcomponent is present.
559 ///
560 /// # Examples
561 ///
562 /// ```
563 /// use fluent_uri::Uri;
564 ///
565 /// let uri = Uri::parse("http://user@example.com/")?;
566 /// assert!(uri.authority().unwrap().has_userinfo());
567 ///
568 /// let uri = Uri::parse("http://example.com/")?;
569 /// assert!(!uri.authority().unwrap().has_userinfo());
570 /// # Ok::<_, fluent_uri::ParseError>(())
571 #[inline]
572 #[must_use]
573 pub fn has_userinfo(&self) -> bool {
574 self.inner.meta.host_bounds.0 != 0
575 }
576
577 /// Checks whether a port subcomponent is present.
578 ///
579 /// # Examples
580 ///
581 /// ```
582 /// use fluent_uri::Uri;
583 ///
584 /// let uri = Uri::parse("foo://localhost:4673/")?;
585 /// assert!(uri.authority().unwrap().has_port());
586 ///
587 /// // The port subcomponent can be empty.
588 /// let uri = Uri::parse("foo://localhost:/")?;
589 /// assert!(uri.authority().unwrap().has_port());
590 ///
591 /// let uri = Uri::parse("foo://localhost/")?;
592 /// let auth = uri.authority().unwrap();
593 /// assert!(!uri.authority().unwrap().has_port());
594 /// # Ok::<_, fluent_uri::ParseError>(())
595 /// ```
596 #[inline]
597 #[must_use]
598 pub fn has_port(&self) -> bool {
599 self.inner.meta.host_bounds.1 != self.inner.val.len()
600 }
601}
602
603/// A parsed [host] component.
604///
605/// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
606#[derive(Clone, Copy)]
607#[cfg_attr(fuzzing, derive(PartialEq, Eq))]
608pub enum Host<'a, RegNameE: Encoder = RegName> {
609 /// An IPv4 address.
610 #[cfg_attr(not(feature = "net"), non_exhaustive)]
611 Ipv4(
612 /// The address.
613 #[cfg(feature = "net")]
614 Ipv4Addr,
615 ),
616 /// An IPv6 address.
617 #[cfg_attr(not(feature = "net"), non_exhaustive)]
618 Ipv6(
619 /// The address.
620 #[cfg(feature = "net")]
621 Ipv6Addr,
622 ),
623 /// An IP address of future version.
624 ///
625 /// This variant is marked as non-exhaustive because the API design
626 /// for IPvFuture addresses is to be determined.
627 #[non_exhaustive]
628 IpvFuture,
629 /// A registered name.
630 ///
631 /// Note that ASCII characters within a registered name are *case-insensitive*.
632 RegName(&'a EStr<RegNameE>),
633}