1use crate::host::{invoke, with_host, JsObj};
25use fusevm::Value;
26use indexmap::IndexMap;
27use rustls::pki_types::ServerName;
28use rustls::{ClientConnection, StreamOwned};
29use std::collections::HashMap;
30use std::io::{Read, Write};
31use std::net::TcpStream;
32
33pub const MODULE_METHODS: &[&str] = &["createServer", "request", "get"];
35
36pub const RESPONSE_METHODS: &[&str] = &[
39 "writeHead",
40 "setHeader",
41 "getHeader",
42 "getHeaderNames",
43 "getHeaders",
44 "hasHeader",
45 "removeHeader",
46 "write",
47 "end",
48 "flushHeaders",
49];
50pub const CLIENT_REQUEST_METHODS: &[&str] = &[
51 "write",
52 "end",
53 "setHeader",
54 "getHeader",
55 "removeHeader",
56 "abort",
57 "destroy",
58 "setTimeout",
59];
60
61pub fn constant(name: &str) -> Option<Value> {
67 match name {
68 "Agent" => Some(with_host(|h| h.alloc(JsObj::Builtin("https.Agent".into())))),
69 "globalAgent" => Some(with_host(|h| {
70 let mut m = IndexMap::new();
71 m.insert("@@native".into(), h.new_str("Agent"));
72 m.insert("maxSockets".into(), Value::Float(f64::INFINITY));
73 m.insert("protocol".into(), h.new_str("https:"));
74 h.new_object(m)
75 })),
76 _ => None,
77 }
78}
79
80pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
82 match method {
83 "createServer" => Some(create_server(args)),
84 "request" => Some(request(args, false)),
85 "get" => Some(request(args, true)),
86 _ => None,
87 }
88}
89
90fn get_prop(recv: &Value, key: &str) -> Option<Value> {
93 with_host(|h| match h.get(recv) {
94 Some(JsObj::Object(p)) => p.get(key).cloned(),
95 _ => None,
96 })
97}
98fn set_prop(recv: &Value, key: &str, val: Value) {
99 with_host(|h| {
100 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
101 p.insert(key.to_string(), val);
102 }
103 });
104}
105fn u64_prop(recv: &Value, key: &str) -> Option<u64> {
106 get_prop(recv, key).map(|v| with_host(|h| h.to_number(&v)) as u64)
107}
108
109fn value_bytes(v: Option<&Value>) -> Vec<u8> {
111 let Some(v) = v else { return Vec::new() };
112 let is_buffer =
113 with_host(|h| matches!(h.get(v), Some(JsObj::Object(p)) if p.contains_key("@@bytes")));
114 if is_buffer {
115 return with_host(|h| match h.get(v) {
116 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|b| h.get(b)) {
117 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u8).collect(),
118 _ => Vec::new(),
119 },
120 _ => Vec::new(),
121 });
122 }
123 with_host(|h| h.str_of(v)).into_bytes()
124}
125
126struct HttpsConn {
129 listener: Value,
130 buf: Vec<u8>,
131}
132
133struct ResState {
134 sock_id: u64,
135 head: bool,
138 status: u16,
139 message: Option<String>,
140 headers: Vec<(String, String)>,
141 body: Vec<u8>,
142}
143
144thread_local! {
145 static CONNS: std::cell::RefCell<HashMap<u64, HttpsConn>> =
146 std::cell::RefCell::new(HashMap::new());
147 static RESPONSES: std::cell::RefCell<HashMap<u64, ResState>> =
148 std::cell::RefCell::new(HashMap::new());
149 static NEXT_RESID: std::cell::Cell<u64> = const { std::cell::Cell::new(1) };
150 static CLIENT_REQS: std::cell::RefCell<HashMap<u64, ClientReq>> =
151 std::cell::RefCell::new(HashMap::new());
152 static NEXT_REQID: std::cell::Cell<u64> = const { std::cell::Cell::new(1) };
153}
154
155fn next_resid() -> u64 {
156 NEXT_RESID.with(|c| {
157 let id = c.get();
158 c.set(id + 1);
159 id
160 })
161}
162fn next_reqid() -> u64 {
163 NEXT_REQID.with(|c| {
164 let id = c.get();
165 c.set(id + 1);
166 id
167 })
168}
169
170pub fn create_server(args: &[Value]) -> Result<Value, String> {
173 let mut options: Option<Value> = None;
174 let mut listener = Value::Undef;
175 for a in args {
176 if with_host(|h| crate::host::is_callable(h, a)) {
177 listener = a.clone();
178 } else if matches!(a, Value::Obj(_)) {
179 options = Some(a.clone());
180 }
181 }
182 let opts = options.ok_or_else(|| {
183 crate::host::type_error(
184 "https.createServer requires an options object with `key` and `cert`",
185 )
186 })?;
187 let cert = value_bytes(get_prop(&opts, "cert").as_ref());
188 let key = value_bytes(get_prop(&opts, "key").as_ref());
189 if cert.is_empty() || key.is_empty() {
190 return Err(crate::host::type_error(
191 "https.createServer requires `key` and `cert`",
192 ));
193 }
194 let config = super::tls::build_server_config(&cert, &key)?;
195
196 let listener_for_hook = listener.clone();
198 let hook: super::tls::ConnHook =
199 std::rc::Rc::new(move |_server: &Value, _socket: &Value, sock_id: u64| {
200 CONNS.with(|c| {
201 c.borrow_mut().insert(
202 sock_id,
203 HttpsConn {
204 listener: listener_for_hook.clone(),
205 buf: Vec::new(),
206 },
207 );
208 });
209 Ok(())
210 });
211 Ok(super::tls::create_server_with_config(
212 config, hook, listener,
213 ))
214}
215
216pub fn drop_conn(sock_id: u64) {
218 CONNS.with(|c| {
219 c.borrow_mut().remove(&sock_id);
220 });
221}
222
223pub fn feed(sock_id: u64, _socket: &Value, bytes: &[u8]) -> Result<(), String> {
228 let is_https = CONNS.with(|c| c.borrow().contains_key(&sock_id));
229 if !is_https {
230 return Ok(());
231 }
232 CONNS.with(|c| {
233 c.borrow_mut()
234 .get_mut(&sock_id)
235 .unwrap()
236 .buf
237 .extend_from_slice(bytes)
238 });
239
240 loop {
241 let (listener, parsed) = CONNS.with(|c| {
242 let mut c = c.borrow_mut();
243 let conn = c.get_mut(&sock_id).unwrap();
244 match parse_request(&conn.buf) {
245 Some((req, consumed)) => {
246 conn.buf.drain(..consumed);
247 (conn.listener.clone(), Some(req))
248 }
249 None => (Value::Undef, None),
250 }
251 });
252 let Some(parsed) = parsed else { break };
253
254 let req = build_incoming(&parsed);
255 let res = build_response(sock_id, parsed.method.eq_ignore_ascii_case("HEAD"));
256 if with_host(|h| crate::host::is_callable(h, &listener)) {
257 invoke(&listener, vec![req.clone(), res], None)?;
258 }
259 if !parsed.body.is_empty() {
265 let encoding = with_host(|h| match h.get(&req) {
266 Some(JsObj::Object(p)) => p.get("@@encoding").map(|v| h.str_of(v)),
267 _ => None,
268 });
269 let chunk = match encoding {
270 Some(enc) => with_host(|h| {
271 let s = super::buffer::encode_bytes(&parsed.body, &enc);
272 h.new_str(s)
273 }),
274 None => super::buffer::from_bytes(&parsed.body),
275 };
276 super::events::instance_call(
277 &req,
278 "emit",
279 vec![with_host(|h| h.new_str("data")), chunk],
280 )?;
281 }
282 super::events::instance_call(&req, "emit", vec![with_host(|h| h.new_str("end"))])?;
283 }
284 Ok(())
285}
286
287struct ParsedReq {
289 method: String,
290 url: String,
291 http_version: String,
292 headers: Vec<(String, String)>,
293 body: Vec<u8>,
294}
295
296fn parse_request(buf: &[u8]) -> Option<(ParsedReq, usize)> {
299 let head_end = find_subslice(buf, b"\r\n\r\n")?;
300 let head = &buf[..head_end];
301 let body_start = head_end + 4;
302
303 let head_str = String::from_utf8_lossy(head);
304 let mut lines = head_str.split("\r\n");
305 let request_line = lines.next()?;
306 let mut parts = request_line.split(' ');
307 let method = parts.next()?.to_string();
308 let url = parts.next()?.to_string();
309 let version = parts.next().unwrap_or("HTTP/1.1");
310 let http_version = version.strip_prefix("HTTP/").unwrap_or("1.1").to_string();
311
312 let mut headers: Vec<(String, String)> = Vec::new();
313 let mut content_length = 0usize;
314 for line in lines {
315 if line.is_empty() {
316 continue;
317 }
318 if let Some((k, v)) = line.split_once(':') {
319 let name = k.trim().to_ascii_lowercase();
320 let value = v.trim().to_string();
321 if name == "content-length" {
322 content_length = value.parse().unwrap_or(0);
323 }
324 headers.push((name, value));
325 }
326 }
327 if buf.len() < body_start + content_length {
328 return None;
329 }
330 let body = buf[body_start..body_start + content_length].to_vec();
331 Some((
332 ParsedReq {
333 method,
334 url,
335 http_version,
336 headers,
337 body,
338 },
339 body_start + content_length,
340 ))
341}
342
343fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
344 haystack.windows(needle.len()).position(|w| w == needle)
345}
346
347fn build_incoming(req: &ParsedReq) -> Value {
348 let headers_obj = with_host(|h| {
349 let mut m = IndexMap::new();
350 for (k, v) in &req.headers {
351 m.insert(k.clone(), h.new_str(v.clone()));
352 }
353 h.new_object(m)
354 });
355 let mut extra = IndexMap::new();
356 extra.insert(
357 "method".into(),
358 with_host(|h| h.new_str(req.method.clone())),
359 );
360 extra.insert("url".into(), with_host(|h| h.new_str(req.url.clone())));
361 extra.insert(
362 "httpVersion".into(),
363 with_host(|h| h.new_str(req.http_version.clone())),
364 );
365 extra.insert("headers".into(), headers_obj);
366 super::tls::new_emitter_object("IncomingMessage", extra)
369}
370
371fn build_response(sock_id: u64, head: bool) -> Value {
372 let resid = next_resid();
373 RESPONSES.with(|r| {
374 r.borrow_mut().insert(
375 resid,
376 ResState {
377 sock_id,
378 head,
379 status: 200,
380 message: None,
381 headers: Vec::new(),
382 body: Vec::new(),
383 },
384 );
385 });
386 let mut extra = IndexMap::new();
387 extra.insert("@@resid".into(), Value::Float(resid as f64));
388 extra.insert("statusCode".into(), Value::Float(200.0));
389 super::tls::new_emitter_object("HTTPSServerResponse", extra)
390}
391
392pub fn instance_call(
395 tag: &str,
396 recv: &Value,
397 method: &str,
398 args: Vec<Value>,
399) -> Result<Value, String> {
400 if super::events::METHODS.contains(&method) {
401 return super::events::instance_call(recv, method, args);
402 }
403 match tag {
404 "HTTPSServerResponse" => response_call(recv, method, args),
405 "HTTPSClientRequest" => client_request_call(recv, method, args),
406 _ => Err(crate::host::type_error(&format!(
407 "{method} is not a function"
408 ))),
409 }
410}
411
412fn resid_of(res: &Value) -> Option<u64> {
413 u64_prop(res, "@@resid")
414}
415
416fn response_call(res: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
417 let Some(resid) = resid_of(res) else {
418 return Err(crate::host::type_error("invalid ServerResponse"));
419 };
420 match method {
421 "writeHead" => {
422 let status =
423 with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(200.0)) as u16;
424 let mut message: Option<String> = None;
425 let mut headers_arg: Option<Value> = None;
426 if let Some(a) = args.get(1) {
427 if with_host(|h| h.as_str(a)).is_some() {
428 message = Some(with_host(|h| h.str_of(a)));
429 } else if !matches!(a, Value::Undef) {
430 headers_arg = Some(a.clone());
431 }
432 }
433 if let Some(a) = args.get(2) {
434 if !matches!(a, Value::Undef) {
435 headers_arg = Some(a.clone());
436 }
437 }
438 let header_pairs = headers_arg.map(|h| object_pairs(&h)).unwrap_or_default();
439 RESPONSES.with(|r| {
440 if let Some(st) = r.borrow_mut().get_mut(&resid) {
441 st.status = status;
442 st.message = message;
443 for (k, v) in header_pairs {
444 upsert_header(&mut st.headers, &k, v);
445 }
446 }
447 });
448 set_prop(res, "statusCode", Value::Float(status as f64));
449 Ok(res.clone())
450 }
451 "setHeader" => {
452 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
453 let v = with_host(|h| h.str_of(&args.get(1).cloned().unwrap_or(Value::Undef)));
454 RESPONSES.with(|r| {
455 if let Some(st) = r.borrow_mut().get_mut(&resid) {
456 upsert_header(&mut st.headers, &k, v);
457 }
458 });
459 Ok(Value::Undef)
460 }
461 "getHeader" => {
462 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)))
463 .to_ascii_lowercase();
464 let val = RESPONSES.with(|r| {
465 r.borrow().get(&resid).and_then(|st| {
466 st.headers
467 .iter()
468 .find(|(hk, _)| hk.eq_ignore_ascii_case(&k))
469 .map(|(_, v)| v.clone())
470 })
471 });
472 Ok(val
473 .map(|v| with_host(|h| h.new_str(v)))
474 .unwrap_or(Value::Undef))
475 }
476 "removeHeader" => {
477 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
478 RESPONSES.with(|r| {
479 if let Some(st) = r.borrow_mut().get_mut(&resid) {
480 st.headers.retain(|(hk, _)| !hk.eq_ignore_ascii_case(&k));
481 }
482 });
483 Ok(Value::Undef)
484 }
485 "hasHeader" => {
491 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
492 let found = RESPONSES.with(|r| {
493 r.borrow()
494 .get(&resid)
495 .is_some_and(|st| st.headers.iter().any(|(hk, _)| hk.eq_ignore_ascii_case(&k)))
496 });
497 Ok(Value::Bool(found))
498 }
499 "getHeaderNames" => {
500 let names = RESPONSES.with(|r| {
501 r.borrow()
502 .get(&resid)
503 .map(|st| {
504 st.headers
505 .iter()
506 .map(|(k, _)| k.to_ascii_lowercase())
507 .collect::<Vec<_>>()
508 })
509 .unwrap_or_default()
510 });
511 Ok(with_host(|h| {
512 let items = names.into_iter().map(|n| h.new_str(n)).collect::<Vec<_>>();
513 h.new_array(items)
514 }))
515 }
516 "getHeaders" => {
517 let pairs = RESPONSES.with(|r| {
518 r.borrow()
519 .get(&resid)
520 .map(|st| {
521 st.headers
522 .iter()
523 .map(|(k, v)| (k.to_ascii_lowercase(), v.clone()))
524 .collect::<Vec<_>>()
525 })
526 .unwrap_or_default()
527 });
528 Ok(with_host(|h| {
529 let mut m = IndexMap::new();
530 for (k, v) in pairs {
531 m.insert(k, h.new_str(v));
532 }
533 h.new_object(m)
534 }))
535 }
536 "flushHeaders" => Ok(Value::Undef),
537 "write" => {
538 let bytes = value_bytes(args.first());
539 RESPONSES.with(|r| {
540 if let Some(st) = r.borrow_mut().get_mut(&resid) {
541 st.body.extend_from_slice(&bytes);
542 }
543 });
544 Ok(Value::Bool(true))
545 }
546 "end" => {
547 if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
548 let bytes = value_bytes(Some(chunk));
549 RESPONSES.with(|r| {
550 if let Some(st) = r.borrow_mut().get_mut(&resid) {
551 st.body.extend_from_slice(&bytes);
552 }
553 });
554 }
555 finish_response(res, resid)?;
556 Ok(res.clone())
557 }
558 _ => Err(crate::host::type_error(&format!(
559 "res.{method} is not a function"
560 ))),
561 }
562}
563
564fn finish_response(res: &Value, resid: u64) -> Result<(), String> {
565 let js_status = u64_prop(res, "statusCode").map(|n| n as u16);
566 let st = RESPONSES.with(|r| r.borrow_mut().remove(&resid));
567 let Some(mut st) = st else { return Ok(()) };
568 if let Some(s) = js_status {
569 st.status = s;
570 }
571 let payload = serialize_response(&mut st);
572 super::tls::socket_write(st.sock_id, &payload);
573 super::tls::socket_end(st.sock_id);
574 super::events::instance_call(res, "emit", vec![with_host(|h| h.new_str("finish"))])?;
575 Ok(())
576}
577
578fn serialize_response(st: &mut ResState) -> Vec<u8> {
582 let reason = st
583 .message
584 .clone()
585 .unwrap_or_else(|| status_text(st.status).to_string());
586 let mut out = format!("HTTP/1.1 {} {}\r\n", st.status, reason).into_bytes();
587 let has = |name: &str| st.headers.iter().any(|(k, _)| k.eq_ignore_ascii_case(name));
588 let chunked = st.headers.iter().any(|(k, v)| {
589 k.eq_ignore_ascii_case("transfer-encoding") && v.to_ascii_lowercase().contains("chunked")
590 });
591 for (k, v) in &st.headers {
592 out.extend_from_slice(format!("{k}: {v}\r\n").as_bytes());
593 }
594 if !chunked && !has("content-length") {
595 out.extend_from_slice(format!("Content-Length: {}\r\n", st.body.len()).as_bytes());
596 }
597 if !has("connection") {
598 out.extend_from_slice(b"Connection: close\r\n");
599 }
600 out.extend_from_slice(b"\r\n");
601 if st.head {
609 } else if chunked {
611 if !st.body.is_empty() {
612 out.extend_from_slice(format!("{:x}\r\n", st.body.len()).as_bytes());
613 out.extend_from_slice(&st.body);
614 out.extend_from_slice(b"\r\n");
615 }
616 out.extend_from_slice(b"0\r\n\r\n");
617 } else {
618 out.extend_from_slice(&st.body);
619 }
620 out
621}
622
623fn upsert_header(headers: &mut Vec<(String, String)>, name: &str, value: String) {
624 if let Some(slot) = headers
625 .iter_mut()
626 .find(|(k, _)| k.eq_ignore_ascii_case(name))
627 {
628 slot.1 = value;
629 } else {
630 headers.push((name.to_string(), value));
631 }
632}
633
634fn object_pairs(obj: &Value) -> Vec<(String, String)> {
635 with_host(|h| match h.get(obj) {
636 Some(JsObj::Object(p)) => p
637 .iter()
638 .filter(|(k, _)| !k.starts_with("@@") && !k.starts_with('#'))
639 .map(|(k, v)| (k.clone(), h.str_of(v)))
640 .collect(),
641 _ => Vec::new(),
642 })
643}
644
645fn status_text(code: u16) -> &'static str {
646 for &(c, msg) in super::http::status_table() {
647 if c == code {
648 return msg;
649 }
650 }
651 "OK"
652}
653
654struct ClientReq {
659 host: String,
660 port: u16,
661 servername: String,
662 reject_unauthorized: bool,
663 method: String,
664 path: String,
665 headers: Vec<(String, String)>,
666 body: Vec<u8>,
667 request: Value,
669 sent: bool,
670}
671
672pub fn request(args: &[Value], is_get: bool) -> Result<Value, String> {
675 let mut host = "localhost".to_string();
676 let mut port: u16 = 443;
677 let mut path = "/".to_string();
678 let mut method = "GET".to_string();
679 let mut servername: Option<String> = None;
680 let mut reject_unauthorized = true;
681 let mut headers: Vec<(String, String)> = Vec::new();
682 let mut cb: Option<Value> = None;
683
684 for a in args {
685 if with_host(|h| crate::host::is_callable(h, a)) {
686 cb = Some(a.clone());
687 } else if with_host(|h| h.as_str(a)).is_some() {
688 let url = with_host(|h| h.str_of(a));
690 parse_url(&url, &mut host, &mut port, &mut path);
691 } else if matches!(a, Value::Obj(_)) {
692 for key in ["hostname", "host"] {
693 if let Some(v) = get_prop(a, key).filter(|v| with_host(|h| h.as_str(v)).is_some()) {
694 host = with_host(|h| h.str_of(&v));
695 }
696 }
697 if let Some(v) = get_prop(a, "port") {
698 let n = with_host(|h| h.to_number(&v));
699 if !n.is_nan() {
700 port = n as u16;
701 }
702 }
703 if let Some(v) = get_prop(a, "path").filter(|v| with_host(|h| h.as_str(v)).is_some()) {
704 path = with_host(|h| h.str_of(&v));
705 }
706 if let Some(v) = get_prop(a, "method").filter(|v| with_host(|h| h.as_str(v)).is_some())
707 {
708 method = with_host(|h| h.str_of(&v));
709 }
710 if let Some(v) =
711 get_prop(a, "servername").filter(|v| with_host(|h| h.as_str(v)).is_some())
712 {
713 servername = Some(with_host(|h| h.str_of(&v)));
714 }
715 if let Some(v) = get_prop(a, "rejectUnauthorized") {
716 reject_unauthorized = with_host(|h| h.truthy(&v));
717 }
718 if let Some(hv) = get_prop(a, "headers").filter(|v| matches!(v, Value::Obj(_))) {
719 for (k, val) in object_pairs(&hv) {
720 headers.push((k, val));
721 }
722 }
723 }
724 }
725 if is_get {
726 method = "GET".to_string();
727 }
728 let servername = servername.unwrap_or_else(|| host.clone());
729
730 let reqid = next_reqid();
731 let mut extra = IndexMap::new();
732 extra.insert("@@reqid".into(), Value::Float(reqid as f64));
733 extra.insert("method".into(), with_host(|h| h.new_str(method.clone())));
734 extra.insert("path".into(), with_host(|h| h.new_str(path.clone())));
735 let request = super::tls::new_emitter_object("HTTPSClientRequest", extra);
736 if let Some(cb) = cb {
739 super::events::instance_call(
740 &request,
741 "on",
742 vec![with_host(|h| h.new_str("response")), cb],
743 )?;
744 }
745 CLIENT_REQS.with(|c| {
746 c.borrow_mut().insert(
747 reqid,
748 ClientReq {
749 host,
750 port,
751 servername,
752 reject_unauthorized,
753 method,
754 path,
755 headers,
756 body: Vec::new(),
757 request: request.clone(),
758 sent: false,
759 },
760 );
761 });
762 if is_get {
764 dispatch_request(reqid)?;
765 }
766 Ok(request)
767}
768
769fn parse_url(url: &str, host: &mut String, port: &mut u16, path: &mut String) {
770 let rest = url.strip_prefix("https://").unwrap_or(url);
771 let (authority, p) = match rest.find('/') {
772 Some(i) => (&rest[..i], &rest[i..]),
773 None => (rest, "/"),
774 };
775 *path = if p.is_empty() {
776 "/".to_string()
777 } else {
778 p.to_string()
779 };
780 if let Some((h, port_str)) = authority.rsplit_once(':') {
781 *host = h.to_string();
782 if let Ok(n) = port_str.parse::<u16>() {
783 *port = n;
784 }
785 } else {
786 *host = authority.to_string();
787 *port = 443;
788 }
789}
790
791fn client_request_call(req: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
792 let reqid = u64_prop(req, "@@reqid");
793 match method {
794 "write" => {
795 if let Some(id) = reqid {
796 let bytes = value_bytes(args.first());
797 CLIENT_REQS.with(|c| {
798 if let Some(r) = c.borrow_mut().get_mut(&id) {
799 r.body.extend_from_slice(&bytes);
800 }
801 });
802 }
803 Ok(Value::Bool(true))
804 }
805 "end" => {
806 if let Some(id) = reqid {
807 if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
808 let bytes = value_bytes(Some(chunk));
809 CLIENT_REQS.with(|c| {
810 if let Some(r) = c.borrow_mut().get_mut(&id) {
811 r.body.extend_from_slice(&bytes);
812 }
813 });
814 }
815 dispatch_request(id)?;
816 }
817 Ok(req.clone())
818 }
819 "setHeader" => {
820 if let Some(id) = reqid {
821 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
822 let v = with_host(|h| h.str_of(&args.get(1).cloned().unwrap_or(Value::Undef)));
823 CLIENT_REQS.with(|c| {
824 if let Some(r) = c.borrow_mut().get_mut(&id) {
825 upsert_header(&mut r.headers, &k, v);
826 }
827 });
828 }
829 Ok(Value::Undef)
830 }
831 "getHeader" => {
832 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)))
833 .to_ascii_lowercase();
834 let val = reqid.and_then(|id| {
835 CLIENT_REQS.with(|c| {
836 c.borrow().get(&id).and_then(|r| {
837 r.headers
838 .iter()
839 .find(|(hk, _)| hk.eq_ignore_ascii_case(&k))
840 .map(|(_, v)| v.clone())
841 })
842 })
843 });
844 Ok(val
845 .map(|v| with_host(|h| h.new_str(v)))
846 .unwrap_or(Value::Undef))
847 }
848 "removeHeader" => {
849 if let Some(id) = reqid {
850 let k = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
851 CLIENT_REQS.with(|c| {
852 if let Some(r) = c.borrow_mut().get_mut(&id) {
853 r.headers.retain(|(hk, _)| !hk.eq_ignore_ascii_case(&k));
854 }
855 });
856 }
857 Ok(Value::Undef)
858 }
859 "abort" | "destroy" | "setTimeout" => Ok(req.clone()),
860 _ => Err(crate::host::type_error(&format!(
861 "req.{method} is not a function"
862 ))),
863 }
864}
865
866fn dispatch_request(reqid: u64) -> Result<(), String> {
869 let sent = CLIENT_REQS.with(|c| c.borrow().get(&reqid).map(|r| r.sent).unwrap_or(true));
871 if sent {
872 return Ok(());
873 }
874 CLIENT_REQS.with(|c| {
875 if let Some(r) = c.borrow_mut().get_mut(&reqid) {
876 r.sent = true;
877 }
878 });
879
880 let (host, port, servername, reject, method, path, headers, body) = CLIENT_REQS.with(|c| {
881 let b = c.borrow();
882 let r = b.get(&reqid).unwrap();
883 (
884 r.host.clone(),
885 r.port,
886 r.servername.clone(),
887 r.reject_unauthorized,
888 r.method.clone(),
889 r.path.clone(),
890 r.headers.clone(),
891 r.body.clone(),
892 )
893 });
894
895 let config = super::tls::client_config(reject);
896 let io_tx = with_host(|h| h.io_sender());
897 with_host(|h| h.incr_handle());
898
899 let mut has_host = false;
901 let mut has_len = false;
902 let mut header_block = String::new();
903 for (k, v) in &headers {
904 if k.eq_ignore_ascii_case("host") {
905 has_host = true;
906 }
907 if k.eq_ignore_ascii_case("content-length") {
908 has_len = true;
909 }
910 if k.eq_ignore_ascii_case("connection") {
911 continue; }
913 header_block.push_str(&format!("{k}: {v}\r\n"));
914 }
915 let host_header = if port == 443 {
916 host.clone()
917 } else {
918 format!("{host}:{port}")
919 };
920 let mut request_bytes = format!("{method} {path} HTTP/1.1\r\n");
921 if !has_host {
922 request_bytes.push_str(&format!("Host: {host_header}\r\n"));
923 }
924 request_bytes.push_str(&header_block);
925 if !has_len && !body.is_empty() {
926 request_bytes.push_str(&format!("Content-Length: {}\r\n", body.len()));
927 }
928 request_bytes.push_str("Connection: close\r\n\r\n");
929 let mut wire = request_bytes.into_bytes();
930 wire.extend_from_slice(&body);
931
932 std::thread::spawn(move || {
933 let result = exchange(&host, port, &servername, config, &wire);
934 match result {
935 Ok(raw) => {
936 let _ = io_tx.send(Box::new(move || deliver_response(reqid, raw)));
937 }
938 Err(msg) => {
939 let _ = io_tx.send(Box::new(move || deliver_error(reqid, msg)));
940 }
941 }
942 });
943 Ok(())
944}
945
946pub(crate) fn exchange(
949 host: &str,
950 port: u16,
951 servername: &str,
952 config: std::sync::Arc<rustls::ClientConfig>,
953 request: &[u8],
954) -> Result<Vec<u8>, String> {
955 let server_name = ServerName::try_from(servername.to_string())
956 .map_err(|_| format!("Error: tls: invalid servername '{servername}'"))?;
957 let sock = TcpStream::connect((host, port))
958 .map_err(|e| format!("Error: connect ECONNREFUSED {host}:{port}: {e}"))?;
959 let conn =
960 ClientConnection::new(config, server_name).map_err(|e| format!("Error: tls: {e}"))?;
961 let mut stream = StreamOwned::new(conn, sock);
962 stream
963 .write_all(request)
964 .map_err(|e| format!("Error: https write: {e}"))?;
965 stream
966 .flush()
967 .map_err(|e| format!("Error: https flush: {e}"))?;
968 let head_request = request
969 .split(|b| *b == b' ')
970 .next()
971 .is_some_and(|m| m.eq_ignore_ascii_case(b"HEAD"));
972 let mut raw = Vec::new();
973 let mut buf = [0u8; 16384];
985 loop {
986 if super::http::response_is_complete(&raw, head_request) {
987 break;
988 }
989 match stream.read(&mut buf) {
990 Ok(0) => break,
991 Ok(n) => raw.extend_from_slice(&buf[..n]),
992 Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
993 Err(e) => {
994 if raw.is_empty() {
995 return Err(format!("Error: https read: {e}"));
996 }
997 break;
998 }
999 }
1000 }
1001 Ok(raw)
1002}
1003
1004fn deliver_response(reqid: u64, raw: Vec<u8>) -> Result<(), String> {
1007 let entry = CLIENT_REQS.with(|c| c.borrow_mut().remove(&reqid));
1008 with_host(|h| h.decr_handle());
1009 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
1010 let Some(entry) = entry else { return Ok(()) };
1011
1012 let (status, message, http_version, headers, body) = parse_response(&raw);
1013
1014 let headers_obj = with_host(|h| {
1015 let mut m = IndexMap::new();
1016 for (k, v) in &headers {
1017 m.insert(k.clone(), h.new_str(v.clone()));
1018 }
1019 h.new_object(m)
1020 });
1021 let mut extra = IndexMap::new();
1022 extra.insert("statusCode".into(), Value::Float(status as f64));
1023 extra.insert("statusMessage".into(), with_host(|h| h.new_str(message)));
1024 extra.insert("httpVersion".into(), with_host(|h| h.new_str(http_version)));
1025 extra.insert("headers".into(), headers_obj);
1026 let res = super::tls::new_emitter_object("IncomingMessage", extra);
1027
1028 super::events::instance_call(
1030 &entry.request,
1031 "emit",
1032 vec![with_host(|h| h.new_str("response")), res.clone()],
1033 )?;
1034 if !body.is_empty() {
1035 let chunk = super::buffer::from_bytes(&body);
1036 super::events::instance_call(&res, "emit", vec![with_host(|h| h.new_str("data")), chunk])?;
1037 }
1038 super::events::instance_call(&res, "emit", vec![with_host(|h| h.new_str("end"))])?;
1039 Ok(())
1040}
1041
1042fn deliver_error(reqid: u64, msg: String) -> Result<(), String> {
1043 let entry = CLIENT_REQS.with(|c| c.borrow_mut().remove(&reqid));
1044 with_host(|h| h.decr_handle());
1045 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
1046 if let Some(entry) = entry {
1047 let err = with_host(|h| {
1048 let mut m = IndexMap::new();
1049 m.insert("message".into(), h.new_str(msg.clone()));
1050 h.new_object(m)
1051 });
1052 super::events::instance_call(
1053 &entry.request,
1054 "emit",
1055 vec![with_host(|h| h.new_str("error")), err],
1056 )?;
1057 }
1058 Ok(())
1059}
1060
1061fn parse_response(raw: &[u8]) -> (u16, String, String, Vec<(String, String)>, Vec<u8>) {
1064 let head_end = find_subslice(raw, b"\r\n\r\n").unwrap_or(raw.len());
1065 let head = String::from_utf8_lossy(&raw[..head_end]);
1066 let body_start = (head_end + 4).min(raw.len());
1067 let mut lines = head.split("\r\n");
1068 let status_line = lines.next().unwrap_or("");
1069 let mut sp = status_line.splitn(3, ' ');
1070 let version = sp
1071 .next()
1072 .unwrap_or("HTTP/1.1")
1073 .strip_prefix("HTTP/")
1074 .unwrap_or("1.1")
1075 .to_string();
1076 let status = sp.next().and_then(|s| s.parse::<u16>().ok()).unwrap_or(0);
1077 let message = sp.next().unwrap_or("").to_string();
1078
1079 let mut headers: Vec<(String, String)> = Vec::new();
1080 let mut chunked = false;
1081 for line in lines {
1082 if line.is_empty() {
1083 continue;
1084 }
1085 if let Some((k, v)) = line.split_once(':') {
1086 let name = k.trim().to_ascii_lowercase();
1087 let value = v.trim().to_string();
1088 if name == "transfer-encoding" && value.to_ascii_lowercase().contains("chunked") {
1089 chunked = true;
1090 }
1091 headers.push((name, value));
1092 }
1093 }
1094 let raw_body = &raw[body_start..];
1095 let body = if chunked {
1096 decode_chunked(raw_body)
1097 } else {
1098 raw_body.to_vec()
1099 };
1100 (status, message, version, headers, body)
1101}
1102
1103fn decode_chunked(mut data: &[u8]) -> Vec<u8> {
1106 let mut out = Vec::new();
1107 while let Some(nl) = find_subslice(data, b"\r\n") {
1108 let size_line = String::from_utf8_lossy(&data[..nl]);
1109 let size_hex = size_line.split(';').next().unwrap_or("").trim();
1110 let size = usize::from_str_radix(size_hex, 16).unwrap_or(0);
1111 if size == 0 {
1112 break;
1113 }
1114 let chunk_start = nl + 2;
1115 let chunk_end = (chunk_start + size).min(data.len());
1116 out.extend_from_slice(&data[chunk_start..chunk_end]);
1117 let next = chunk_end + 2;
1119 if next >= data.len() {
1120 break;
1121 }
1122 data = &data[next..];
1123 }
1124 out
1125}