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