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/.gitignore b/.gitignore index a2b9b60..19bf526 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ -__pycache__ -pyvenv.cfg -.vscode -.cache -.pytest_cache - -# virtual environments for win and linux -wenv -lenv \ No newline at end of file +build +.venv +*.o \ No newline at end of file diff --git a/README.md b/README.md index 189bc27..8f2e933 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,10 @@ -## Textbrowser +### Prereqs +- ncurses +- curl -### Setting up -```bash - python3 -m venv venv - - # linux/macoss - source ./venv/bin/activate - # windows - venv\Scripts\activate.bat - - pip3 install -r ./requirements.txt -``` -### Running -```bash - python3 -m textbrowser -``` -### Testing -```bash - pytest . -``` +### Setup +```python + python3 -m venv .venv + source .venv/bin/activate + pip3 install -r requirements.txt +``` \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..bfe9d4d --- /dev/null +++ b/meson.build @@ -0,0 +1,6 @@ +project('textbrowser', 'c') + +ncurses = dependency('ncurses', version: '>=5.7') +curl = dependency('libcurl', version: '>=8.5') + +executable('textbrowser', 'src/main.c', dependencies: [ncurses, curl]) \ 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/old/README.md b/old/README.md new file mode 100644 index 0000000..189bc27 --- /dev/null +++ b/old/README.md @@ -0,0 +1,21 @@ +## Textbrowser + +### Setting up +```bash + python3 -m venv venv + + # linux/macoss + source ./venv/bin/activate + # windows + venv\Scripts\activate.bat + + pip3 install -r ./requirements.txt +``` +### Running +```bash + python3 -m textbrowser +``` +### Testing +```bash + pytest . +``` diff --git a/old/requirements.txt b/old/requirements.txt new file mode 100644 index 0000000..839b7a1 --- /dev/null +++ b/old/requirements.txt @@ -0,0 +1,37 @@ +aiocontextvars==0.2.2 +appdirs==1.4.3 +astroid==2.4.1 +attrs==19.3.0 +beautifulsoup4==4.8.0 +certifi==2024.7.4 +chardet==3.0.4 +colorama==0.4.1 +contextvars==2.4 +distlib==0.3.0 +filelock==3.0.12 +idna==3.7 +immutables==0.12 +importlib-metadata==1.6.0 +importlib-resources==1.5.0 +isort==4.3.21 +lazy-object-proxy==1.4.3 +loguru==0.6.0 +mccabe==0.6.1 +more-itertools==8.2.0 +packaging==20.3 +pluggy==0.13.1 +py==1.10.0 +pyfakefs==4.2.1 +pylint==2.5.2 +pyparsing==2.4.7 +pytest==5.4.1 +requests==2.32.4 +six==1.14.0 +soupsieve==2.0 +toml==0.10.0 +typed-ast==1.4.1 +urllib3==2.5.0 +virtualenv==20.26.6 +wcwidth==0.1.9 +wrapt==1.12.1 +zipp==3.19.1 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/requirements.txt b/requirements.txt index 839b7a1..a2c9c7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,37 +1,2 @@ -aiocontextvars==0.2.2 -appdirs==1.4.3 -astroid==2.4.1 -attrs==19.3.0 -beautifulsoup4==4.8.0 -certifi==2024.7.4 -chardet==3.0.4 -colorama==0.4.1 -contextvars==2.4 -distlib==0.3.0 -filelock==3.0.12 -idna==3.7 -immutables==0.12 -importlib-metadata==1.6.0 -importlib-resources==1.5.0 -isort==4.3.21 -lazy-object-proxy==1.4.3 -loguru==0.6.0 -mccabe==0.6.1 -more-itertools==8.2.0 -packaging==20.3 -pluggy==0.13.1 -py==1.10.0 -pyfakefs==4.2.1 -pylint==2.5.2 -pyparsing==2.4.7 -pytest==5.4.1 -requests==2.32.4 -six==1.14.0 -soupsieve==2.0 -toml==0.10.0 -typed-ast==1.4.1 -urllib3==2.5.0 -virtualenv==20.26.6 -wcwidth==0.1.9 -wrapt==1.12.1 -zipp==3.19.1 +meson==1.9.1 +ninja==1.13.0 diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..be2d97c --- /dev/null +++ b/src/main.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include + +// request history +static char ** history[100][100]; +static int history_ptr = 0; +static int history_size = 100; + +// 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; + InputError error; +} InputResult; + +typedef struct { + char* content; + 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\ + :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(); +} + +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) { + 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) { + return (InputResult){ .input = input, 0 }; +} + +InputResult sanitize_input(const char * input) { + return (InputResult){ .input = input, 0 }; +} + +int main() { + WINDOW* wnd = initscr(); // Start ncurses mode + + char buf[80]; + + do { + printw("\n> Where you want to go: "); + refresh(); + getstr(buf); + + Command cmd = is_command(buf); + if (cmd != Noop) { + handle_command(cmd); + continue; + } + + InputResult sanitized = sanitize_input(&buf); + if (sanitized.error != InputOk) { + printw("Dangerous input provided\n"); + continue; + } + + InputResult validated = validate_input(sanitized.input); + if (validated.error != InputOk) { + printw("Invalid input provided\n"); + continue; + } + + // 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); + } + + } while (true); + + endwin(); + return 0; +} \ No newline at end of file