/* Copyright (C) 2021 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

use super::detect;
use crate::core::{
    Direction, Flow, HttpRangeContainerBlock, StreamingBufferConfig, SuricataFileContext, SC,
};
use crate::http2::http2::HTTP2Transaction;
use crate::http2::http2::SURICATA_HTTP2_FILE_CONFIG;

use nom7::branch::alt;
use nom7::bytes::streaming::{take_till, take_while};
use nom7::character::complete::{char, digit1};
use nom7::combinator::{map_res, value};
use nom7::error::{make_error, ErrorKind};
use nom7::{Err, IResult};
use std::os::raw::c_uchar;
use std::str::FromStr;

#[derive(Debug)]
#[repr(C)]
pub struct HTTPContentRange {
    pub start: i64,
    pub end: i64,
    pub size: i64,
}

pub fn http2_parse_content_range_star(input: &[u8]) -> IResult<&[u8], HTTPContentRange> {
    let (i2, _) = char('*')(input)?;
    let (i2, _) = char('/')(i2)?;
    let (i2, size) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?;
    return Ok((
        i2,
        HTTPContentRange {
            start: -1,
            end: -1,
            size,
        },
    ));
}

pub fn http2_parse_content_range_def(input: &[u8]) -> IResult<&[u8], HTTPContentRange> {
    let (i2, start) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(input)?;
    let (i2, _) = char('-')(i2)?;
    let (i2, end) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?;
    let (i2, _) = char('/')(i2)?;
    let (i2, size) = alt((
        value(-1, char('*')),
        map_res(map_res(digit1, std::str::from_utf8), i64::from_str),
    ))(i2)?;
    return Ok((i2, HTTPContentRange { start, end, size }));
}

fn http2_parse_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> {
    let (i2, _) = take_while(|c| c == b' ')(input)?;
    let (i2, _) = take_till(|c| c == b' ')(i2)?;
    let (i2, _) = take_while(|c| c == b' ')(i2)?;
    return alt((
        http2_parse_content_range_star,
        http2_parse_content_range_def,
    ))(i2);
}

pub fn http2_parse_check_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> {
    let (rem, v) = http2_parse_content_range(input)?;
    if v.start > v.end || (v.end > 0 && v.size > 0 && v.end > v.size - 1) {
        return Err(Err::Error(make_error(rem, ErrorKind::Verify)));
    }
    return Ok((rem, v));
}

#[no_mangle]
pub unsafe extern "C" fn rs_http_parse_content_range(
    cr: &mut HTTPContentRange, buffer: *const u8, buffer_len: u32,
) -> std::os::raw::c_int {
    let slice = build_slice!(buffer, buffer_len as usize);
    match http2_parse_content_range(slice) {
        Ok((_, c)) => {
            *cr = c;
            return 0;
        }
        _ => {
            return -1;
        }
    }
}

fn http2_range_key_get(tx: &mut HTTP2Transaction) -> Result<(Vec<u8>, usize), ()> {
    let hostv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":authority")?;
    let mut hostv = &hostv[..];
    if let Some(p) = hostv.iter().position(|&x| x == b':') {
        hostv = &hostv[..p];
    }
    let uriv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":path")?;
    let mut uriv = &uriv[..];
    if let Some(p) = uriv.iter().position(|&x| x == b'?') {
        uriv = &uriv[..p];
    }
    if let Some(p) = uriv.iter().rposition(|&x| x == b'/') {
        uriv = &uriv[p..];
    }
    let mut r = Vec::with_capacity(hostv.len() + uriv.len());
    r.extend_from_slice(hostv);
    r.extend_from_slice(uriv);
    return Ok((r, hostv.len()));
}

pub fn http2_range_open(
    tx: &mut HTTP2Transaction, v: &HTTPContentRange, flow: *const Flow,
    cfg: &'static SuricataFileContext, dir: Direction, data: &[u8],
) {
    if v.end <= 0 || v.size <= 0 {
        // skipped for incomplete range information
        return;
    }
    if v.end == v.size - 1 && v.start == 0 {
        // whole file in one range
        return;
    }
    let flags = if dir == Direction::ToServer { tx.ft_ts.file_flags } else { tx.ft_tc.file_flags };
    if let Ok((key, index)) = http2_range_key_get(tx) {
        let name = &key[index..];
        tx.file_range = unsafe {
            HttpRangeContainerOpenFile(
                key.as_ptr(),
                key.len() as u32,
                flow,
                v,
                cfg.files_sbcfg,
                name.as_ptr(),
                name.len() as u16,
                flags,
                data.as_ptr(),
                data.len() as u32,
            )
        };
    }
}

pub fn http2_range_append(cfg: &'static SuricataFileContext, fr: *mut HttpRangeContainerBlock, data: &[u8]) {
    unsafe {
        HttpRangeAppendData(cfg.files_sbcfg, fr, data.as_ptr(), data.len() as u32);
    }
}

pub fn http2_range_close(
    tx: &mut HTTP2Transaction, dir: Direction, data: &[u8],
) {
    let added = if let Some(c) = unsafe { SC } {
        if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } {
            let (files, flags) = if dir == Direction::ToServer {
                (&mut tx.ft_ts.file, tx.ft_ts.file_flags)
            } else {
                (&mut tx.ft_tc.file, tx.ft_tc.file_flags)
            };
            let added = (c.HTPFileCloseHandleRange)(
                    sfcm.files_sbcfg,
                    files,
                    flags,
                    tx.file_range,
                    data.as_ptr(),
                    data.len() as u32,
                    );
            (c.HttpRangeFreeBlock)(tx.file_range);
            added
        } else {
            false
        }
    } else {
        false
    };
    tx.file_range = std::ptr::null_mut();
    if added {
        tx.tx_data.incr_files_opened();
    }
}

// Defined in app-layer-htp-range.h
extern "C" {
    pub fn HttpRangeContainerOpenFile(
        key: *const c_uchar, keylen: u32, f: *const Flow, cr: &HTTPContentRange,
        sbcfg: *const StreamingBufferConfig, name: *const c_uchar, name_len: u16, flags: u16,
        data: *const c_uchar, data_len: u32,
    ) -> *mut HttpRangeContainerBlock;
    pub fn HttpRangeAppendData(
        cfg: *const StreamingBufferConfig, c: *mut HttpRangeContainerBlock, data: *const c_uchar, data_len: u32,
    ) -> std::os::raw::c_int;
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_http2_parse_content_range() {
        let buf0: &[u8] = " bytes */100".as_bytes();
        let r0 = http2_parse_content_range(buf0);
        match r0 {
            Ok((rem, rg)) => {
                // Check the first message.
                assert_eq!(rg.start, -1);
                assert_eq!(rg.end, -1);
                assert_eq!(rg.size, 100);
                // And we should have no bytes left.
                assert_eq!(rem.len(), 0);
            }
            _ => {
                panic!("Result should have been ok.");
            }
        }

        let buf1: &[u8] = " bytes 10-20/200".as_bytes();
        let r1 = http2_parse_content_range(buf1);
        match r1 {
            Ok((rem, rg)) => {
                // Check the first message.
                assert_eq!(rg.start, 10);
                assert_eq!(rg.end, 20);
                assert_eq!(rg.size, 200);
                // And we should have no bytes left.
                assert_eq!(rem.len(), 0);
            }
            _ => {
                panic!("Result should have been ok.");
            }
        }

        let buf2: &[u8] = " bytes 30-68/*".as_bytes();
        let r2 = http2_parse_content_range(buf2);
        match r2 {
            Ok((rem, rg)) => {
                // Check the first message.
                assert_eq!(rg.start, 30);
                assert_eq!(rg.end, 68);
                assert_eq!(rg.size, -1);
                // And we should have no bytes left.
                assert_eq!(rem.len(), 0);
            }
            _ => {
                panic!("Result should have been ok.");
            }
        }
    }
}
