1use super::{Attribute, AttributeValue, Name, Strings, Syntax, prepared, segments};
4use crate::error::{Error, ErrorCode, Located, Result};
5use crate::oid;
6use core::cmp::Ordering;
7use core::ops::Range;
8use structured_asn1::memory::{Memory, OwnedVec, Work};
9use structured_asn1::{Class, Oid, OwnedOid, Parser, ScopedReader as Reader, Tag, universal};
10
11const FIELD: &str = "name.selector";
12
13#[derive(Debug)]
21pub struct Selector {
22 parts: OwnedVec<OwnedVec<Selected>>,
23}
24
25#[derive(Debug)]
26struct Selected {
27 kind: Kind,
28 bytes: OwnedVec<u8>,
29 value: Value,
30 offset: u64,
31}
32
33#[derive(Debug)]
34enum Kind {
35 Known(Oid<'static>),
36 Numeric(OwnedOid),
37}
38
39impl Kind {
40 fn as_oid(&self) -> Oid<'_> {
41 match self {
42 Self::Known(kind) => *kind,
43 Self::Numeric(kind) => kind.as_oid(),
44 }
45 }
46}
47
48#[derive(Debug)]
49enum Value {
50 Assertion,
51 Ber {
52 tag: Tag,
53 contents: Range<usize>,
54 joined: Option<OwnedVec<u8>>,
55 },
56}
57
58impl Selector {
59 pub fn parse(text: &str, execution: &Memory) -> Result<Self> {
75 Self::parse_with(text, execution, None)
76 }
77
78 pub fn parse_in(text: &str, parent: &Reader<'_, '_>) -> Result<Self> {
84 Self::parse_with(text, &parent.execution(), Some(parent))
85 }
86
87 fn parse_with<W: Work>(
88 text: &str,
89 execution: &W,
90 parent: Option<&Reader<'_, '_>>,
91 ) -> Result<Self> {
92 let work = (text.len() as u64)
95 .checked_mul(64)
96 .and_then(|n| n.checked_add(1))
97 .ok_or_else(|| {
98 Error::resource(structured_asn1::memory::MemoryError::WorkExceeded, FIELD, 0)
99 })?;
100 execution
101 .charge_work(work)
102 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
103 scan::<true>(text, |_, _, _, _| Ok(()))?;
104 let parser = Parser::ber(execution.memory().clone());
105 let mut parts = execution.vector();
106 let mut current = execution.vector();
107 scan::<true>(text, |label, value, offset, last| {
108 current
109 .try_push(selected(label, value, offset, &parser, execution, parent)?)
110 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
111 if last {
112 parts
113 .try_push(core::mem::replace(&mut current, execution.vector()).into_owned())
114 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
115 }
116 Ok(())
117 })?;
118 execution
119 .charge_work(parts.len() as u64)
120 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
121 parts.reverse(); Ok(Self {
123 parts: parts.into_owned(),
124 })
125 }
126
127 pub fn matches_name<W: Work>(&self, name: &Name<'_>, execution: &W) -> Result<bool> {
131 self.matches_name_with(name, execution, None)
132 }
133
134 pub fn matches_name_in(&self, name: &Name<'_>, parent: &Reader<'_, '_>) -> Result<bool> {
137 self.matches_name_with(name, &parent.execution(), Some(parent))
138 }
139
140 fn matches_name_with<W: Work>(
141 &self,
142 name: &Name<'_>,
143 execution: &W,
144 parent: Option<&Reader<'_, '_>>,
145 ) -> Result<bool> {
146 if self.parts.len() != name.parts().len() {
147 return Ok(false);
148 }
149 for (selected, part) in self.parts.iter().zip(name.parts()) {
150 execution
151 .charge_work(1)
152 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
153 let fail = |reason| Error::resource(reason, FIELD, 0);
154 let order = prepared::compare_views(
155 selected.iter().map(|item| project(item, execution)),
156 part.attributes()
157 .iter()
158 .copied()
159 .map(|attribute| prepared::Prepared::of_in(attribute, execution, parent)),
160 execution,
161 fail,
162 )?;
163 if order != Ordering::Equal {
164 return Ok(false);
165 }
166 }
167 Ok(true)
168 }
169
170 pub fn matches<W: Work>(&self, other: &Self, execution: &W) -> Result<bool> {
172 if self.parts.len() != other.parts.len() {
173 return Ok(false);
174 }
175 for (left, right) in self.parts.iter().zip(&other.parts) {
176 execution
177 .charge_work(1)
178 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
179 if prepared::compare_views(
180 left.iter().map(|item| project(item, execution)),
181 right.iter().map(|item| project(item, execution)),
182 execution,
183 |reason| Error::resource(reason, FIELD, 0),
184 )? != Ordering::Equal
185 {
186 return Ok(false);
187 }
188 }
189 Ok(true)
190 }
191}
192
193fn selected<W: Work>(
194 label: &str,
195 value: &str,
196 offset: u64,
197 parser: &Parser,
198 execution: &W,
199 parent: Option<&Reader<'_, '_>>,
200) -> Result<Selected> {
201 let kind = match descriptor(label) {
202 Some(kind) => Kind::Known(kind),
203 None => Kind::Numeric(OwnedOid::parse_in(label, execution).map_err(|error| {
204 Error::new(
205 ErrorCode::Encoding(error.code()),
206 FIELD,
207 offset + error.offset(),
208 )
209 })?),
210 };
211 let mut bytes = execution.vector();
212 let value = if let Some(hex) = value.strip_prefix('#') {
213 for pair in hex.as_bytes().as_chunks::<2>().0 {
214 bytes
215 .try_push(
216 (digit(pair[0]).expect("validated hex") << 4)
217 | digit(pair[1]).expect("validated hex"),
218 )
219 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
220 }
221 ber(&bytes, kind.as_oid(), parser, execution, parent).map_err(|error| {
222 Error::new(
223 error.code(),
224 FIELD,
225 offset + label.len() as u64 + 2 + 2 * error.offset(),
226 )
227 })?
228 } else {
229 let mut at = 0;
230 let input = value.as_bytes();
231 while at < input.len() {
232 let byte = if input[at] == b'\\' {
233 at += 1;
234 if let Some(high) = digit(input[at]) {
235 at += 1;
236 (high << 4) | digit(input[at]).expect("validated escape")
237 } else {
238 input[at]
239 }
240 } else {
241 input[at]
242 };
243 bytes
244 .try_push(byte)
245 .map_err(|reason| Error::resource(reason, FIELD, offset))?;
246 at += 1;
247 }
248 Value::Assertion
249 };
250 Ok(Selected {
251 kind,
252 bytes: bytes.into_owned(),
253 value,
254 offset,
255 })
256}
257
258fn ber<W: Work>(
259 bytes: &[u8],
260 kind: Oid<'_>,
261 parser: &Parser,
262 execution: &W,
263 parent: Option<&Reader<'_, '_>>,
264) -> Result<Value> {
265 let mut reader = match parent {
266 Some(parent) => parser.read_query_in(parent, bytes, 0, structured_asn1::Encoding::Ber, 0),
267 None => parser.read(bytes, 0),
268 }
269 .field(FIELD)?;
270 let element = reader
271 .next_element()
272 .field(FIELD)?
273 .ok_or_else(|| malformed(0))?;
274 if !reader.is_empty() {
275 return Err(malformed(element.span().end()));
276 }
277 super::walk_der(&reader, &element, FIELD, Strings::AsWritten)?;
282 let tag = element.tag();
283 let start = element.as_bytes().len()
284 - element.contents().len()
285 - if element.is_indefinite() { 2 } else { 0 };
286 let contents = start..start + element.contents().len();
287 execution
288 .charge_work(
289 (kind.as_bytes().len() as u64)
290 .checked_mul(40)
291 .ok_or_else(|| {
292 Error::resource(structured_asn1::memory::MemoryError::WorkExceeded, FIELD, 0)
293 })?,
294 )
295 .map_err(|reason| Error::resource(reason, FIELD, 0))?;
296 let logical_encoding = match tag.number() {
297 Some(number) => super::AttributeValue {
298 tag: Tag::primitive(number),
299 contents: &[],
300 der: &[],
301 offset: 0,
302 depth: 0,
303 }
304 .encoding(),
305 None => super::Encoding::Other,
306 };
307 let joined = if tag.class() == Class::Universal
308 && tag.is_constructed()
309 && tag.number().is_some_and(super::segmented_universal_type)
310 && tag.number() != Some(universal::BIT_STRING)
311 && super::permits(kind, logical_encoding)
312 {
313 let mut joined = execution.vector();
314 segments::walk_in(
315 &reader,
316 &element,
317 FIELD,
318 false,
319 Some(&mut joined),
320 execution,
321 )?;
322 Some(joined.into_owned())
323 } else {
324 None
325 };
326 Ok(Value::Ber {
327 tag,
328 contents,
329 joined,
330 })
331}
332
333fn project<'a, W: Work>(item: &'a Selected, execution: &W) -> Result<prepared::Prepared<'a, W>> {
334 let kind = item.kind.as_oid();
335 let value = match &item.value {
336 Value::Assertion => {
337 execution
338 .charge_work((kind.as_bytes().len() as u64) * 40)
339 .map_err(|reason| Error::resource(reason, FIELD, item.offset))?;
340 let tag = match super::syntax_of(kind) {
341 Some(Syntax::Directory) => universal::UTF8_STRING,
342 Some(Syntax::Printable) => universal::PRINTABLE_STRING,
343 Some(Syntax::Ia5) => universal::IA5_STRING,
344 None => {
345 return Err(Error::new(
346 ErrorCode::UnsupportedNameSyntax,
347 FIELD,
348 item.offset,
349 ));
350 }
351 };
352 AttributeValue {
353 tag: Tag::primitive(tag),
354 contents: &item.bytes,
355 der: &[],
356 offset: item.offset,
357 depth: 0,
358 }
359 }
360 Value::Ber {
361 tag,
362 contents,
363 joined,
364 } => AttributeValue {
365 tag: if joined.is_some() {
366 Tag::primitive(tag.number().expect("universal string"))
367 } else {
368 *tag
369 },
370 contents: joined.as_deref().unwrap_or(&item.bytes[contents.clone()]),
371 der: &item.bytes,
372 offset: item.offset,
373 depth: 0,
374 },
375 };
376 let attribute = Attribute { kind, value };
377 if matches!(item.value, Value::Assertion) {
378 let work = (kind.as_bytes().len() as u64)
379 .checked_mul(64)
380 .and_then(|kinds| {
381 (value.contents.len() as u64)
382 .checked_mul(2)
383 .and_then(|contents| kinds.checked_add(contents))
384 })
385 .and_then(|work| work.checked_add(1))
386 .ok_or_else(|| {
387 Error::resource(
388 structured_asn1::memory::MemoryError::WorkExceeded,
389 FIELD,
390 item.offset,
391 )
392 })?;
393 execution
394 .charge_work(work)
395 .map_err(|reason| Error::resource(reason, FIELD, item.offset))?;
396 if !super::well_formed(&attribute) {
397 return Err(Error::new(ErrorCode::MalformedValue, FIELD, item.offset));
398 }
399 }
400 let prepared = prepared::Prepared::of(attribute, execution)?;
401 if matches!(item.value, Value::Assertion) && !prepared.is_prepared() {
402 return Err(Error::new(
403 ErrorCode::UnsupportedNameSyntax,
404 FIELD,
405 item.offset,
406 ));
407 }
408 Ok(prepared)
409}
410
411fn malformed(offset: u64) -> Error {
412 Error::new(ErrorCode::MalformedValue, FIELD, offset)
413}
414
415fn digit(byte: u8) -> Option<u8> {
416 match byte {
417 b'0'..=b'9' => Some(byte - b'0'),
418 b'a'..=b'f' => Some(byte - b'a' + 10),
419 b'A'..=b'F' => Some(byte - b'A' + 10),
420 _ => None,
421 }
422}
423
424fn label_fits<const REGISTERED: bool>(label: &str, offset: u64) -> Result<()> {
425 let Some(first) = label.as_bytes().first() else {
426 return Err(malformed(offset));
427 };
428 if first.is_ascii_digit() {
429 let mut count = 0;
430 for component in label.split('.') {
431 if component.is_empty()
432 || !component.bytes().all(|b| b.is_ascii_digit())
433 || (component.len() > 1 && component.starts_with('0'))
434 {
435 return Err(malformed(offset));
436 }
437 count += 1;
438 }
439 if count < 2 {
440 return Err(malformed(offset));
441 }
442 } else if !first.is_ascii_alphabetic()
443 || !label
444 .bytes()
445 .all(|b| b.is_ascii_alphanumeric() || b == b'-')
446 {
447 return Err(malformed(offset));
448 } else if REGISTERED && descriptor(label).is_none() {
449 return Err(Error::new(ErrorCode::UnsupportedNameSyntax, FIELD, offset));
450 }
451 Ok(())
452}
453
454fn scan<const REGISTERED: bool>(
457 text: &str,
458 mut visit: impl FnMut(&str, &str, u64, bool) -> Result<()>,
459) -> Result<()> {
460 if text.is_empty() {
461 return Ok(());
462 }
463 let bytes = text.as_bytes();
464 let mut at = 0;
465 loop {
466 let start = at;
467 while at < bytes.len() && bytes[at] != b'=' {
468 at += 1;
469 }
470 if at == bytes.len() {
471 return Err(malformed(start as u64));
472 }
473 let label = &text[start..at];
474 label_fits::<REGISTERED>(label, start as u64)?;
475 at += 1;
476 let value = at;
477 if bytes.get(at) == Some(&b'#') {
478 at += 1;
479 let hex = at;
480 while at < bytes.len() && !matches!(bytes[at], b',' | b'+') {
481 if digit(bytes[at]).is_none() {
482 return Err(malformed(at as u64));
483 }
484 at += 1;
485 }
486 if at == hex || !(at - hex).is_multiple_of(2) {
487 return Err(malformed(hex as u64));
488 }
489 } else {
490 let mut last_space = false;
491 while at < bytes.len() && !matches!(bytes[at], b',' | b'+') {
492 let byte = bytes[at];
493 if byte == b'\\' {
494 at += 1;
495 let Some(&escaped) = bytes.get(at) else {
496 return Err(malformed(at as u64));
497 };
498 if digit(escaped).is_some() {
499 at += 1;
500 if bytes.get(at).copied().and_then(digit).is_none() {
501 return Err(malformed(at as u64));
502 }
503 } else if !matches!(
504 escaped,
505 b' ' | b'"' | b'#' | b'+' | b',' | b';' | b'<' | b'=' | b'>' | b'\\'
506 ) {
507 return Err(malformed(at as u64));
508 }
509 last_space = false;
510 } else {
511 if matches!(byte, 0 | b'"' | b';' | b'<' | b'>')
512 || (at == value && matches!(byte, b' ' | b'#'))
513 {
514 return Err(malformed(at as u64));
515 }
516 last_space = byte == b' ';
517 }
518 at += 1;
519 }
520 if last_space {
521 return Err(malformed((at - 1) as u64));
522 }
523 }
524 let last = bytes.get(at) != Some(&b'+');
525 visit(label, &text[value..at], start as u64, last)?;
526 if at == bytes.len() {
527 return Ok(());
528 }
529 at += 1;
530 if at == bytes.len() {
531 return Err(malformed(at as u64));
532 }
533 }
534}
535
536pub(crate) fn syntax_fits(text: &str) -> bool {
540 scan::<false>(text, |_, _, _, _| Ok(())).is_ok()
541}
542
543pub(crate) fn attribute_type_syntax_fits(text: &str) -> bool {
545 label_fits::<false>(text, 0).is_ok()
546}
547
548const DESCRIPTORS: &[(&str, Oid<'static>)] = &[
549 ("CN", oid::COMMON_NAME),
550 ("L", oid::LOCALITY),
551 ("ST", oid::STATE_OR_PROVINCE),
552 ("O", oid::ORGANIZATION),
553 ("OU", oid::ORGANIZATIONAL_UNIT),
554 ("C", oid::COUNTRY),
555 ("STREET", oid::STREET_ADDRESS),
556 ("DC", oid::DOMAIN_COMPONENT),
557 ("UID", oid::USER_ID),
558 ("emailAddress", oid::EMAIL_ADDRESS),
559 ("serialNumber", oid::SERIAL_NUMBER),
560 ("surname", oid::SURNAME),
561 ("givenName", oid::GIVEN_NAME),
562 ("initials", oid::INITIALS),
563 ("generationQualifier", oid::GENERATION_QUALIFIER),
564 ("title", oid::TITLE),
565 ("dnQualifier", oid::DN_QUALIFIER),
566 ("pseudonym", oid::PSEUDONYM),
567 ("organizationIdentifier", oid::ORGANIZATION_IDENTIFIER),
568];
569
570pub(super) const REGISTERED_DESCRIPTORS: usize = 18;
575
576pub(super) fn registered_name(kind: Oid<'_>) -> Option<&'static str> {
577 DESCRIPTORS[..REGISTERED_DESCRIPTORS]
578 .iter()
579 .find(|(_, oid)| *oid == kind)
580 .map(|(name, _)| *name)
581}
582
583fn descriptor(text: &str) -> Option<Oid<'static>> {
584 if text.eq_ignore_ascii_case("sn") {
585 return Some(oid::SURNAME);
586 }
587 DESCRIPTORS
588 .iter()
589 .find(|(name, _)| name.eq_ignore_ascii_case(text))
590 .map(|(_, oid)| *oid)
591}