h3i/prompts/h3/
headers.rs1use crate::quiche;
30use inquire::error::InquireResult;
31use inquire::validator::Validation;
32use inquire::Text;
33use quiche::h3::frame::Frame;
34
35use crate::encode_header_block;
36use crate::encode_header_block_literal;
37use crate::prompts::h3;
38use crate::StreamIdAllocator;
39
40use super::squish_suggester;
41use super::stream::prompt_fin_stream;
42use super::SuggestionResult;
43use super::AUTO_PICK;
44use super::EMPTY_PICKS;
45use super::ESC_TO_RET;
46use super::PUSH_ID_PROMPT;
47use super::STREAM_ID_PROMPT;
48use crate::actions::h3::Action;
49
50pub fn prompt_headers(
51 sid_alloc: &mut StreamIdAllocator, host_port: &str, raw: bool, literal: bool,
52) -> InquireResult<Action> {
53 let stream_id = Text::new(STREAM_ID_PROMPT)
54 .with_placeholder(EMPTY_PICKS)
55 .with_help_message(ESC_TO_RET)
56 .with_validator(validate_stream_id)
57 .prompt()?;
58
59 let stream_id = match stream_id.as_str() {
60 "" => {
61 let id = sid_alloc.peek_next_id();
62 println!("{AUTO_PICK}={id}");
63 id
64 },
65
66 _ => stream_id.parse::<u64>().unwrap(),
67 };
68
69 let mut headers = vec![];
70
71 if !raw {
72 headers.extend_from_slice(&pseudo_headers(host_port)?);
73 }
74
75 headers.extend_from_slice(&headers_read_loop()?);
76
77 sid_alloc.take_next_id();
78
79 let header_block = if literal {
80 encode_header_block_literal(&headers).unwrap_or_default()
81 } else {
82 encode_header_block(&headers).unwrap_or_default()
83 };
84
85 let fin_stream = prompt_fin_stream()?;
86
87 let action = Action::SendHeadersFrame {
88 stream_id,
89 fin_stream,
90 headers,
91 literal_headers: literal,
92 frame: Frame::Headers { header_block },
93 };
94
95 Ok(action)
96}
97
98pub fn prompt_push_promise() -> InquireResult<Action> {
99 let stream_id = h3::prompt_stream_id()?;
100 let push_id = h3::prompt_varint(PUSH_ID_PROMPT)?;
101
102 let headers = headers_read_loop()?;
103 let header_block = if headers.is_empty() {
104 vec![]
105 } else {
106 encode_header_block(&headers).unwrap()
107 };
108
109 let fin_stream = prompt_fin_stream()?;
110
111 let action = Action::SendFrame {
112 stream_id,
113 fin_stream,
114 frame: Frame::PushPromise {
115 push_id,
116 header_block,
117 },
118 };
119
120 Ok(action)
121}
122
123fn pseudo_headers(host_port: &str) -> InquireResult<Vec<quiche::h3::Header>> {
124 let method = Text::new("method:")
125 .with_autocomplete(&method_suggester)
126 .with_default("GET")
127 .with_help_message(ESC_TO_RET)
128 .prompt()?;
129
130 let help = format!("Press enter/return for default ({host_port}");
131 let authority = Text::new("authority:")
132 .with_default(host_port)
133 .with_help_message(&help)
134 .prompt()?;
135
136 let path = Text::new("path:").with_default("/").prompt()?;
137
138 let scheme = Text::new("scheme:")
139 .with_default("https")
140 .with_help_message(ESC_TO_RET)
141 .prompt()?;
142
143 Ok(vec![
144 quiche::h3::Header::new(b":method", method.as_bytes()),
145 quiche::h3::Header::new(b":authority", authority.as_bytes()),
146 quiche::h3::Header::new(b":path", path.as_bytes()),
147 quiche::h3::Header::new(b":scheme", scheme.as_bytes()),
148 ])
149}
150
151fn headers_read_loop() -> InquireResult<Vec<quiche::h3::Header>> {
152 let mut headers = vec![];
153 loop {
154 let name = Text::new("field name:")
155 .with_help_message(
156 "type 'q!' to complete headers, or ESC to return to actions",
157 )
158 .prompt()?;
159
160 if name == "q!" {
161 break;
162 }
163
164 let value = Text::new("field value:")
165 .with_help_message(ESC_TO_RET)
166 .prompt()?;
167
168 headers.push(quiche::h3::Header::new(name.as_bytes(), value.as_bytes()));
169 }
170
171 Ok(headers)
172}
173
174fn method_suggester(val: &str) -> SuggestionResult<Vec<String>> {
175 let suggestions = ["GET", "POST", "PUT", "DELETE"];
176
177 squish_suggester(&suggestions, val)
178}
179
180fn validate_stream_id(id: &str) -> SuggestionResult<Validation> {
181 if id.is_empty() {
182 return Ok(Validation::Valid);
183 }
184
185 h3::validate_varint(id)
186}