1use bytes::Bytes;
17
18use crate::error::BuildError;
19use crate::headers::grammar::is_token_char;
20use crate::message::{Header, Headers, Method, Request, Response, StatusCode};
21use crate::name::HeaderName;
22use crate::uri::Uri;
23
24pub(crate) fn check_value(value: &[u8], field: &'static str) -> Result<(), BuildError> {
30 if let Some(pos) = value
31 .iter()
32 .position(|&b| matches!(b, b'\r' | b'\n' | b'\0'))
33 {
34 return Err(BuildError::IllegalCharacter {
35 field,
36 offset: pos,
37 byte: value.get(pos).copied().unwrap_or(0),
38 });
39 }
40 Ok(())
41}
42
43pub(crate) fn check_token(value: &[u8], field: &'static str) -> Result<(), BuildError> {
45 check_value(value, field)?;
46 if value.is_empty() || !value.iter().all(|&b| is_token_char(b)) {
47 return Err(BuildError::NotAToken { field });
48 }
49 Ok(())
50}
51
52impl Header {
53 pub fn build(name: HeaderName, value: impl Into<Bytes>) -> Result<Self, BuildError> {
57 let value = value.into();
58 check_value(&value, "header value")?;
59 if let HeaderName::Other(raw) = &name {
60 check_token(raw, "header name")?;
61 }
62 Ok(Self::new_unchecked(name, value))
63 }
64}
65
66#[derive(Debug)]
78pub struct RequestBuilder {
79 method: Method,
80 uri: Uri,
81 headers: Headers,
82 body: Bytes,
83}
84
85impl RequestBuilder {
86 #[must_use]
88 pub fn new(method: Method, uri: Uri) -> Self {
89 Self {
90 method,
91 uri,
92 headers: Headers::new(),
93 body: Bytes::new(),
94 }
95 }
96
97 pub fn header(mut self, name: HeaderName, value: impl Into<Bytes>) -> Result<Self, BuildError> {
99 self.headers.push(Header::build(name, value)?);
100 Ok(self)
101 }
102
103 pub fn set_header(
109 mut self,
110 name: &HeaderName,
111 value: impl Into<Bytes>,
112 ) -> Result<Self, BuildError> {
113 let header = Header::build(name.clone(), value)?;
114 self.headers.remove_all(name);
115 self.headers.push(header);
116 Ok(self)
117 }
118
119 #[must_use]
121 pub fn max_forwards(mut self, hops: u8) -> Self {
122 self.headers.push(Header::new_unchecked(
123 HeaderName::MaxForwards,
124 Bytes::from(hops.to_string()),
125 ));
126 self
127 }
128
129 pub fn cseq(mut self, sequence: u32, method: &Method) -> Result<Self, BuildError> {
131 check_token(method.as_bytes(), "CSeq method")?;
132 let mut value = sequence.to_string().into_bytes();
133 value.push(b' ');
134 value.extend_from_slice(method.as_bytes());
135 self.headers
136 .push(Header::new_unchecked(HeaderName::CSeq, Bytes::from(value)));
137 Ok(self)
138 }
139
140 #[must_use]
145 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
146 let body = body.into();
147 self.headers.remove_all(&HeaderName::ContentLength);
148 self.headers.push(Header::new_unchecked(
149 HeaderName::ContentLength,
150 Bytes::from(body.len().to_string()),
151 ));
152 self.body = body;
153 self
154 }
155
156 #[must_use]
161 pub fn build(mut self) -> Request {
162 if self.headers.get(&HeaderName::ContentLength).is_none() {
163 self.headers.push(Header::new_unchecked(
164 HeaderName::ContentLength,
165 Bytes::from_static(b"0"),
166 ));
167 }
168 let mut request = Request::new(self.method, self.uri);
169 request.headers = self.headers;
170 request.set_body(self.body);
171 request
172 }
173}
174
175#[derive(Debug)]
177pub struct ResponseBuilder {
178 status: StatusCode,
179 reason: Bytes,
180 headers: Headers,
181 body: Bytes,
182}
183
184impl ResponseBuilder {
185 pub fn new(status: StatusCode, reason: impl Into<Bytes>) -> Result<Self, BuildError> {
190 let reason = reason.into();
191 check_value(&reason, "reason phrase")?;
192 Ok(Self {
193 status,
194 reason,
195 headers: Headers::new(),
196 body: Bytes::new(),
197 })
198 }
199
200 pub fn to_request(
208 request: &Request,
209 status: StatusCode,
210 reason: impl Into<Bytes>,
211 ) -> Result<Self, BuildError> {
212 let mut builder = Self::new(status, reason)?;
213 for (name, label) in [
214 (HeaderName::Via, "Via"),
215 (HeaderName::From, "From"),
216 (HeaderName::To, "To"),
217 (HeaderName::CallId, "Call-ID"),
218 (HeaderName::CSeq, "CSeq"),
219 ] {
220 if request.headers.get(&name).is_none() {
221 return Err(BuildError::MissingRequiredResponseHeader { header: label });
222 }
223 for header in request.headers.get_all(&name) {
224 builder.headers.push(header.clone());
225 }
226 }
227 if let Some(history) = crate::headers::history::for_response(request, status) {
231 builder
232 .headers
233 .push(Header::new_unchecked(HeaderName::HistoryInfo, history));
234 }
235 Ok(builder)
236 }
237
238 pub fn header(mut self, name: HeaderName, value: impl Into<Bytes>) -> Result<Self, BuildError> {
240 self.headers.push(Header::build(name, value)?);
241 Ok(self)
242 }
243
244 pub fn set_header(
246 mut self,
247 name: &HeaderName,
248 value: impl Into<Bytes>,
249 ) -> Result<Self, BuildError> {
250 let header = Header::build(name.clone(), value)?;
251 self.headers.remove_all(name);
252 self.headers.push(header);
253 Ok(self)
254 }
255
256 #[must_use]
258 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
259 let body = body.into();
260 self.headers.remove_all(&HeaderName::ContentLength);
261 self.headers.push(Header::new_unchecked(
262 HeaderName::ContentLength,
263 Bytes::from(body.len().to_string()),
264 ));
265 self.body = body;
266 self
267 }
268
269 #[must_use]
271 pub fn build(mut self) -> Response {
272 if self.headers.get(&HeaderName::ContentLength).is_none() {
273 self.headers.push(Header::new_unchecked(
274 HeaderName::ContentLength,
275 Bytes::from_static(b"0"),
276 ));
277 }
278 let mut response = Response::new(self.status, self.reason);
279 response.headers = self.headers;
280 response.set_body(self.body);
281 response
282 }
283}
284
285#[cfg(test)]
286#[allow(
287 clippy::unwrap_used,
288 clippy::expect_used,
289 clippy::panic,
290 clippy::indexing_slicing
291)]
292mod tests {
293 use super::*;
294 use crate::headers::CSeq;
295 use crate::uri::{Host, HostName};
296
297 fn uri() -> Uri {
298 Uri::sip(Host::Name(
299 HostName::new(Bytes::from_static(b"example.com")).expect("a valid host"),
300 ))
301 }
302
303 fn answerable_invite() -> RequestBuilder {
304 RequestBuilder::new(Method::Invite, uri())
305 .header(HeaderName::Via, "SIP/2.0/UDP host;branch=z9hG4bK1")
306 .expect("valid Via")
307 .header(HeaderName::From, "<sip:a@example.com>;tag=1")
308 .expect("valid From")
309 .header(HeaderName::To, "<sip:b@example.com>")
310 .expect("valid To")
311 .header(HeaderName::CallId, "call@example.com")
312 .expect("valid Call-ID")
313 .cseq(1, &Method::Invite)
314 .expect("valid CSeq")
315 }
316
317 #[test]
323 fn crlf_injection_rejected_in_every_user_supplied_field() {
324 let payloads: &[&[u8]] = &[
326 b"value\r\nInjected: yes",
327 b"value\rInjected: yes",
328 b"value\nInjected: yes",
329 b"value\0truncated",
330 b"\r\n",
331 b"\n\n",
332 ];
333
334 for payload in payloads {
335 let p = Bytes::copy_from_slice(payload);
336
337 assert!(
339 Header::build(HeaderName::Subject, p.clone()).is_err(),
340 "header value accepted {payload:?}"
341 );
342 assert!(
344 Header::build(HeaderName::Other(p.clone()), Bytes::from_static(b"x")).is_err(),
345 "header name accepted {payload:?}"
346 );
347 assert!(
349 RequestBuilder::new(Method::Options, uri())
350 .header(HeaderName::CallId, p.clone())
351 .is_err(),
352 "request builder accepted {payload:?}"
353 );
354 assert!(
356 ResponseBuilder::new(StatusCode::new(200).unwrap(), p.clone()).is_err(),
357 "reason phrase accepted {payload:?}"
358 );
359 assert!(
361 ResponseBuilder::new(StatusCode::new(200).unwrap(), "OK")
362 .unwrap()
363 .header(HeaderName::Server, p.clone())
364 .is_err(),
365 "response builder accepted {payload:?}"
366 );
367 assert!(
369 RequestBuilder::new(Method::Options, uri())
370 .cseq(1, &Method::Other(p.clone()))
371 .is_err(),
372 "CSeq method accepted {payload:?}"
373 );
374 assert!(
378 HostName::new(p.clone()).is_err(),
379 "host name accepted {payload:?}"
380 );
381 }
382 }
383
384 #[test]
386 fn host_names_are_validated_not_just_screened() {
387 assert!(HostName::new("example.com").is_ok());
388 assert!(HostName::new("host-5.sub.example.com").is_ok());
389 for bad in [
390 "",
391 "exa mple.com",
392 "host@evil.com",
393 "host;lr",
394 "<host>",
395 "host/path",
396 ] {
397 assert!(HostName::new(bad).is_err(), "{bad:?} should be rejected");
398 }
399 }
400
401 #[test]
402 fn a_built_request_frames_correctly() {
403 let request = RequestBuilder::new(Method::Options, uri())
404 .header(HeaderName::CallId, "abc@example.com")
405 .unwrap()
406 .cseq(1, &Method::Options)
407 .unwrap()
408 .max_forwards(70)
409 .build();
410
411 let mut out = Vec::new();
412 request.write_to(&mut out);
413 let text = String::from_utf8_lossy(&out);
414 assert!(text.starts_with("OPTIONS sip:example.com SIP/2.0\r\n"));
415 assert!(text.contains("CSeq: 1 OPTIONS\r\n"));
416 assert!(text.contains("Content-Length: 0\r\n"));
418 assert!(text.ends_with("\r\n\r\n"));
419 }
420
421 #[test]
422 fn setting_a_body_sets_the_matching_content_length() {
423 let request = RequestBuilder::new(Method::Options, uri())
424 .body(Bytes::from_static(b"hello"))
425 .build();
426 assert_eq!(request.body().len(), 5);
427 assert_eq!(
428 request
429 .headers
430 .value(&HeaderName::ContentLength)
431 .as_deref()
432 .map(<[u8]>::to_vec),
433 Some(b"5".to_vec())
434 );
435
436 let request = RequestBuilder::new(Method::Options, uri())
439 .body(Bytes::from_static(b"hello"))
440 .body(Bytes::from_static(b"hi"))
441 .build();
442 assert_eq!(request.headers.count(&HeaderName::ContentLength), 1);
443 assert_eq!(
444 request
445 .headers
446 .value(&HeaderName::ContentLength)
447 .as_deref()
448 .map(<[u8]>::to_vec),
449 Some(b"2".to_vec())
450 );
451 }
452
453 #[test]
454 fn a_response_copies_the_via_stack_in_order() {
455 let request = RequestBuilder::new(Method::Invite, uri())
456 .header(HeaderName::Via, "SIP/2.0/UDP first;branch=z9hG4bK1")
457 .unwrap()
458 .header(HeaderName::Via, "SIP/2.0/UDP second;branch=z9hG4bK2")
459 .unwrap()
460 .header(HeaderName::From, "<sip:a@b>;tag=1")
461 .unwrap()
462 .header(HeaderName::To, "<sip:c@d>")
463 .unwrap()
464 .header(HeaderName::CallId, "x@y")
465 .unwrap()
466 .cseq(7, &Method::Invite)
467 .unwrap()
468 .build();
469
470 let response =
471 ResponseBuilder::to_request(&request, StatusCode::new(180).unwrap(), "Ringing")
472 .unwrap()
473 .build();
474
475 let vias: Vec<_> = response
476 .headers
477 .get_all(&HeaderName::Via)
478 .map(|h| h.value().to_vec())
479 .collect();
480 assert_eq!(
481 vias,
482 vec![
483 b"SIP/2.0/UDP first;branch=z9hG4bK1".to_vec(),
484 b"SIP/2.0/UDP second;branch=z9hG4bK2".to_vec(),
485 ],
486 "the Via stack must be copied in order; a response walks it back"
487 );
488 assert_eq!(
489 response.headers.typed::<CSeq>().and_then(Result::ok),
490 Some(CSeq {
491 sequence: 7,
492 method: Method::Invite
493 })
494 );
495 }
496
497 #[test]
498 fn history_is_returned_in_responses_other_than_100() {
499 let request = answerable_invite()
500 .header(HeaderName::Supported, "histinfo")
501 .unwrap()
502 .build();
503 let trying = ResponseBuilder::to_request(&request, StatusCode::new(100).unwrap(), "Trying")
504 .unwrap()
505 .build();
506 assert!(trying.headers.get(&HeaderName::HistoryInfo).is_none());
507
508 let ringing =
509 ResponseBuilder::to_request(&request, StatusCode::new(180).unwrap(), "Ringing")
510 .unwrap()
511 .build();
512 assert_eq!(
513 ringing.headers.value(&HeaderName::HistoryInfo).as_deref(),
514 Some(&b"<sip:example.com>;index=1"[..])
515 );
516 }
517
518 #[test]
519 fn repeated_history_rows_are_one_ordered_cache() {
520 let request = answerable_invite()
521 .header(HeaderName::HistoryInfo, "<sip:first@example.com>;index=1")
522 .unwrap()
523 .header(
524 HeaderName::HistoryInfo,
525 "<sip:second@example.com>;index=1.1;mp=1",
526 )
527 .unwrap()
528 .build();
529 let response =
530 ResponseBuilder::to_request(&request, StatusCode::new(180).unwrap(), "Ringing")
531 .unwrap()
532 .build();
533 assert_eq!(
534 response.headers.value(&HeaderName::HistoryInfo).as_deref(),
535 Some(&b"<sip:first@example.com>;index=1, <sip:second@example.com>;index=1.1;mp=1"[..])
536 );
537 }
538
539 #[test]
540 fn history_privacy_anonymizes_a_response_cache() {
541 let request = answerable_invite()
542 .header(
543 HeaderName::HistoryInfo,
544 "<sip:alice@example.com?Reason=SIP%3Bcause%3D302>;index=1",
545 )
546 .unwrap()
547 .header(HeaderName::Privacy, "history")
548 .unwrap()
549 .build();
550 let response =
551 ResponseBuilder::to_request(&request, StatusCode::new(486).unwrap(), "Busy Here")
552 .unwrap()
553 .build();
554 assert_eq!(
555 response.headers.value(&HeaderName::HistoryInfo).as_deref(),
556 Some(&b"<sip:anonymous@anonymous.invalid>;index=1"[..])
557 );
558 }
559
560 #[test]
563 fn a_response_can_be_built_for_a_request_with_an_unparseable_header() {
564 use crate::{Limits, parse_datagram};
565 let text = "OPTIONS sip:a@b.com SIP/2.0\r\n\
566 Via: SIP/2.0/UDP h;branch=z9hG4bKx\r\n\
567 To: \"unterminated <sip:a@b.com>\r\n\
568 From: <sip:c@d>;tag=1\r\n\
569 Call-ID: x@y\r\n\
570 CSeq: 1 OPTIONS\r\n\
571 Content-Length: 0\r\n\r\n";
572 let msg = parse_datagram(Bytes::from(text), &Limits::datagram()).expect("frames");
573 let request = msg.as_request().expect("a request");
574 assert!(
575 request
576 .headers
577 .typed::<crate::headers::To>()
578 .is_some_and(|r| r.is_err())
579 );
580
581 let response =
582 ResponseBuilder::to_request(request, StatusCode::new(400).unwrap(), "Bad Request")
583 .expect("must still build")
584 .build();
585 let mut out = Vec::new();
586 response.write_to(&mut out);
587 assert!(String::from_utf8_lossy(&out).starts_with("SIP/2.0 400 Bad Request\r\n"));
588 }
589}