diff options
author | Shav Kinderlehrer <[email protected]> | 2024-07-23 17:48:28 -0400 |
---|---|---|
committer | Shav Kinderlehrer <[email protected]> | 2024-07-23 17:48:28 -0400 |
commit | dc0f2ce9ba97ebb47e05b80a511da6eb29818b63 (patch) | |
tree | dc83035069f5a015047be1ca3da6f65781eb4695 /src | |
parent | f638f4bd1e3a03bc2bdd5f9dcd57d4830fd3c553 (diff) | |
download | molehole-ncurses.tar.gz molehole-ncurses.zip |
Merge old-moleholencurses
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 154 | ||||
-rw-r--r-- | src/app_action.rs | 24 | ||||
-rw-r--r-- | src/app_event.rs | 10 | ||||
-rw-r--r-- | src/component.rs | 43 | ||||
-rw-r--r-- | src/components/global_keys.rs | 131 | ||||
-rw-r--r-- | src/components/mod.rs | 3 | ||||
-rw-r--r-- | src/components/status.rs | 86 | ||||
-rw-r--r-- | src/components/url_manager.rs | 34 | ||||
-rwxr-xr-x | src/config.c | 18 | ||||
-rw-r--r-- | src/keys/key_commands.rs | 55 | ||||
-rw-r--r-- | src/keys/mod.rs | 1 | ||||
-rwxr-xr-x | src/main.c | 83 | ||||
-rw-r--r-- | src/main.rs | 59 | ||||
-rwxr-xr-x | src/molerat/connect.c | 133 | ||||
-rwxr-xr-x | src/molerat/net.c | 68 | ||||
-rwxr-xr-x | src/molerat/request.c | 34 | ||||
-rwxr-xr-x | src/molerat/response.c | 215 | ||||
-rwxr-xr-x | src/molerat/url.c | 181 | ||||
-rw-r--r-- | src/tui.rs | 62 | ||||
-rwxr-xr-x | src/ui/color.c | 10 | ||||
-rwxr-xr-x | src/ui/status.c | 54 | ||||
-rwxr-xr-x | src/util.c | 11 |
22 files changed, 807 insertions, 662 deletions
diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index 7d398f0..0000000 --- a/src/app.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crossterm::event::Event; -use eyre::Result; -use ratatui::prelude::*; -use std::time::Duration; - -use crate::app_action::AppAction; -use crate::app_event::AppEvent; -use crate::component::Component; -use crate::components; -use crate::keys::key_commands::KeyCommand; -use crate::tui; - -pub struct App { - pub tui: tui::Tui, - pub tick_rate: Duration, - pub components: Vec<Box<dyn Component>>, - pub key_commands: Vec<KeyCommand>, - - should_quit: bool, -} - -impl App { - pub fn new(tick_rate: Duration) -> Result<Self> { - let tui = tui::init()?; - - Ok(Self { - tui, - tick_rate, - - should_quit: false, - components: vec![], - key_commands: vec![], - }) - } - - pub fn run(&mut self) -> Result<()> { - let global_keys = components::global_keys::GlobalKeys { - key_commands: self.key_commands.clone(), - ..Default::default() - }; - let status_bar = components::status::StatusBar { - message: "Press '?' to show the help menu".to_string(), - ..Default::default() - }; - let url_manager = components::url_manager::UrlManager::default(); - self.components = vec![ - Box::new(global_keys), - Box::new(status_bar), - Box::new(url_manager), - ]; - - for component in &mut self.components { - component.init()?; - } - - loop { - if self.should_quit { - break Ok(()); - } - - self.draw()?; - } - } - - pub fn draw(&mut self) -> Result<()> { - let event: Option<AppEvent> = match tui::get_event(self.tick_rate)? { - Some(event) => match event { - Event::Key(key) => Some(AppEvent::Key(key)), - Event::Mouse(mouse) => Some(AppEvent::Mouse(mouse)), - Event::FocusGained => todo!(), - Event::FocusLost => todo!(), - Event::Paste(_) => todo!(), - Event::Resize(_, _) => todo!(), - }, - None => None, - }; - - if let Some(event) = event { - self.handle_event(event)?; - } - - if self.should_quit { - return Ok(()); - } - - self.tui.draw(|frame| { - let layout = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![ - Constraint::Percentage(100), - Constraint::Min(1), - ]) - .split(frame.size()); - - // status bar - let _ = self.components[1].render(frame, layout[1]); - - // global_keys - let _ = self.components[0].render(frame, frame.size()); - })?; - - self.update()?; - - Ok(()) - } - - pub fn update(&mut self) -> Result<()> { - let mut events: Vec<AppEvent> = vec![]; - for component in &mut self.components { - if let Some(event) = component.update() { - events.push(event); - } - } - - for event in events { - self.handle_event(event)?; - } - - Ok(()) - } - - pub fn quit(&mut self) -> Result<()> { - tui::restore()?; - self.should_quit = true; - - Ok(()) - } - - fn handle_action(&mut self, action: AppAction) -> Result<()> { - match action { - AppAction::Quit => Ok(self.quit()?), - _ => { - for component in &mut self.components { - component.handle_action(action.clone()); - } - Ok(()) - } - } - } - - fn handle_event(&mut self, event: AppEvent) -> Result<()> { - let mut actions: Vec<AppAction> = vec![]; - for component in &mut self.components { - if let Some(action) = component.handle_event(event.clone()) { - actions.push(action); - } - } - - for action in actions { - self.handle_action(action)?; - } - Ok(()) - } -} diff --git a/src/app_action.rs b/src/app_action.rs deleted file mode 100644 index 7825724..0000000 --- a/src/app_action.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::fmt; - -#[derive(Default, Clone, Debug)] -pub enum AppAction { - StatusBarSetMessage(String), - StatusBarSetError(String), - OpenUrl, - - ScrollUp, - ScrollDown, - ScrollTop, - ScrollBottom, - - ShowHelpMenu, - - #[default] - Quit, -} - -impl fmt::Display for AppAction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} diff --git a/src/app_event.rs b/src/app_event.rs deleted file mode 100644 index 94a8d0d..0000000 --- a/src/app_event.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crossterm::event::{KeyEvent, MouseEvent}; -use url::Url; - -#[derive(Clone)] -pub enum AppEvent { - Key(KeyEvent), - Mouse(MouseEvent), - - OpenUrl(Url), -} diff --git a/src/component.rs b/src/component.rs deleted file mode 100644 index 3e8fd8d..0000000 --- a/src/component.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crossterm::event::{KeyEvent, MouseEvent}; -use eyre::Result; -use ratatui::prelude::{Frame, Rect}; - -use crate::app_action::AppAction; -use crate::app_event::AppEvent; - -pub trait Component { - fn init(&mut self) -> Result<()> { - Ok(()) - } - - #[allow(unused)] - fn handle_action(&mut self, action: AppAction) {} - - #[allow(unused)] - fn handle_event(&mut self, event: AppEvent) -> Option<AppAction> { - match event { - AppEvent::Key(key_event) => self.handle_key_event(key_event), - AppEvent::Mouse(mouse_event) => { - self.handle_mouse_event(mouse_event) - } - _ => None, - } - } - - #[allow(unused)] - fn handle_key_event(&mut self, key: KeyEvent) -> Option<AppAction> { - None - } - - #[allow(unused)] - fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Option<AppAction> { - None - } - - #[allow(unused)] - fn update(&mut self) -> Option<AppEvent> { - None - } - - fn render(&mut self, frame: &mut Frame, rect: Rect) -> Result<()>; -} diff --git a/src/components/global_keys.rs b/src/components/global_keys.rs deleted file mode 100644 index dd903b1..0000000 --- a/src/components/global_keys.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crossterm::event::{KeyEvent, KeyEventKind}; -use ratatui::prelude::{ - Alignment, Color, Constraint, Direction, Frame, Layout, Line, Margin, Rect, - Span, Style, Stylize, -}; -use ratatui::widgets::block::{Block, BorderType, Title}; -use ratatui::widgets::{ - Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, - Wrap, -}; - -use crate::app_action::AppAction; -use crate::component::Component; -use crate::keys::key_commands::{serialize_key_event, KeyCommand}; - -#[derive(Default)] -pub struct GlobalKeys { - pub key_commands: Vec<KeyCommand>, - - pub should_show: bool, - pub scroll: usize, - pub scroll_state: ScrollbarState, -} - -impl Component for GlobalKeys { - fn init(&mut self) -> eyre::Result<()> { - self.scroll_state = - ScrollbarState::new(self.key_commands.len()).position(self.scroll); - - Ok(()) - } - - fn handle_action(&mut self, action: AppAction) { - match action { - AppAction::ScrollUp => { - if self.scroll > 0 { - self.scroll -= 1; - } - } - AppAction::ScrollDown => { - if self.scroll < self.key_commands.len() - 1 { - self.scroll += 1; - } - } - AppAction::ScrollTop => { - self.scroll = 0; - } - AppAction::ScrollBottom => { - self.scroll = self.key_commands.len() - 1; - } - AppAction::ShowHelpMenu => { - self.should_show = !self.should_show; - self.scroll = 0; - } - - _ => {} - } - self.scroll_state = self.scroll_state.position(self.scroll); - } - - fn handle_key_event(&mut self, key: KeyEvent) -> Option<AppAction> { - if key.kind == KeyEventKind::Press { - let key_event = serialize_key_event(key); - for key_command in &mut self.key_commands { - if key_command.key_code == key_event { - return Some(key_command.action.clone()); - } - } - } - - None - } - - fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> { - let vertical_center = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Percentage(50 / 2), - Constraint::Percentage(50), - Constraint::Percentage(50 / 2), - ]) - .split(rect); - let center = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Percentage(50 / 2), - Constraint::Percentage(50), - Constraint::Percentage(50 / 2), - ]) - .split(vertical_center[1])[1]; - - let block = Block::default() - .title( - Title::from("Keyboard shortcuts").alignment(Alignment::Center), - ) - .borders(Borders::ALL) - .border_type(BorderType::Thick) - .style(Style::default().bg(Color::DarkGray)); - - let mut lines: Vec<Line> = vec![]; - for key_command in &mut self.key_commands { - let command = Span::from(key_command.key_code.clone()); - let description = - Span::from(key_command.description.clone()).italic(); - let spacer = Span::from(" "); - - let line = Line::from(vec![command, spacer, description]); - lines.push(line); - } - - let commands = Paragraph::new(lines) - .block(block) - .wrap(Wrap { trim: true }) - .scroll((u16::try_from(self.scroll)?, 0)); - - if self.should_show { - frame.render_widget(Clear, center); - frame.render_widget(commands, center); - frame.render_stateful_widget( - Scrollbar::new(ScrollbarOrientation::VerticalRight), - center.inner(&Margin { - vertical: 1, - horizontal: 0, - }), - &mut self.scroll_state, - ); - } - - Ok(()) - } -} diff --git a/src/components/mod.rs b/src/components/mod.rs deleted file mode 100644 index 07d34ab..0000000 --- a/src/components/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod global_keys; -pub mod status; -pub mod url_manager; diff --git a/src/components/status.rs b/src/components/status.rs deleted file mode 100644 index aa2b384..0000000 --- a/src/components/status.rs +++ /dev/null @@ -1,86 +0,0 @@ -use ratatui::prelude::*; -use ratatui::widgets::*; - -use crate::app_action::AppAction; -use crate::app_event::AppEvent; -use crate::component::Component; -use crate::keys::key_commands::serialize_key_event; - -#[derive(Default, Clone)] -pub struct StatusBar { - pub message: String, - pub current_key: String, - pub error: bool, - pub url_to_open: Option<url::Url>, -} - -impl Component for StatusBar { - fn handle_key_event( - &mut self, - key: crossterm::event::KeyEvent, - ) -> Option<AppAction> { - let key_str = serialize_key_event(key); - self.current_key = key_str; - - None - } - - fn handle_action(&mut self, action: crate::app_action::AppAction) { - match action.clone() { - AppAction::StatusBarSetMessage(message) => { - self.error = false; - self.message = message; - } - AppAction::StatusBarSetError(message) => { - self.error = true; - self.message = message; - } - AppAction::OpenUrl => { - self.url_to_open = - Some(url::Url::parse("molerat://example.com").unwrap()); - } - _ => {} - } - } - - fn update(&mut self) -> Option<AppEvent> { - if let Some(url) = &self.url_to_open { - let event = AppEvent::OpenUrl(url.clone()); - self.url_to_open = None; - return Some(event); - } - - None - } - - fn render( - &mut self, - frame: &mut ratatui::prelude::Frame, - rect: ratatui::prelude::Rect, - ) -> eyre::Result<()> { - let block = - Block::default().style(Style::default().bg(if self.error { - Color::Red - } else { - Color::DarkGray - })); - - let layout = Layout::default() - .direction(Direction::Horizontal) - .constraints(vec![ - Constraint::Percentage(50), - Constraint::Percentage(50), - ]) - .split(rect); - - let message = Paragraph::new(self.message.clone()).block(block.clone()); - let current_key = Paragraph::new(self.current_key.clone()) - .block(block) - .alignment(Alignment::Right); - - frame.render_widget(message, layout[0]); - frame.render_widget(current_key, layout[1]); - - Ok(()) - } -} diff --git a/src/components/url_manager.rs b/src/components/url_manager.rs deleted file mode 100644 index 6067923..0000000 --- a/src/components/url_manager.rs +++ /dev/null @@ -1,34 +0,0 @@ -use url::Url; - -use crate::app_action::AppAction; -use crate::app_event::AppEvent; -use crate::component::Component; - -#[derive(Default)] -pub struct UrlManager { - url: Option<Url>, -} - -impl Component for UrlManager { - fn handle_event(&mut self, event: AppEvent) -> Option<AppAction> { - match event { - AppEvent::OpenUrl(url) => { - self.url = Some(url.clone()); - return Some(AppAction::StatusBarSetMessage(format!( - "Opening {}", - url.as_str() - ))); - } - _ => {} - } - None - } - - fn render( - &mut self, - _frame: &mut ratatui::prelude::Frame, - _rect: ratatui::prelude::Rect, - ) -> eyre::Result<()> { - Ok(()) - } -} diff --git a/src/config.c b/src/config.c new file mode 100755 index 0000000..19a6bfa --- /dev/null +++ b/src/config.c @@ -0,0 +1,18 @@ +#include "config.h" +#include "connect.h" +#include "response.h" + +void init_config(struct config *conf) { + conf->s.url = NULL; + conf->s.conn = NULL; + conf->s.res = NULL; +} + +void conf_cleanup(struct config *conf) { + if (conf->s.url != NULL) + free_url(conf->s.url); + if (conf->s.conn != NULL) + tls_cleanup(conf->s.conn); + if (conf->s.res != NULL) + free_response(conf->s.res); +} diff --git a/src/keys/key_commands.rs b/src/keys/key_commands.rs deleted file mode 100644 index fc06286..0000000 --- a/src/keys/key_commands.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - -use crate::app_action::AppAction; - -#[derive(Default, Clone)] -pub struct KeyCommand { - pub key_code: String, - pub description: String, - pub action: AppAction, -} - -impl std::fmt::Display for KeyCommand { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}\t{}", self.key_code, self.description) - } -} - -pub fn serialize_key_event(event: KeyEvent) -> String { - let mut modifiers = Vec::with_capacity(3); - if event.modifiers.intersects(KeyModifiers::CONTROL) { - modifiers.push("ctrl"); - } - if event.modifiers.intersects(KeyModifiers::SUPER) - || event.modifiers.intersects(KeyModifiers::HYPER) - || event.modifiers.intersects(KeyModifiers::META) - { - modifiers.push("super"); - } - if event.modifiers.intersects(KeyModifiers::ALT) { - modifiers.push("alt"); - } - - let char; - let key = match event.code { - KeyCode::Backspace | KeyCode::Delete => "del", - KeyCode::Enter => "enter", - KeyCode::Left => "left", - KeyCode::Right => "right", - KeyCode::Up => "up", - KeyCode::Down => "down", - KeyCode::Tab => "tab", - KeyCode::Char(' ') => "space", - KeyCode::Char(c) => { - char = c.to_string(); - &char - } - KeyCode::Esc => "esc", - _ => "", - }; - let separator = if modifiers.is_empty() { "" } else { "-" }; - let serialized_event = - format!("{}{}{}", modifiers.join("-"), separator, key); - - serialized_event -} diff --git a/src/keys/mod.rs b/src/keys/mod.rs deleted file mode 100644 index c884843..0000000 --- a/src/keys/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod key_commands; diff --git a/src/main.c b/src/main.c new file mode 100755 index 0000000..26090ff --- /dev/null +++ b/src/main.c @@ -0,0 +1,83 @@ +#include <ncurses.h> +#include <openssl/ssl.h> +#include <stdlib.h> + +#include "color.h" +#include "config.h" +#include "connect.h" +#include "net.h" +#include "request.h" +#include "response.h" +#include "status.h" +#include "url.h" +#include "util.h" + +void exit_cleanup(void) { endwin(); } + +void init_ncurses(void) { + initscr(); + cbreak(); + noecho(); + + curs_set(0); + keypad(stdscr, TRUE); + scrollok(stdscr, TRUE); + + if (!has_colors()) + die("Terminal does not support colors"); + + start_color(); + set_colors(); + + refresh(); +} + +int main(void) { + struct config conf; + init_config(&conf); + + init_ncurses(); + atexit(exit_cleanup); + + getmaxyx(stdscr, conf.i.height, conf.i.width); + + init_status(&conf); + conf.s.url = init_url(); + conf.s.conn = init_connection(); + + prompt_status_url(&conf); + update_status(&conf, conf.s.url_string); + + int rc = parse_url(conf.s.url, conf.s.url_string); + if (rc < 0) { + error_status(&conf, "Invalid URL"); + conf_cleanup(&conf); + return 1; + } + + rc = tls_connect(&conf, *conf.s.url); + if (rc < 0) { + conf_cleanup(&conf); + return 1; + } + + struct request req; + req.kind = GET; + req.url = *conf.s.url; + send_request(&conf, &req); + + struct response res; + rc = read_response(&conf, &res); + if (rc < 0) { + conf_cleanup(&conf); + return rc * -1; + } + conf.s.res = &res; + + printw("%s\n", conf.s.res->content); + + getch(); + + conf_cleanup(&conf); + return 0; +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 3b8bb70..0000000 --- a/src/main.rs +++ /dev/null @@ -1,59 +0,0 @@ -mod app; -mod app_action; -mod app_event; -mod component; -mod components; -mod keys; -mod tui; - -use eyre::Result; - -use app_action::AppAction; -use keys::key_commands::KeyCommand; - -fn main() -> Result<()> { - tui::install_hooks()?; - let mut app = app::App::new(std::time::Duration::from_millis(10))?; - let mut key_commands = vec![ - // Status bar - KeyCommand { - key_code: "o".to_string(), - description: "Open new link".to_string(), - action: AppAction::OpenUrl, - }, - // Navigation - KeyCommand { - key_code: "g".to_string(), - description: "Scroll to top".to_string(), - action: AppAction::ScrollTop, - }, - KeyCommand { - key_code: "G".to_string(), - description: "Scroll to bottom".to_string(), - action: AppAction::ScrollBottom, - }, - KeyCommand { - key_code: "k".to_string(), - description: "Scroll up one line".to_string(), - action: AppAction::ScrollUp, - }, - KeyCommand { - key_code: "j".to_string(), - description: "Scroll down one line".to_string(), - action: AppAction::ScrollDown, - }, - KeyCommand { - key_code: "q".to_string(), - description: "Quit molehole".to_string(), - action: AppAction::Quit, - }, - KeyCommand { - key_code: "?".to_string(), - description: "Toggle help menu".to_string(), - action: AppAction::ShowHelpMenu, - }, - ]; - app.key_commands.append(&mut key_commands); - - app.run() -} diff --git a/src/molerat/connect.c b/src/molerat/connect.c new file mode 100755 index 0000000..96c904b --- /dev/null +++ b/src/molerat/connect.c @@ -0,0 +1,133 @@ +#include <errno.h> +#include <netdb.h> +#include <openssl/ssl.h> +#include <stdbool.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "connect.h" +#include "status.h" +#include "url.h" + +int connect_socket(struct config *conf, struct url url) { + struct addrinfo hints; + struct addrinfo *servinfo; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + char port[5]; + sprintf(port, "%d", url.port); + int rc = getaddrinfo(url.host, port, &hints, &servinfo); + if (rc != 0) { + error_status(conf, (char *)gai_strerror(rc)); + return ERR_GETADDRINFO; + } + + struct addrinfo *p; + int sockfd; + for (p = servinfo; p != NULL; p = p->ai_next) { + sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sockfd == -1) { + continue; + } + + rc = connect(sockfd, p->ai_addr, p->ai_addrlen); + if (rc == -1) { + close(sockfd); + continue; + } + + break; + } + + if (p == NULL) { + error_status(conf, strerror(errno)); + return ERR_CONNECT; + } + update_status(conf, "Connected to socket"); + freeaddrinfo(servinfo); + + return sockfd; +} + +int tls_connect(struct config *conf, struct url url) { + char status_msg[strlen(url.host) + 128]; + sprintf(status_msg, "Connecting to %s...", url.host); + update_status(conf, status_msg); + + struct connection *conn = init_connection(); + conf->s.conn = conn; + + int sock; + int rc = connect_socket(conf, url); + if (rc < 0) { + return rc; + } else { + sock = rc; + } + + const SSL_METHOD *method; + method = TLS_method(); + + SSL_CTX *ctx; + ctx = SSL_CTX_new(method); + if (ctx == NULL) { + error_status(conf, "Failed to init SSL"); + return ERR_SSL_CTX; + } + + SSL *ssl; + ssl = SSL_new(ctx); + if (ssl == NULL) { + error_status(conf, "Failed to init SSL"); + return ERR_SSL_SSL; + } + + rc = SSL_set_fd(ssl, sock); + if (rc == 0) { + error_status(conf, "Failed to wrap socket"); + return ERR_SSL_SSL; + } + + rc = SSL_connect(ssl); + if (rc <= 0) { + error_status(conf, "Failed to connect SSL"); + return ERR_SSL_SSL; + } + + conn->ssl = ssl; + conn->sockfd = sock; + conn->used = true; + conf->s.conn = conn; + + update_status(conf, "Connected"); + + return 0; +} + +void tls_cleanup(struct connection *conn) { + if (conn->sockfd) { + shutdown(conn->sockfd, SHUT_RDWR); + close(conn->sockfd); + } + + if (conn->ssl) { + SSL_shutdown(conn->ssl); + SSL_CTX_free(SSL_get_SSL_CTX(conn->ssl)); + SSL_free(conn->ssl); + } + + free(conn); +} + +struct connection *init_connection(void) { + struct connection *conn = malloc(sizeof(struct connection)); + memset(conn, 0, sizeof(struct connection)); + + return conn; +} diff --git a/src/molerat/net.c b/src/molerat/net.c new file mode 100755 index 0000000..f3eef06 --- /dev/null +++ b/src/molerat/net.c @@ -0,0 +1,68 @@ +#include <openssl/ssl.h> +#include <string.h> +#include <time.h> + +#include "config.h" +#include "connect.h" +#include "net.h" +#include "request.h" +#include "response.h" +#include "status.h" + +int send_request(struct config *conf, struct request *req) { + char *req_string = request_to_string(req); + + int rc = SSL_write(conf->s.conn->ssl, req_string, strlen(req_string)); + + if (rc < 0) { + return SSL_SEND_ERROR; + } + + free(req_string); + return 0; +} + +int read_response(struct config *conf, struct response *res) { + struct timespec start_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + int buf_len = 4096; + char *buf = malloc(buf_len); + + int bytes_read = 0; + + do { + bytes_read = SSL_read(conf->s.conn->ssl, buf, buf_len); + + if (bytes_read == buf_len) { + buf_len *= 2; + char *temp_buf = realloc(buf, buf_len); + if (temp_buf == NULL) + return ALLOC_ERROR; + + buf = temp_buf; + } + } while (bytes_read > 0); + + int rc = parse_response(res, buf); + if (rc < 0) { + return RESPONSE_PARSE_ERROR; + } + free(buf); + + struct timespec end_time; + clock_gettime(CLOCK_MONOTONIC, &end_time); + + float time_diff = (end_time.tv_sec - start_time.tv_sec) + + 1e-9 * (end_time.tv_nsec - start_time.tv_nsec); + + int msg_len = + snprintf(NULL, 0, "Received after %0.3f seconds", time_diff) + 1; + char *msg = malloc(msg_len); + snprintf(msg, msg_len, "Received after %.3f seconds", time_diff); + + update_status(conf, msg); + free(msg); + + return 0; +} diff --git a/src/molerat/request.c b/src/molerat/request.c new file mode 100755 index 0000000..1d59a09 --- /dev/null +++ b/src/molerat/request.c @@ -0,0 +1,34 @@ +#include <ncurses.h> +#include <stdlib.h> +#include <string.h> + +#include "request.h" +#include "url.h" + +char *get_request_kind(enum RequestKind kind) { + switch (kind) { + case GET: + return "get"; + case PUT: + return "put"; + case DEL: + return "del"; + } +} + +char *request_to_string(struct request *req) { + int len = sizeof(struct request) + + 6; // +1 for null terminator +5 for request whitespace + + char *buf = malloc(len); + + char *kind = get_request_kind(req->kind); + char *host = req->url.host != NULL ? req->url.host : ""; + char *path = req->url.path != NULL ? req->url.path : ""; + char *query = req->url.query != NULL ? req->url.query : ""; + char *fragment = req->url.fragment != NULL ? req->url.fragment : ""; + + snprintf(buf, len, "%s %s%s%s%s\r\n\r\n", kind, host, path, query, fragment); + + return buf; +} diff --git a/src/molerat/response.c b/src/molerat/response.c new file mode 100755 index 0000000..0fffe21 --- /dev/null +++ b/src/molerat/response.c @@ -0,0 +1,215 @@ +#include <stdlib.h> +#include <string.h> + +#include "response.h" + +#define SET_STR(segment) \ + (segment) = malloc(i - start + 1); \ + strncpy((segment), s + start, i - start); + +#define MOVE(amount) \ + i += (amount); \ + start = i; + +#define CHECK_AT_END() \ + if (cur == '\0') { \ + state = END; \ + } + +#define AT_DELIM (cur == '\t' && next == '\r' && next_next == '\n') + +enum state { + STATUS, + + MESSAGE, + MESSAGE_S, + + TYPE_S, + TYPE, + + LENGTH_S, + LENGTH, + + HASH, + HASH_S, + + CONTENT, + END +}; + +int parse_response(struct response *res, char *s) { + int i = 0; + int start = 0; + char cur; + char next; + char next_next; + + int s_len = strlen(s); + + enum state state = STATUS; + + while (state != END) { + cur = s[i]; + if (i < s_len) + next = s[i + 1]; + if (i + 1 < s_len) + next_next = s[i + 2]; + + switch (state) { + case STATUS: + if (cur == '\r' && next == '\n') { + char *status_str; + SET_STR(status_str); + int status = atoi(status_str); + if (status > 0) + res->status = status; + else + return INVALID_STATUS; + free(status_str); + + MOVE(2); + state = MESSAGE; + } + + break; + case MESSAGE: + if (cur == ':') { + char *message_str; + SET_STR(message_str); + if (strncmp(message_str, "message", 7) != 0) { + MOVE(strlen(message_str) * -1); + state = TYPE; + break; + } else { + MOVE(1); // skip ':' + state = MESSAGE_S; + } + + free(message_str); + } + break; + case MESSAGE_S: + if (AT_DELIM) { + char *message_str; + SET_STR(message_str); + res->message = message_str; + + MOVE(3); + state = TYPE; + } + + break; + case TYPE: + if (cur == ':') { + char *type_str; + SET_STR(type_str); + if (strncmp(type_str, "type", 4) != 0) { + MOVE(strlen(type_str) * -1); + state = LENGTH; + } else { + MOVE(1); // skip ':' + state = TYPE_S; + } + + free(type_str); + } + break; + case TYPE_S: + if (cur == '/') { + SET_STR(res->type.type); + MOVE(1); + } else if (AT_DELIM) { + SET_STR(res->type.subtype); + + MOVE(3); + state = LENGTH; + } + break; + + case LENGTH: + if (cur == ':') { + char *length_str; + SET_STR(length_str); + if (strncmp(length_str, "length", 6) != 0) { + MOVE(strlen(length_str) * -1); + state = HASH; + } else { + MOVE(1); // skip ':' + state = LENGTH_S; + } + free(length_str); + } + + case LENGTH_S: + if (AT_DELIM) { + char *length_str; + SET_STR(length_str); + res->length = atoi(length_str); + free(length_str); + + MOVE(3); + state = HASH; + } + break; + + case HASH: + if (cur == ':') { + char *hash_str; + SET_STR(hash_str); + if (strncmp(hash_str, "hash", 4) != 0) { + MOVE(strlen(hash_str) * -1); + free(hash_str); + return UNKNOWN_KEY; + } else { + MOVE(1); // skip ':' + state = HASH_S; + } + free(hash_str); + } + break; + case HASH_S: + if ((cur == '\r' && next == '\n') || AT_DELIM) { + char *hash_str; + SET_STR(hash_str); + res->hash = hash_str; + + MOVE(4); + state = CONTENT; + } + + case CONTENT: + if (res->length < 1) { + state = END; + break; + } + + if (i == s_len - 1) { + char *content_str; + SET_STR(content_str); + res->content = content_str; + + state = END; + } + + case END: + break; + } + + i++; + if (i == s_len) { + state = END; + } + } + + return 0; +} + +void free_response(struct response *res) { + free(res->message); + + free(res->type.type); + free(res->type.subtype); + + free(res->hash); + free(res->content); +} diff --git a/src/molerat/url.c b/src/molerat/url.c new file mode 100755 index 0000000..0fd99ea --- /dev/null +++ b/src/molerat/url.c @@ -0,0 +1,181 @@ +#include <stdlib.h> +#include <string.h> + +#include "url.h" + +#define SET_STR(segment) \ + (segment) = malloc(i - start + 1); \ + strncpy((segment), s + start, i - start); + +#define MOVE(amount) \ + i += (amount); \ + start = i; + +#define SET_AT_END(segment) \ + if (cur == '\0') { \ + state = END; \ + if (i - 1 > start) { \ + SET_STR(segment); \ + } \ + } + +enum state { SCHEME, HOST, PORT, PATH, QUERY, FRAGMENT, END }; + +int parse_url(struct url *url, char *s) { + int i = 0; // index into *s + int start = 0; // index of current mode start + enum state state = SCHEME; + char cur; + + while (i < MAX_URL_LENGTH) { + cur = s[i]; + + if (cur == ' ') + return INVALID_CHARACTER; + + switch (state) { + case SCHEME: + if (cur == ':') { + state = HOST; + SET_STR(url->scheme); + + MOVE(3); // skip the '://' + } + SET_AT_END(url->scheme); + break; + + case HOST: + if (cur == ':') { + state = PORT; + SET_STR(url->host); + + MOVE(1); + } + + if (cur == '/') { + state = PATH; + SET_STR(url->host); + + MOVE(0); + } + SET_AT_END(url->host); + break; + + case PORT: + if (cur == '/') { + state = PATH; + char *port; + SET_STR(port); + + url->port = atoi(port); + + MOVE(0); + } + if (cur == '\0') { + state = END; + + char *port; + SET_STR(port); + + url->port = atoi(port); + } + + break; + + case PATH: + if (cur == '?') { + state = QUERY; + SET_STR(url->path); + + MOVE(0); + } + + if (cur == '#') { + state = FRAGMENT; + SET_STR(url->path); + + MOVE(0); + } + SET_AT_END(url->path); + break; + + case QUERY: + if (cur == '#') { + state = FRAGMENT; + SET_STR(url->query); + + MOVE(0); + } + SET_AT_END(url->query); + break; + + case FRAGMENT: + if (cur == '\0') { + state = END; + SET_STR(url->fragment); + + MOVE(0); + } + break; + + case END: + break; + } + + i++; + + if (cur == '\0') + break; + if (state == END) + break; + } + + if (url->host == NULL) + return MISSING_HOST; + if (url->port == 0) + return MISSING_PORT; + + if (url->path == NULL) { + url->path = malloc(2); + strcpy(url->path, "/"); + } + + return 0; +} + +struct url *init_url(void) { + struct url *url = malloc(sizeof(struct url)); + + url->scheme = NULL; + url->host = NULL; + url->port = 2693; + + url->path = NULL; + + url->query = NULL; + url->fragment = NULL; + + return url; +} + +void free_url(struct url *url) { + free(url->scheme); + free(url->host); + free(url->path); + free(url->fragment); + free(url->query); + free(url); +} + +int len_url(struct url *url) { + int len = 0; + + len += url->scheme != NULL ? strlen(url->scheme) : 0; + len += url->host != NULL ? strlen(url->host) : 0; + len += sizeof(url->port); + len += url->path != NULL ? strlen(url->path) : 0; + len += url->query != NULL ? strlen(url->query) : 0; + len += url->fragment != NULL ? strlen(url->fragment) : 0; + + return len; +} diff --git a/src/tui.rs b/src/tui.rs deleted file mode 100644 index 911a50d..0000000 --- a/src/tui.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crossterm::event::{ - Event, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, - PushKeyboardEnhancementFlags, -}; -use crossterm::terminal::{ - disable_raw_mode, enable_raw_mode, EnterAlternateScreen, - LeaveAlternateScreen, -}; -use crossterm::{event, execute}; -use ratatui::prelude::{CrosstermBackend, Terminal}; -use std::io; -use std::io::{stdout, Stdout}; -use std::panic; - -pub type Tui = Terminal<CrosstermBackend<Stdout>>; - -pub fn init() -> io::Result<Tui> { - execute!(stdout(), EnterAlternateScreen)?; - execute!( - stdout(), - PushKeyboardEnhancementFlags( - KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES, - ) - )?; - enable_raw_mode()?; - - Terminal::new(CrosstermBackend::new(stdout())) -} - -pub fn restore() -> io::Result<()> { - execute!(stdout(), LeaveAlternateScreen)?; - execute!(stdout(), PopKeyboardEnhancementFlags)?; - disable_raw_mode()?; - - Ok(()) -} - -pub fn get_event(tick: std::time::Duration) -> io::Result<Option<Event>> { - if event::poll(tick)? { - return Ok(Some(event::read()?)); - } - - Ok(None) -} - -pub fn install_hooks() -> eyre::Result<()> { - let hook_builder = color_eyre::config::HookBuilder::default(); - let (panic_hook, eyre_hook) = hook_builder.into_hooks(); - - let panic_hook = panic_hook.into_panic_hook(); - panic::set_hook(Box::new(move |panic_info| { - restore().unwrap(); - panic_hook(panic_info); - })); - - let eyre_hook = eyre_hook.into_eyre_hook(); - eyre::set_hook(Box::new(move |error| { - restore().unwrap(); - eyre_hook(error) - }))?; - Ok(()) -} diff --git a/src/ui/color.c b/src/ui/color.c new file mode 100755 index 0000000..e834193 --- /dev/null +++ b/src/ui/color.c @@ -0,0 +1,10 @@ +#include <ncurses.h> + +#include "color.h" + +void set_colors(void) { + /* fg color, bg color */ + init_pair(STATUS_MAIN, COLOR_DIM_WHITE, COLOR_GREY); + init_pair(STATUS_ERROR, COLOR_DIM_WHITE, COLOR_DIM_RED); + init_pair(STATUS_PROMPT, COLOR_GREY, COLOR_DIM_WHITE); +} diff --git a/src/ui/status.c b/src/ui/status.c new file mode 100755 index 0000000..703575d --- /dev/null +++ b/src/ui/status.c @@ -0,0 +1,54 @@ +#include <ncurses.h> +#include <string.h> + +#include "color.h" +#include "config.h" +#include "connect.h" +#include "status.h" +#include "url.h" + +void init_status(struct config *conf) { + WINDOW *status = newwin(1, conf->i.width, conf->i.height - 1, 0); + + conf->i.status_win = status; + update_status(conf, ""); +} + +void update_status(struct config *conf, char *s) { + werase(conf->i.status_win); + wbkgd(conf->i.status_win, COLOR_PAIR(STATUS_MAIN)); + wprintw(conf->i.status_win, "%s", s); + wrefresh(conf->i.status_win); +} + +void prompt_status_url(struct config *conf) { + echo(); + curs_set(1); + + werase(conf->i.status_win); + + char prompt[] = "Enter a molerat URL: molerat://"; + werase(conf->i.status_win); + wbkgd(conf->i.status_win, COLOR_PAIR(STATUS_PROMPT)); + wprintw(conf->i.status_win, "%s", prompt); + wrefresh(conf->i.status_win); + + char url_string[MAX_URL_LENGTH]; + wgetstr(conf->i.status_win, url_string); + + char url_string_with_scheme[MAX_URL_LENGTH] = "molerat://"; + strlcat(url_string_with_scheme, url_string, MAX_URL_LENGTH); + + conf->s.url_string = url_string_with_scheme; + + curs_set(0); + noecho(); +} + +void error_status(struct config *conf, char *s) { + werase(conf->i.status_win); + wbkgd(conf->i.status_win, COLOR_PAIR(STATUS_ERROR)); + wprintw(conf->i.status_win, "Error: %s [Press RETURN]", s); + wrefresh(conf->i.status_win); + getch(); +} diff --git a/src/util.c b/src/util.c new file mode 100755 index 0000000..2015a08 --- /dev/null +++ b/src/util.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <ncurses.h> + +noreturn void die(const char *s) { + endwin(); + + perror(s); + exit(EXIT_FAILURE); +} |