Add extension function on Easy for getting certinfo.

This commit is contained in:
jcamiel 2023-02-13 22:00:49 +01:00
parent 4acb12d2bd
commit 9fb1cb7801
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
4 changed files with 170 additions and 1 deletions

View File

@ -29,7 +29,7 @@ use super::request::*;
use super::request_spec::*; use super::request_spec::*;
use super::response::*; use super::response::*;
use super::{Header, HttpError, Verbosity}; use super::{Header, HttpError, Verbosity};
use crate::http::ContextDir; use crate::http::{easy_ext, ContextDir};
use crate::util::logger::Logger; use crate::util::logger::Logger;
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
@ -123,6 +123,9 @@ impl Client {
// way to get access to the outgoing headers. // way to get access to the outgoing headers.
self.handle.verbose(true).unwrap(); self.handle.verbose(true).unwrap();
// Activates the access of certificates info chain after a transfer has been executed.
self.handle.certinfo(true).unwrap();
if !options.connects_to.is_empty() { if !options.connects_to.is_empty() {
let connects = to_list(&options.connects_to); let connects = to_list(&options.connects_to);
self.handle.connect_to(connects).unwrap(); self.handle.connect_to(connects).unwrap();
@ -312,6 +315,7 @@ impl Client {
let duration = start.elapsed(); let duration = start.elapsed();
let length = response_body.len(); let length = response_body.len();
let certificate = None; let certificate = None;
let _certinfo = easy_ext::get_certinfo(&self.handle)?;
self.handle.reset(); self.handle.reset();
let request = Request { let request = Request {

View File

@ -0,0 +1,151 @@
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2023 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use curl::easy::Easy;
use curl::Error;
use curl_sys::{curl_certinfo, curl_slist};
use std::ffi::CStr;
use std::ptr;
/// Represents certificate information.
/// `data` has format "name:content";
pub struct CertInfo {
pub data: Vec<String>,
}
/// Returns the information of the first certificate in the certificates chain.
pub fn get_certinfo(easy: &Easy) -> Result<Option<CertInfo>, Error> {
unsafe {
let mut certinfo = ptr::null_mut::<curl_certinfo>();
let rc =
curl_sys::curl_easy_getinfo(easy.raw(), curl_sys::CURLINFO_CERTINFO, &mut certinfo);
if rc != curl_sys::CURLE_OK {
return Err(Error::new(rc));
}
if certinfo.is_null() {
return Ok(None);
}
let count = (*certinfo).num_of_certs;
if count <= 0 {
return Ok(None);
}
let slist = *((*certinfo).certinfo.offset(0));
let data = to_list(slist);
Ok(Some(CertInfo { data }))
}
}
/// Converts an instance of libcurl linked list [`curl_slist`] to a vec of [`String`].
fn to_list(slist: *mut curl_slist) -> Vec<String> {
let mut data = vec![];
let mut cur = slist;
loop {
if cur.is_null() {
break;
}
unsafe {
let ret = CStr::from_ptr((*cur).data).to_bytes();
let value = String::from_utf8_lossy(ret);
data.push(value.to_string());
cur = (*cur).next
}
}
data
}
// // Iterator based implementation more similar to curl crates List implementation.
// // See <https://github.com/alexcrichton/curl-rust/blob/main/src/easy/list.rs>
// pub struct CertInfo2 {
// raw: *mut curl_certinfo,
// }
//
// // An iterator over CertInfo2
// pub struct Iter<'a> {
// me: &'a CertInfo2,
// cur: u32,
// }
//
// pub unsafe fn from_raw(raw: *mut curl_certinfo) -> CertInfo2 {
// CertInfo2 { raw }
// }
//
// impl CertInfo2 {
// pub fn new() -> CertInfo2 {
// CertInfo2 {
// raw: ptr::null_mut(),
// }
// }
//
// pub fn iter(&self) -> Iter {
// Iter {
// me: self,
// cur: 0,
// }
// }
// }
//
// impl<'a> IntoIterator for &'a CertInfo2 {
// type Item = *mut curl_slist;
// type IntoIter = Iter<'a>;
//
// fn into_iter(self) -> Iter<'a> {
// self.iter()
// }
// }
//
// impl<'a> Iterator for Iter<'a> {
// type Item = *mut curl_slist;
//
// fn next(&mut self) -> Option<*mut curl_slist> {
// unsafe {
// if self.cur >= (*self.me.raw).num_of_certs as u32 {
// return None
// }
// let slist = *((*self.me.raw).certinfo.offset(self.cur as isize));
// self.cur += 1;
// Some(slist)
// }
// }
// }
#[cfg(test)]
mod tests {
use super::to_list;
use std::ffi::CString;
use std::ptr;
#[test]
fn convert_curl_slist_to_vec() {
let mut slist = ptr::null_mut();
unsafe {
for value in ["foo", "bar", "baz"] {
let str = CString::new(value).unwrap();
slist = curl_sys::curl_slist_append(slist, str.as_ptr());
}
}
assert_eq!(
to_list(slist),
vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
);
unsafe {
curl_sys::curl_slist_free_all(slist);
}
}
}

View File

@ -42,3 +42,16 @@ pub enum HttpError {
}, },
InvalidUrl(String), InvalidUrl(String),
} }
impl From<curl::Error> for HttpError {
fn from(err: curl::Error) -> Self {
let code = err.code() as i32;
let description = err.description().to_string();
let url = "".to_string();
HttpError::Libcurl {
code,
description,
url,
}
}
}

View File

@ -38,6 +38,7 @@ mod context_dir;
mod cookie; mod cookie;
mod core; mod core;
mod debug; mod debug;
mod easy_ext;
mod error; mod error;
mod header; mod header;
mod mimetype; mod mimetype;