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 expected_result: Default::default(),
94 };
95
96 Ok(action)
97}
98
99pub fn prompt_push_promise() -> InquireResult<Action> {
100 let stream_id = h3::prompt_stream_id()?;
101 let push_id = h3::prompt_varint(PUSH_ID_PROMPT)?;
102
103 let headers = headers_read_loop()?;
104 let header_block = if headers.is_empty() {
105 vec![]
106 } else {
107 encode_header_block(&headers).unwrap()
108 };
109
110 let fin_stream = prompt_fin_stream()?;
111
112 let action = Action::SendFrame {
113 stream_id,
114 fin_stream,
115 frame: Frame::PushPromise {
116 push_id,
117 header_block,
118 },
119 expected_result: Default::default(),
120 };
121
122 Ok(action)
123}
124
125fn pseudo_headers(host_port: &str) -> InquireResult<Vec<quiche::h3::Header>> {
126 let method = Text::new("method:")
127 .with_autocomplete(&method_suggester)
128 .with_default("GET")
129 .with_help_message(ESC_TO_RET)
130 .prompt()?;
131
132 let help = format!("Press enter/return for default ({host_port}");
133 let authority = Text::new("authority:")
134 .with_default(host_port)
135 .with_help_message(&help)
136 .prompt()?;
137
138 let path = Text::new("path:").with_default("/").prompt()?;
139
140 let scheme = Text::new("scheme:")
141 .with_default("https")
142 .with_help_message(ESC_TO_RET)
143 .prompt()?;
144
145 Ok(vec![
146 quiche::h3::Header::new(b":method", method.as_bytes()),
147 quiche::h3::Header::new(b":authority", authority.as_bytes()),
148 quiche::h3::Header::new(b":path", path.as_bytes()),
149 quiche::h3::Header::new(b":scheme", scheme.as_bytes()),
150 ])
151}
152
153fn headers_read_loop() -> InquireResult<Vec<quiche::h3::Header>> {
154 let mut headers = vec![];
155 loop {
156 let name = Text::new("field name:")
157 .with_help_message(
158 "type 'q!' to complete headers, or ESC to return to actions",
159 )
160 .prompt()?;
161
162 if name == "q!" {
163 break;
164 }
165
166 let value = Text::new("field value:")
167 .with_help_message(ESC_TO_RET)
168 .prompt()?;
169
170 headers.push(quiche::h3::Header::new(name.as_bytes(), value.as_bytes()));
171 }
172
173 Ok(headers)
174}
175
176fn method_suggester(val: &str) -> SuggestionResult<Vec<String>> {
177 let suggestions = ["GET", "POST", "PUT", "DELETE"];
178
179 squish_suggester(&suggestions, val)
180}
181
182fn validate_stream_id(id: &str) -> SuggestionResult<Validation> {
183 if id.is_empty() {
184 return Ok(Validation::Valid);
185 }
186
187 h3::validate_varint(id)
188}