1#[cfg(feature = "multipart")]
2mod multipart;
3use bytes::Bytes;
4use futures_core::Stream;
5use http::request::Builder;
6use http::HeaderValue;
7use http::Request;
8use http::Response;
9use http::{header::CONTENT_TYPE, Uri};
10use http_body_util::StreamBody;
11use http_body_util::{combinators::UnsyncBoxBody, Empty, Full};
12#[cfg(feature = "multipart")]
13pub use multipart::*;
14#[cfg(feature = "serde")]
15use serde::Serialize;
16use std::future::Future;
17
18use crate::body::{empty, full};
19use crate::client::ClientBody;
20use crate::client::MaybeAbort;
21use crate::error::BodyError;
22use crate::error::ClientError;
23
24pub trait RequestExt<B>: Sized {
26 #[cfg(feature = "json")]
27 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
28 fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>>;
29 #[cfg(feature = "query")]
30 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
31 fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Request<B>>;
32 #[cfg(feature = "multipart")]
33 #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
34 fn multipart(
35 self,
36 form: multipart::Form,
37 ) -> crate::Result<Request<UnsyncBoxBody<Bytes, BodyError>>>;
38 #[cfg(feature = "form")]
39 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
40 fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>>;
41 #[cfg(feature = "stream")]
42 fn stream<S: Stream>(self, stream: S) -> crate::Result<Request<StreamBody<S>>>;
43 fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>>;
44 fn empty(self) -> crate::Result<Request<Empty<Bytes>>>;
45 fn collect_into_bytes(self) -> impl Future<Output = crate::Result<Request<Full<Bytes>>>> + Send
46 where
47 B: http_body::Body<Data = Bytes> + Send + 'static,
48 B::Error: std::error::Error + Send + Sync;
49 fn with_version(self, version: http::Version) -> Request<B>;
50 fn with_method(self, method: http::Method) -> Request<B>;
51 fn with_header<K>(self, key: K, value: http::header::HeaderValue) -> Request<B>
52 where
53 K: http::header::IntoHeaderName;
54 fn with_headers(self, header_map: http::header::HeaderMap) -> Request<B>;
55 #[cfg(feature = "auth")]
56 #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
57 fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Request<B>
58 where
59 U: std::fmt::Display,
60 P: std::fmt::Display,
61 Self: Sized,
62 {
63 let header_value = crate::util::basic_auth(username, password);
64 self.with_header(http::header::AUTHORIZATION, header_value)
65 }
66
67 #[cfg(feature = "auth")]
68 #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
69 fn bearer_auth<T>(self, token: T) -> Request<B>
70 where
71 T: std::fmt::Display,
72 {
73 let header_value = crate::util::bearer_auth(token);
74 self.with_header(http::header::AUTHORIZATION, header_value)
75 }
76
77 fn send<S, R>(self, client: S) -> impl Future<Output = crate::Result<S::Response>> + Send
78 where
79 B: http_body::Body<Data = Bytes> + Send + 'static,
80 B::Error: std::error::Error + Send + Sync,
81 S: tower_service::Service<Request<ClientBody>, Response = Response<R>> + Send,
82 R: http_body::Body,
83 <S as tower_service::Service<Request<ClientBody>>>::Error:
84 std::error::Error + Send + Sync + 'static,
85 <S as tower_service::Service<Request<ClientBody>>>::Future: Send;
86
87 #[cfg(feature = "rt-tokio")]
88 #[cfg_attr(docsrs, doc(cfg(feature = "rt-tokio")))]
89 fn send_timeout<S, R>(
91 self,
92 client: S,
93 timeout: std::time::Duration,
94 ) -> impl Future<Output = crate::Result<Response<MaybeAbort<R>>>> + Send
95 where
96 B: http_body::Body<Data = Bytes> + Send + 'static,
97 B::Error: std::error::Error + Send + Sync,
98 S: tower_service::Service<Request<ClientBody>, Response = Response<R>> + Send,
99 <S as tower_service::Service<Request<ClientBody>>>::Error:
100 std::error::Error + Send + Sync + 'static,
101 <S as tower_service::Service<Request<ClientBody>>>::Future: Send,
102 R: http_body::Body,
103 Self: Sized,
104 {
105 use tower::util::ServiceExt;
106 self.send(tower_http::timeout::Timeout::new(
107 client.map_response(|r| r.map(MaybeAbort::success)),
108 timeout,
109 ))
110 }
111}
112
113pub trait RequestBuilderExt: Sized {
115 #[cfg(feature = "json")]
116 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
117 fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>>;
118 #[cfg(feature = "query")]
119 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
120 fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Self>;
121 #[cfg(feature = "multipart")]
122 #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
123 fn multipart(self, form: multipart::Form) -> crate::Result<Request<crate::DynBody>>;
124 #[cfg(feature = "form")]
125 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
126 fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>>;
127 fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>>;
128 fn empty(self) -> crate::Result<Request<Empty<Bytes>>>;
129 fn headers(self, header_map: http::header::HeaderMap) -> Self;
130 #[cfg(feature = "auth")]
131 #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
132 fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self
133 where
134 U: std::fmt::Display,
135 P: std::fmt::Display,
136 Self: Sized;
137 #[cfg(feature = "auth")]
138 #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
139 fn bearer_auth<T>(self, token: T) -> Self
140 where
141 T: std::fmt::Display;
142}
143
144impl RequestBuilderExt for Builder {
145 #[cfg(feature = "json")]
147 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
148 fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>> {
149 let req = self
150 .body(())
151 .map_err(crate::Error::with_context("build request body"))?;
152 req.json(body)
153 }
154
155 #[cfg(feature = "query")]
157 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
158 fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Self> {
159 let new_uri = if let Some(uri) = self.uri_ref() {
160 build_query_uri(uri.clone(), query)?
161 } else {
162 Uri::default()
163 };
164 Ok(self.uri(new_uri))
165 }
166
167 #[cfg(feature = "multipart")]
169 #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
170 fn multipart(self, form: multipart::Form) -> crate::Result<Request<crate::DynBody>> {
171 let req = self
172 .body(())
173 .map_err(crate::Error::with_context("build request body"))?;
174 req.multipart(form)
175 }
176
177 #[cfg(feature = "form")]
179 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
180 fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>> {
181 let req = self
182 .body(())
183 .map_err(crate::Error::with_context("build request body"))?;
184 req.form(form)
185 }
186
187 fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>> {
189 let req = self
190 .body(())
191 .map_err(crate::Error::with_context("build request body"))?;
192 req.plain_text(body)
193 }
194
195 fn empty(self) -> crate::Result<Request<Empty<Bytes>>> {
197 self.body(())
198 .map_err(crate::Error::with_context("build request body"))?
199 .empty()
200 }
201
202 #[cfg(feature = "auth")]
204 #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
205 fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self
206 where
207 U: std::fmt::Display,
208 P: std::fmt::Display,
209 Self: Sized,
210 {
211 let header_value = crate::util::basic_auth(username, password);
212 self.header(http::header::AUTHORIZATION, header_value)
213 }
214
215 #[cfg(feature = "auth")]
217 #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
218 fn bearer_auth<T>(self, token: T) -> Self
219 where
220 T: std::fmt::Display,
221 {
222 let header_value = crate::util::bearer_auth(token);
223 self.header(http::header::AUTHORIZATION, header_value)
224 }
225
226 fn headers(mut self, header_map: http::header::HeaderMap) -> Self {
228 if let Some(headers) = self.headers_mut() {
229 headers.extend(header_map);
230 }
231 self
232 }
233}
234
235impl<B> RequestExt<B> for Request<B>
236where
237 B: Send,
238{
239 #[cfg(feature = "json")]
241 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
242 fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>> {
243 let json_body =
244 serde_json::to_vec(&body).map_err(crate::Error::with_context("serialize json body"))?;
245 let (mut parts, _) = self.into_parts();
246 parts.headers.insert(
247 CONTENT_TYPE,
248 HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
249 );
250 let request = Request::from_parts(parts, Full::new(Bytes::from(json_body)));
251 Ok(request)
252 }
253
254 #[cfg(feature = "query")]
256 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
257 fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Request<B>> {
258 use http::uri::PathAndQuery;
259 use std::str::FromStr;
260 let new_query = serde_urlencoded::to_string(query)
261 .map_err(crate::Error::with_context("serialize query string"))?;
262 if new_query.is_empty() {
263 return Ok(self);
264 }
265 let (mut parts, body) = self.into_parts();
266 let mut uri_parts = parts.uri.into_parts();
267 let new_uri = if let Some(pq) = uri_parts.path_and_query {
268 let mut new_pq_string = String::with_capacity(new_query.len() + pq.as_str().len() + 2);
269 new_pq_string.push_str(pq.path());
270 new_pq_string.push('?');
271 if let Some(old_query) = pq.query() {
272 new_pq_string.push_str(old_query);
273 new_pq_string.push('&');
274 }
275 new_pq_string.push_str(&new_query);
276 let new_pq = PathAndQuery::from_str(&new_pq_string)
277 .map_err(crate::Error::with_context("parse new path and query"))?;
278 uri_parts.path_and_query = Some(new_pq);
279 Uri::from_parts(uri_parts).map_err(crate::Error::with_context(
280 "reconstruct uri with new path and query",
281 ))?
282 } else {
283 Uri::builder()
284 .path_and_query(new_query)
285 .build()
286 .map_err(crate::Error::with_context("build new uri"))?
287 };
288 parts.uri = new_uri;
289 Ok(Request::from_parts(parts, body))
290 }
291
292 #[cfg(feature = "multipart")]
294 #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
295 fn multipart(self, mut form: multipart::Form) -> crate::Result<Request<crate::DynBody>> {
296 let (mut parts, _) = self.into_parts();
297 let boundary = form.boundary();
298 parts.headers.insert(
299 CONTENT_TYPE,
300 HeaderValue::from_str(&format!(
301 "{}; boundary={}",
302 mime::MULTIPART_FORM_DATA,
303 boundary
304 ))
305 .map_err(crate::Error::with_context("build content type header"))?,
306 );
307 if let Some(length) = form.compute_length() {
308 parts.headers.insert(
309 http::header::CONTENT_LENGTH,
310 HeaderValue::from_str(&length.to_string())
311 .map_err(crate::Error::with_context("build content length header"))?,
312 );
313 }
314 let body = form.stream();
315 Ok(Request::from_parts(parts, body))
316 }
317
318 #[cfg(feature = "form")]
320 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
321 fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>> {
322 let (mut parts, _) = self.into_parts();
323 let body = serde_urlencoded::to_string(form)
324 .map_err(crate::Error::with_context("serialize form"))?;
325 parts.headers.insert(
326 CONTENT_TYPE,
327 HeaderValue::from_static(mime::APPLICATION_WWW_FORM_URLENCODED.as_ref()),
328 );
329 Ok(Request::from_parts(parts, full(body)))
330 }
331
332 #[inline]
334 fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>> {
335 let (mut parts, _) = self.into_parts();
336 parts.headers.insert(
337 CONTENT_TYPE,
338 HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
339 );
340 Ok(Request::from_parts(parts, full(body)))
341 }
342
343 #[cfg(feature = "stream")]
345 fn stream<S: Stream>(self, stream: S) -> crate::Result<Request<StreamBody<S>>> {
346 let (parts, _) = self.into_parts();
347 Ok(Request::from_parts(parts, StreamBody::new(stream)))
348 }
349
350 #[inline]
352 fn empty(self) -> crate::Result<Request<Empty<Bytes>>> {
353 let (parts, _) = self.into_parts();
354 Ok(Request::from_parts(parts, empty()))
355 }
356
357 async fn collect_into_bytes(self) -> crate::Result<Request<Full<Bytes>>>
359 where
360 B: http_body::Body<Data = Bytes> + Send + 'static,
361 B::Error: std::error::Error + Send + Sync,
362 {
363 use http_body_util::BodyExt;
364 let (parts, body) = self.into_parts();
365 let body = body
366 .collect()
367 .await
368 .map_err(|e| {
369 crate::Error::new(
370 crate::ErrorKind::Body(BodyError(Box::new(e))),
371 "collect body stream",
372 )
373 })?
374 .to_bytes();
375 Ok(Request::from_parts(parts, full(body)))
376 }
377
378 #[inline]
380 fn with_version(mut self, version: http::Version) -> Request<B> {
381 *self.version_mut() = version;
382 self
383 }
384
385 #[inline]
387 fn with_method(mut self, method: http::Method) -> Request<B> {
388 *self.method_mut() = method;
389 self
390 }
391
392 #[inline]
407 fn with_header<K>(mut self, key: K, value: http::header::HeaderValue) -> Request<B>
408 where
409 K: http::header::IntoHeaderName,
410 {
411 self.headers_mut().insert(key, value);
412 self
413 }
414
415 #[inline]
417 fn with_headers(mut self, header_map: http::header::HeaderMap) -> Request<B> {
418 self.headers_mut().extend(header_map);
419 self
420 }
421
422 #[allow(unused_mut)]
426 async fn send<S, R>(self, mut client: S) -> crate::Result<S::Response>
427 where
428 B: http_body::Body<Data = Bytes> + Send + 'static,
429 B::Error: std::error::Error + Send + Sync,
430 S: tower_service::Service<Request<ClientBody>, Response = Response<R>> + Send,
431 R: http_body::Body,
432 <S as tower_service::Service<Request<ClientBody>>>::Error:
433 std::error::Error + Send + Sync + 'static,
434 <S as tower_service::Service<Request<ClientBody>>>::Future: Send,
435 {
436 use http_body_util::BodyExt;
437 #[allow(unused_imports)]
438 use tower_service::Service;
439 #[cfg(all(
440 any(
441 feature = "decompression-deflate",
442 feature = "decompression-gzip",
443 feature = "decompression-br",
444 feature = "decompression-zstd",
445 ),
446 feature = "rt-tokio"
447 ))]
448 let mut client = tower_http::decompression::Decompression::new(client);
449 let request = self.map(|b| UnsyncBoxBody::new(b.map_err(|e| BodyError(Box::new(e)))));
450 match client.call(request).await {
451 #[cfg(all(
452 any(
453 feature = "decompression-deflate",
454 feature = "decompression-gzip",
455 feature = "decompression-br",
456 feature = "decompression-zstd",
457 ),
458 feature = "rt-tokio"
459 ))]
460 Ok(response) => Ok(response.map(|b| b.into_inner())),
461 #[cfg(not(all(
462 any(
463 feature = "decompression-deflate",
464 feature = "decompression-gzip",
465 feature = "decompression-br",
466 feature = "decompression-zstd",
467 ),
468 feature = "rt-tokio"
469 )))]
470 Ok(response) => Ok(response),
471 Err(e) => {
472 let e = ClientError::from(e);
473 Err(crate::Error::new(
474 crate::ErrorKind::Client(e),
475 "send request",
476 ))
477 }
478 }
479 }
480}
481
482#[cfg(feature = "query")]
483fn build_query_uri<Q: Serialize + ?Sized>(uri: Uri, query: &Q) -> crate::Result<Uri> {
484 use std::str::FromStr;
485 let new_query = serde_urlencoded::to_string(query)
486 .map_err(crate::Error::with_context("serialize query string"))?;
487 if new_query.is_empty() {
488 return Ok(uri);
489 }
490 let mut uri_parts = uri.into_parts();
491 let new_uri = if let Some(pq) = uri_parts.path_and_query {
492 let mut new_pq_string = String::with_capacity(new_query.len() + pq.as_str().len() + 2);
493 new_pq_string.push_str(pq.path());
494 new_pq_string.push('?');
495 if let Some(old_query) = pq.query() {
496 new_pq_string.push_str(old_query);
497 new_pq_string.push('&');
498 }
499 new_pq_string.push_str(&new_query);
500 let new_pq = http::uri::PathAndQuery::from_str(&new_pq_string)
501 .map_err(crate::Error::with_context("parse new path and query"))?;
502 uri_parts.path_and_query = Some(new_pq);
503 Uri::from_parts(uri_parts).map_err(crate::Error::with_context(
504 "reconstruct uri with new path and query",
505 ))?
506 } else {
507 Uri::builder()
508 .path_and_query(new_query)
509 .build()
510 .map_err(crate::Error::with_context("build new uri"))?
511 };
512 Ok(new_uri)
513}
514
515#[cfg(test)]
519mod tests {
520
521 use super::*;
522 use std::collections::BTreeMap;
523
524 #[test]
525 fn add_query_append() -> crate::Result<()> {
526 let req = Request::get("https://google.com/")
527 .query(&[("foo", "bar")])?
528 .query(&[("qux", 3)])?
529 .empty()?;
530
531 assert_eq!(req.uri().query(), Some("foo=bar&qux=3"));
532 Ok(())
533 }
534
535 #[test]
536 fn add_query_append_same() -> crate::Result<()> {
537 let req = Request::get("https://google.com/")
538 .query(&[("foo", "a"), ("foo", "b")])?
539 .empty()?;
540
541 assert_eq!(req.uri().query(), Some("foo=a&foo=b"));
542 Ok(())
543 }
544
545 #[test]
546 fn add_query_struct() -> crate::Result<()> {
547 #[derive(serde::Serialize)]
548 struct Params {
549 foo: String,
550 qux: i32,
551 }
552
553 let params = Params {
554 foo: "bar".into(),
555 qux: 3,
556 };
557 let req = Request::get("https://google.com/")
558 .query(¶ms)?
559 .empty()?;
560
561 assert_eq!(req.uri().query(), Some("foo=bar&qux=3"));
562 Ok(())
563 }
564
565 #[test]
566 fn add_query_map() -> crate::Result<()> {
567 let mut params = BTreeMap::new();
568 params.insert("foo", "bar");
569 params.insert("qux", "three");
570
571 let req = Request::get("https://google.com/")
572 .query(¶ms)?
573 .empty()?;
574 assert_eq!(req.uri().query(), Some("foo=bar&qux=three"));
575 Ok(())
576 }
577
578 #[test]
579 fn test_replace_headers() {
580 use http::HeaderMap;
581
582 let mut headers = HeaderMap::new();
583 headers.insert("foo", "bar".parse().unwrap());
584 headers.append("foo", "baz".parse().unwrap());
585
586 let req = Request::get("https://hyper.rs")
587 .header("im-a", "keeper")
588 .header("foo", "pop me")
589 .headers(headers)
590 .empty()
591 .expect("request build");
592
593 assert_eq!(req.headers()["im-a"], "keeper");
594
595 let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
596 assert_eq!(foo.len(), 2);
597 assert_eq!(foo[0], "bar");
598 assert_eq!(foo[1], "baz");
599 }
600
601 #[test]
602 #[cfg(feature = "auth")]
603 fn test_basic_auth_sensitive_header() {
604 let some_url = "https://localhost/";
605
606 let req = Request::get(some_url)
607 .basic_auth("Aladdin", Some("open sesame"))
608 .empty()
609 .expect("request build");
610
611 assert_eq!(req.uri().to_string(), "https://localhost/");
612 assert_eq!(
613 req.headers()["authorization"],
614 "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
615 );
616 assert!(req.headers()["authorization"].is_sensitive());
617 }
618
619 #[test]
620 #[cfg(feature = "auth")]
621 fn test_bearer_auth_sensitive_header() {
622 let some_url = "https://localhost/";
623
624 let req = Request::get(some_url)
625 .bearer_auth("Hold my bear")
626 .empty()
627 .expect("request build");
628
629 assert_eq!(req.uri().to_string(), "https://localhost/");
630 assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
631 assert!(req.headers()["authorization"].is_sensitive());
632 }
633
634 #[test]
635 fn test_explicit_sensitive_header() {
636 let some_url = "https://localhost/";
637
638 let mut header = http::HeaderValue::from_static("in plain sight");
639 header.set_sensitive(true);
640
641 let req = Request::get(some_url)
642 .header("hiding", header)
643 .empty()
644 .expect("request build");
645
646 assert_eq!(req.uri().to_string(), "https://localhost/");
647 assert_eq!(req.headers()["hiding"], "in plain sight");
648 assert!(req.headers()["hiding"].is_sensitive());
649 }
650
651 #[test]
652 fn convert_from_http_request() {
653 let req = Request::builder()
654 .method("GET")
655 .uri("http://localhost/")
656 .header("User-Agent", "my-awesome-agent/1.0")
657 .body("test test test")
658 .unwrap();
659 let test_data = b"test test test";
660 assert_eq!(req.body().as_bytes(), &test_data[..]);
661 let headers = req.headers();
662 assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
663 assert_eq!(req.method(), http::Method::GET);
664 assert_eq!(req.uri().to_string(), "http://localhost/");
665 }
666
667 #[test]
668 fn set_http_request_version() {
669 let req = Request::builder()
670 .method("GET")
671 .uri("http://localhost/")
672 .header("User-Agent", "my-awesome-agent/1.0")
673 .version(http::Version::HTTP_11)
674 .body("test test test")
675 .unwrap();
676 let test_data = b"test test test";
677 assert_eq!(req.body().as_bytes(), &test_data[..]);
678 let headers = req.headers();
679 assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
680 assert_eq!(req.method(), http::Method::GET);
681 assert_eq!(req.uri().to_string(), "http://localhost/");
682 assert_eq!(req.version(), http::Version::HTTP_11);
683 }
684}