@@ -46,6 +46,121 @@ pub(crate) fn is_http_port(opts: &options::Options, port: u16) -> (bool, bool) {
4646 ( false , false )
4747}
4848
49+ pub ( crate ) async fn parse_http_response (
50+ opts : & options:: Options ,
51+ response : reqwest:: Response ,
52+ banner : & mut Banner ,
53+ address : & str ,
54+ port : u16 ,
55+ ) {
56+ let headers_of_interest: Vec < & str > = opts
57+ . port_scanner_http_headers
58+ . split ( ',' )
59+ . map ( |s| s. trim ( ) )
60+ . filter ( |s| !s. is_empty ( ) )
61+ . collect ( ) ;
62+ let mut content_type = String :: from ( "text/html" ) ;
63+
64+ // collect headers
65+ for ( name, value) in response. headers ( ) {
66+ let name = name. to_string ( ) ;
67+ let mut value = value. to_str ( ) . unwrap ( ) ;
68+
69+ if name == "content-type" {
70+ if value. contains ( ';' ) {
71+ value = value. split ( ';' ) . next ( ) . unwrap ( ) ;
72+ }
73+ value. clone_into ( & mut content_type) ;
74+ }
75+
76+ if headers_of_interest. contains ( & name. as_str ( ) ) {
77+ banner. insert ( name, value. to_owned ( ) ) ;
78+ }
79+ }
80+
81+ // collect info from html
82+ let body = response. text ( ) . await ;
83+ if let Ok ( body) = body {
84+ if content_type. contains ( "text/html" ) {
85+ if let Some ( caps) = HTML_TITLE_PARSER . captures ( & body) {
86+ banner. insert (
87+ "html.title" . to_owned ( ) ,
88+ caps. get ( 1 ) . unwrap ( ) . as_str ( ) . trim ( ) . to_owned ( ) ,
89+ ) ;
90+ }
91+ } else if content_type. contains ( "application/" ) || content_type. contains ( "text/" ) {
92+ banner. insert ( "body" . to_owned ( ) , body. to_owned ( ) ) ;
93+ }
94+ } else {
95+ log:: debug!(
96+ "can't read response body from {}:{}: {:?}" ,
97+ address,
98+ port,
99+ body. err( )
100+ ) ;
101+ }
102+ }
103+
104+ pub ( crate ) async fn parse_http_raw_response (
105+ opts : & options:: Options ,
106+ response : & str ,
107+ banner : & mut Banner ,
108+ ) {
109+ let headers_of_interest: Vec < & str > = opts
110+ . port_scanner_http_headers
111+ . split ( ',' )
112+ . map ( |s| s. trim ( ) )
113+ . filter ( |s| !s. is_empty ( ) )
114+ . collect ( ) ;
115+ let mut content_type = String :: from ( "text/html" ) ;
116+
117+ // split response into headers and body
118+ let lines = response. lines ( ) ;
119+ let mut headers_section = true ;
120+ let mut body_lines = Vec :: new ( ) ;
121+
122+ for line in lines {
123+ if headers_section {
124+ if line. trim ( ) . is_empty ( ) {
125+ // empty line indicates end of headers
126+ headers_section = false ;
127+ } else if let Some ( colon_pos) = line. find ( ':' ) {
128+ let name = line[ ..colon_pos] . trim ( ) . to_lowercase ( ) ;
129+ let value = line[ colon_pos + 1 ..] . trim ( ) ;
130+
131+ if name == "content-type" {
132+ let mut ct_value = value;
133+ if ct_value. contains ( ';' ) {
134+ ct_value = ct_value. split ( ';' ) . next ( ) . unwrap ( ) ;
135+ }
136+ ct_value. clone_into ( & mut content_type) ;
137+ }
138+
139+ if headers_of_interest. contains ( & name. as_str ( ) ) {
140+ banner. insert ( name, value. to_owned ( ) ) ;
141+ }
142+ }
143+ } else {
144+ body_lines. push ( line) ;
145+ }
146+ }
147+
148+ // process body
149+ if !body_lines. is_empty ( ) {
150+ let body = body_lines. join ( "\n " ) ;
151+ if content_type. contains ( "text/html" ) {
152+ if let Some ( caps) = HTML_TITLE_PARSER . captures ( & body) {
153+ banner. insert (
154+ "html.title" . to_owned ( ) ,
155+ caps. get ( 1 ) . unwrap ( ) . as_str ( ) . trim ( ) . to_owned ( ) ,
156+ ) ;
157+ }
158+ } else if content_type. contains ( "application/" ) || content_type. contains ( "text/" ) {
159+ banner. insert ( "body" . to_owned ( ) , body) ;
160+ }
161+ }
162+ }
163+
49164pub ( crate ) async fn http_grabber (
50165 opts : & options:: Options ,
51166 host : & str ,
@@ -161,52 +276,7 @@ pub(crate) async fn http_grabber(
161276 . await ;
162277
163278 if let Ok ( resp) = resp {
164- let headers_of_interest: Vec < & str > = opts
165- . port_scanner_http_headers
166- . split ( ',' )
167- . map ( |s| s. trim ( ) )
168- . filter ( |s| !s. is_empty ( ) )
169- . collect ( ) ;
170- let mut content_type = String :: from ( "text/html" ) ;
171-
172- // collect headers
173- for ( name, value) in resp. headers ( ) {
174- let name = name. to_string ( ) ;
175- let mut value = value. to_str ( ) . unwrap ( ) ;
176-
177- if name == "content-type" {
178- if value. contains ( ';' ) {
179- value = value. split ( ';' ) . next ( ) . unwrap ( ) ;
180- }
181- value. clone_into ( & mut content_type) ;
182- }
183-
184- if headers_of_interest. contains ( & name. as_str ( ) ) {
185- banner. insert ( name, value. to_owned ( ) ) ;
186- }
187- }
188-
189- // collect info from html
190- let body = resp. text ( ) . await ;
191- if let Ok ( body) = body {
192- if content_type. contains ( "text/html" ) {
193- if let Some ( caps) = HTML_TITLE_PARSER . captures ( & body) {
194- banner. insert (
195- "html.title" . to_owned ( ) ,
196- caps. get ( 1 ) . unwrap ( ) . as_str ( ) . trim ( ) . to_owned ( ) ,
197- ) ;
198- }
199- } else if content_type. contains ( "application/" ) || content_type. contains ( "text/" ) {
200- banner. insert ( "body" . to_owned ( ) , body. to_owned ( ) ) ;
201- }
202- } else {
203- log:: debug!(
204- "can't read response body from {}:{}: {:?}" ,
205- address,
206- port,
207- body. err( )
208- ) ;
209- }
279+ parse_http_response ( opts, resp, & mut banner, address, port) . await ;
210280 } else {
211281 log:: debug!(
212282 "can't connect via http client to {}:{}: {:?}" ,
0 commit comments