From a0a84ecc3579b09f4b8040832332bd8807583bfc Mon Sep 17 00:00:00 2001 From: 1okey Date: Fri, 28 Nov 2025 22:01:24 +0200 Subject: [PATCH 1/7] add meson build file, install ncurses --- .gitignore | 11 ++--------- meson.build | 5 +++++ old/.gitignore | 9 +++++++++ README.md => old/README.md | 0 requirements.txt => old/requirements.txt | 0 {test => old/test}/__init__.py | 0 {test => old/test}/test_browser.py | 0 {test => old/test}/test_caching.py | 0 {textbrowser => old/textbrowser}/__init__.py | 0 {textbrowser => old/textbrowser}/__main__.py | 0 {textbrowser => old/textbrowser}/argparser.py | 0 {textbrowser => old/textbrowser}/browser.py | 0 {textbrowser => old/textbrowser}/cache.py | 0 {textbrowser => old/textbrowser}/utils.py | 0 src/main.c | 18 ++++++++++++++++++ 15 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 meson.build create mode 100644 old/.gitignore rename README.md => old/README.md (100%) rename requirements.txt => old/requirements.txt (100%) rename {test => old/test}/__init__.py (100%) rename {test => old/test}/test_browser.py (100%) rename {test => old/test}/test_caching.py (100%) rename {textbrowser => old/textbrowser}/__init__.py (100%) rename {textbrowser => old/textbrowser}/__main__.py (100%) rename {textbrowser => old/textbrowser}/argparser.py (100%) rename {textbrowser => old/textbrowser}/browser.py (100%) rename {textbrowser => old/textbrowser}/cache.py (100%) rename {textbrowser => old/textbrowser}/utils.py (100%) create mode 100644 src/main.c diff --git a/.gitignore b/.gitignore index a2b9b60..ad37eb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,2 @@ -__pycache__ -pyvenv.cfg -.vscode -.cache -.pytest_cache - -# virtual environments for win and linux -wenv -lenv \ No newline at end of file +build +*.o \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..f5de517 --- /dev/null +++ b/meson.build @@ -0,0 +1,5 @@ +project('textbrowser', 'c') + +ncurses = dependency('ncurses', version: '>=5.7') + +executable('textbrowser', 'src/main.c', dependencies: [ncurses]) \ No newline at end of file diff --git a/old/.gitignore b/old/.gitignore new file mode 100644 index 0000000..a2b9b60 --- /dev/null +++ b/old/.gitignore @@ -0,0 +1,9 @@ +__pycache__ +pyvenv.cfg +.vscode +.cache +.pytest_cache + +# virtual environments for win and linux +wenv +lenv \ No newline at end of file diff --git a/README.md b/old/README.md similarity index 100% rename from README.md rename to old/README.md diff --git a/requirements.txt b/old/requirements.txt similarity index 100% rename from requirements.txt rename to old/requirements.txt diff --git a/test/__init__.py b/old/test/__init__.py similarity index 100% rename from test/__init__.py rename to old/test/__init__.py diff --git a/test/test_browser.py b/old/test/test_browser.py similarity index 100% rename from test/test_browser.py rename to old/test/test_browser.py diff --git a/test/test_caching.py b/old/test/test_caching.py similarity index 100% rename from test/test_caching.py rename to old/test/test_caching.py diff --git a/textbrowser/__init__.py b/old/textbrowser/__init__.py similarity index 100% rename from textbrowser/__init__.py rename to old/textbrowser/__init__.py diff --git a/textbrowser/__main__.py b/old/textbrowser/__main__.py similarity index 100% rename from textbrowser/__main__.py rename to old/textbrowser/__main__.py diff --git a/textbrowser/argparser.py b/old/textbrowser/argparser.py similarity index 100% rename from textbrowser/argparser.py rename to old/textbrowser/argparser.py diff --git a/textbrowser/browser.py b/old/textbrowser/browser.py similarity index 100% rename from textbrowser/browser.py rename to old/textbrowser/browser.py diff --git a/textbrowser/cache.py b/old/textbrowser/cache.py similarity index 100% rename from textbrowser/cache.py rename to old/textbrowser/cache.py diff --git a/textbrowser/utils.py b/old/textbrowser/utils.py similarity index 100% rename from textbrowser/utils.py rename to old/textbrowser/utils.py diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b0198ee --- /dev/null +++ b/src/main.c @@ -0,0 +1,18 @@ +#include +#include + +int main() { + WINDOW* wnd = initscr(); // Start ncurses mode + printw("> Where you want to go:\n"); + refresh(); + char buf[80]; + getstr(buf); + printw("you entered: %s\n Press ESC to escape", buf); + refresh(); + while (getch() != 27) { + printw("Press ESC to escape", buf); + refresh(); + } + endwin(); + return 0; +} \ No newline at end of file From 32465b3ecf6028f12ce01aff709dc2f48e5ea5bf Mon Sep 17 00:00:00 2001 From: 10key Date: Fri, 28 Nov 2025 22:36:59 +0200 Subject: [PATCH 2/7] add meson and ninja as reqs for python venv --- .gitignore | 1 + requirements.txt | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index ad37eb7..19bf526 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build +.venv *.o \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a2c9c7e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +meson==1.9.1 +ninja==1.13.0 From 8250590aa843bfcdc91f317272a869230167c5bc Mon Sep 17 00:00:00 2001 From: 10key Date: Sun, 30 Nov 2025 22:28:42 +0200 Subject: [PATCH 3/7] add curl into meson deps --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index f5de517..dcceec5 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,6 @@ project('textbrowser', 'c') ncurses = dependency('ncurses', version: '>=5.7') +curl = dependency('libcurl', version: '>=8.7') -executable('textbrowser', 'src/main.c', dependencies: [ncurses]) \ No newline at end of file +executable('textbrowser', 'src/main.c', dependencies: [ncurses, curl]) \ No newline at end of file From a2baee73023fd13b35fd45846a98d4376647ce3e Mon Sep 17 00:00:00 2001 From: 10key Date: Sun, 30 Nov 2025 22:28:54 +0200 Subject: [PATCH 4/7] add basic readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..8f2e933 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +### Prereqs +- ncurses +- curl + +### Setup +```python + python3 -m venv .venv + source .venv/bin/activate + pip3 install -r requirements.txt +``` \ No newline at end of file From 983e2670a1828a5b8a60d5ee8674e38aef5efa06 Mon Sep 17 00:00:00 2001 From: 10key Date: Sun, 30 Nov 2025 22:29:39 +0200 Subject: [PATCH 5/7] fix segfault, define basic funcs and types --- src/main.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/src/main.c b/src/main.c index b0198ee..0db0632 100644 --- a/src/main.c +++ b/src/main.c @@ -1,18 +1,90 @@ #include +#include #include +static char ** history[100][100]; +static int history_ptr = 0; +static int history_size = 100; + +typedef enum { Ok = 0, Invalid = 1, Unsafe = 2 } InputErrorCode; + +typedef struct { + char* input; + InputErrorCode error; +} InputResult; + +typedef struct { + char* content; + InputErrorCode error; +} RequestResult; + +void print_help() { + printw("Textbrowser provides a most simple way to peek at websites\n\ + :help - print this help\n\ + :exit - to exit browser\n\ + :history - to exit browser"); + refresh(); +} + +void print_history() { + for (int i = 0; i < history_ptr; i++) { + printw("%d: %s\n", i + 1, history[i]); + } + refresh(); +} + +RequestResult request(const char * url) { + return (RequestResult){ .content = NULL, .error = 0 }; +} + +InputResult validate_input(const char * input) { + return (InputResult){ .input = input, 0 }; +} + +InputResult sanitize_input(const char * input) { + return (InputResult){ .input = input, 0 }; +} + int main() { WINDOW* wnd = initscr(); // Start ncurses mode - printw("> Where you want to go:\n"); - refresh(); + char buf[80]; - getstr(buf); - printw("you entered: %s\n Press ESC to escape", buf); - refresh(); - while (getch() != 27) { - printw("Press ESC to escape", buf); + + do { refresh(); - } + printw("\n> Where you want to go: "); + refresh(); + getstr(buf); + + if (strcmp(buf, ":help") == 0) { + print_help(); + continue; + } + + if (strcmp(buf, ":history") == 0) { + print_history(); + continue; + } + + InputResult sanitized = sanitize_input(&buf); + if (sanitized.error) { + printw("Dangerous input provided\n"); + continue; + } + + InputResult validated = validate_input(sanitized.input); + if (validated.error) { + printw("Invalid input provided\n"); + continue; + } + + printw("you entered: %s\n", buf); + if (history_ptr < history_size) { + strcpy(history[history_ptr++], buf); + } + + } while (true); + endwin(); return 0; } \ No newline at end of file From 5c882227e6c9aaf0620196a144ae12fdc1b3883c Mon Sep 17 00:00:00 2001 From: 10key Date: Sun, 30 Nov 2025 23:09:08 +0200 Subject: [PATCH 6/7] feat: add request function to make curl request, is_command and handle_command to react on reserved words --- src/main.c | 108 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/src/main.c b/src/main.c index 0db0632..be2d97c 100644 --- a/src/main.c +++ b/src/main.c @@ -1,23 +1,33 @@ #include +#include #include #include +#include +// request history static char ** history[100][100]; static int history_ptr = 0; static int history_size = 100; -typedef enum { Ok = 0, Invalid = 1, Unsafe = 2 } InputErrorCode; +// response cache +// actual array of responses +// keys of pages that point to responses + +typedef enum { InputOk = 0, InputInvalid = 1, InputUnsafe = 2 } InputError; typedef struct { char* input; - InputErrorCode error; + InputError error; } InputResult; typedef struct { char* content; - InputErrorCode error; + uint32_t size; + InputError error; } RequestResult; +typedef enum { Noop = 0, Exit = 1, Help = 2, History = 3 } Command; + void print_help() { printw("Textbrowser provides a most simple way to peek at websites\n\ :help - print this help\n\ @@ -33,8 +43,71 @@ void print_history() { refresh(); } +Command is_command(const char * input) { + // trie is much more efficient here but for simplicity we use strcmp + if (strcmp(input, ":help") == 0) { + return Help; + } else if (strcmp(input, ":exit") == 0) { + return Exit; + } else if (strcmp(input, ":history") == 0) { + return History; + } + + // clear or clean + + return Noop; +} + +void handle_command(Command cmd) { + switch (cmd) { + case Help: + print_help(); + break; + case Exit: + printw("Exiting the application\n"); + exit(0); + break; + case History: + print_history(); + break; + default: + break; + } +} + + RequestResult request(const char * url) { - return (RequestResult){ .content = NULL, .error = 0 }; + CURL* conn = curl_easy_init(); + if (conn == NULL) { + return (RequestResult){ .content = NULL, .size = 0, .error = 1 }; + } + + curl_easy_setopt(conn, CURLOPT_URL, url); + CURLcode res = curl_easy_perform(conn); + + if (res != CURLE_OK) { + curl_easy_cleanup(conn); + return (RequestResult){ .content = NULL, .size = 0, .error = 1 }; + } + + char * buf = (char* ) malloc(4086 * sizeof(char)); + uint32_t actual_size = 0; + curl_easy_recv(conn, buf, sizeof(buf), &actual_size); + + curl_socket_t sockfd; + + /* Extract the socket from the curl handle - we need it for waiting. */ + res = curl_easy_getinfo(conn, CURLINFO_ACTIVESOCKET, &sockfd); + if (res != CURLE_OK) { + curl_easy_cleanup(conn); + return (RequestResult){ .content = NULL, .size = 0, .error = 1 }; + } + /* read data */ + res = curl_easy_recv(conn, buf, sizeof(buf), &actual_size); + + curl_easy_cleanup(conn); + + return (RequestResult){ .content = buf, .size = actual_size, .error = 0 }; } InputResult validate_input(const char * input) { @@ -51,34 +124,39 @@ int main() { char buf[80]; do { - refresh(); printw("\n> Where you want to go: "); refresh(); getstr(buf); - if (strcmp(buf, ":help") == 0) { - print_help(); - continue; - } - - if (strcmp(buf, ":history") == 0) { - print_history(); + Command cmd = is_command(buf); + if (cmd != Noop) { + handle_command(cmd); continue; } InputResult sanitized = sanitize_input(&buf); - if (sanitized.error) { + if (sanitized.error != InputOk) { printw("Dangerous input provided\n"); continue; } InputResult validated = validate_input(sanitized.input); - if (validated.error) { + if (validated.error != InputOk) { printw("Invalid input provided\n"); continue; } - printw("you entered: %s\n", buf); + // cache lookup first and then attempt to request + RequestResult response = request(validated.input); + if (response.error != 0) { + printw("Failed to fetch the page\n"); + continue; + } + + printw("%s\n", buf); + + // check if history is full + // have a deque structure for history if (history_ptr < history_size) { strcpy(history[history_ptr++], buf); } From e0e6c7120339c158df0cf4e90aa602ff5aab87c6 Mon Sep 17 00:00:00 2001 From: 10key Date: Sun, 30 Nov 2025 23:11:02 +0200 Subject: [PATCH 7/7] Attempt to setup C workflow --- .github/workflows/c-app.yml | 31 +++++++++++++++++++++++++++ .github/workflows/python-app.yml | 36 -------------------------------- meson.build | 2 +- 3 files changed, 32 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/c-app.yml delete mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/c-app.yml b/.github/workflows/c-app.yml new file mode 100644 index 0000000..41c71e0 --- /dev/null +++ b/.github/workflows/c-app.yml @@ -0,0 +1,31 @@ +name: C application + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + sudo apt-get install libncurses5-dev libncursesw5-dev libcurl4-openssl-dev + python -m pip install --upgrade pip + pip install meson ninja + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Setup meson build + run: | + meson setup build + - name: Compile with meson + run: | + meson compile -C build diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml deleted file mode 100644 index c7f5067..0000000 --- a/.github/workflows/python-app.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python application - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest diff --git a/meson.build b/meson.build index dcceec5..bfe9d4d 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('textbrowser', 'c') ncurses = dependency('ncurses', version: '>=5.7') -curl = dependency('libcurl', version: '>=8.7') +curl = dependency('libcurl', version: '>=8.5') executable('textbrowser', 'src/main.c', dependencies: [ncurses, curl]) \ No newline at end of file