AmendHub

Download:

jcs

/

detritus

/

amendments

/

18

*: Switch to page model, cache them and add back/forward navigation


jcs made amendment 18 about 1 year ago
--- browser.c Mon Oct 28 13:54:36 2024 +++ browser.c Sun Nov 3 11:23:08 2024 @@ -19,6 +19,7 @@ #include <string.h> #include "detritus.h" +#include "browser.h" #include "focusable.h" #include "util.h" @@ -33,44 +34,56 @@ static Rect zerorect = { 0, 0, 0, 0 }; static Pattern fill_pattern; -extern struct request_handler gemini_handler; -extern struct request_handler gopher_handler; +extern struct uri_handler gemini_handler; +extern struct uri_handler gopher_handler; -struct request_handler * request_handlers[] = { +struct uri_handler * uri_handlers[] = { &gemini_handler, &gopher_handler, }; -void browser_setup_shadow(struct browser *browser); bool browser_close(struct focusable *focusable); void browser_idle(struct focusable *focusable, EventRecord *event); void browser_update_menu(struct browser *browser); +void browser_update_buttons(struct browser *browser); void browser_update(struct focusable *focusable, EventRecord *event); -void browser_use_shadow(struct browser *browser); -void browser_reveal_shadow(struct browser *browser); void browser_key_down(struct focusable *focusable, EventRecord *event); void browser_mouse_down(struct focusable *focusable, EventRecord *event); bool browser_handle_menu(struct focusable *focusable, short menu, short item); void browser_atexit(struct focusable *focusable); -void browser_go(struct browser *browser); -bool browser_load_url(struct browser *browser, char *uristr); +void browser_go(struct browser *browser, short dir); +page_handle browser_create_page(struct browser *browser, char *uristr); void browser_draw_status(struct browser *browser); -void browser_stop_request(struct browser *browser); -void browser_cleanup(struct browser *browser); +void browser_follow_redir(struct browser *browser); +void browser_stop_loading_page(struct browser *browser); +void browser_free_links(struct browser *browser); void browser_idle(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)focusable->cookie; + struct uri_handler *handler; + struct URI *redir; TEIdle(browser->uri_te); - if (browser->request && - !browser->handler->process_request(browser->request)) - /* processing failed/finished */ - browser_stop_request(browser); + if (browser->loading_page) { + HLock(browser->loading_page); + handler = (*(browser->loading_page))->handler; + + if (!handler->process(browser->loading_page)) { + if ((*(browser->loading_page))->redir_to) + browser_follow_redir(browser); + else { + browser_stop_loading_page(browser); + browser->redirs = 0; + } + } + + HUnlock(browser->loading_page); + } } struct browser * @@ -79,7 +92,8 @@ browser_init(void) char title[64]; struct browser *browser; struct focusable *focusable; - Rect bounds, te_bounds, padding; + Handle rgnsave; + Rect bounds, te_bounds, padding, r; long width, height; browser = xmalloczero(sizeof(struct browser)); @@ -104,11 +118,44 @@ browser_init(void) panic("Can't create window"); SetPort(browser->win); - /* uri TE */ + browser->header = NewRgn(); + OpenRgn(); + r.top = 0; + r.left = 0; + r.right = bounds.right - bounds.left; + r.bottom = 26; + FrameRect(&r); + + rgnsave = browser->win->rgnSave; + browser->win->rgnSave = NULL; + + /* back and forward */ bounds.top = PADDING; bounds.left = PADDING; - bounds.right = browser->win->portRect.right - PADDING - 50; + bounds.right = bounds.left + 20; bounds.bottom = bounds.top + 16; + browser->back = NewControl(browser->win, &bounds, "\p<", true, + 1, 1, 1, pushButProc, 0L); + HiliteControl(browser->back, 255); + + browser->win->rgnSave = rgnsave; + FrameRoundRect(&bounds, 10, 10); + browser->win->rgnSave = NULL; + + bounds.top = PADDING; + bounds.left = bounds.right - 1; + bounds.right = bounds.left + 20; + browser->fwd = NewControl(browser->win, &bounds, "\p>", true, + 1, 1, 1, pushButProc, 0L); + HiliteControl(browser->fwd, 255); + + browser->win->rgnSave = rgnsave; + FrameRoundRect(&bounds, 10, 10); + browser->win->rgnSave = NULL; + + /* uri TE */ + bounds.left = bounds.right + PADDING; + bounds.right = browser->win->portRect.right - PADDING - 60; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); TextFont(geneva); @@ -119,11 +166,20 @@ browser_init(void) TEAutoView(false, browser->uri_te); TEActivate(browser->uri_te); + browser->win->rgnSave = rgnsave; + FrameRect(&bounds); + browser->win->rgnSave = NULL; + bounds.left = bounds.right + PADDING; bounds.right = browser->win->portRect.right - PADDING; - browser->go_button = NewControl(browser->win, &bounds, "\pGo", true, + browser->go_stop = NewControl(browser->win, &bounds, "\pGo", true, 1, 1, 1, pushButProc, 0L); + HiliteControl(browser->go_stop, 255); + browser->win->rgnSave = rgnsave; + FrameRoundRect(&bounds, 10, 10); + CloseRgn(browser->header); + /* output TV */ bounds.left = 0; bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH + 1; @@ -166,135 +222,137 @@ browser_init(void) focusable->atexit = browser_atexit; focusable_add(focusable); - DrawControls(browser->win); - browser_setup_shadow(browser); - - browser_statusf(browser, "Hello, cyberpals!"); + snprintf(browser->status_text, sizeof(browser->status_text), + "Hello, cyberpals!"); + browser_update(focusable, NULL); return browser; } -void -browser_setup_shadow(struct browser *browser) +bool +browser_close(struct focusable *focusable) { - long width, height; + struct browser *browser = (struct browser *)focusable->cookie; + page_handle pageh, fpage, bpage, tpage; + struct page *page; - width = browser->win->portRect.right - browser->win->portRect.left; - height = browser->win->portRect.bottom - browser->win->portRect.top; + browser_stop_loading_page(browser); - if (browser->shadow) { - ClosePort(browser->shadow); - if (browser->shadow->portBits.baseAddr) - xfree(&browser->shadow->portBits.baseAddr); - xfree(&browser->shadow); + if (browser->current_page) { + pageh = browser->current_page; + HLock(pageh); + fpage = (*pageh)->fwd_page; + HUnlock(pageh); + while (fpage) { + HLock(fpage); + tpage = (*fpage)->fwd_page; + (*fpage)->handler->cleanup(fpage); + HLock(fpage); + xfree(&(*fpage)->uri); + DisposeHandle(fpage); + fpage = tpage; + } + + HLock(pageh); + bpage = (*pageh)->back_page; + HUnlock(pageh); + while (bpage) { + HLock(bpage); + tpage = (*bpage)->back_page; + (*bpage)->handler->cleanup(bpage); + HLock(bpage); + xfree(&(*bpage)->uri); + DisposeHandle(bpage); + bpage = tpage; + } + + HLock(pageh); + (*pageh)->handler->cleanup(pageh); + HLock(pageh); + xfree(&(*pageh)->uri); + DisposeHandle(pageh); } - GetPort(&browser->shadow_old_port); + browser_free_links(browser); + + TEDispose(browser->uri_te); + TVDispose(browser->output_tv); + DisposeWindow(browser->win); + xfree(&browser); + focusable->cookie = NULL; - browser->shadow = (GrafPtr)xmalloczero(sizeof(GrafPort)); - if (browser->shadow == NULL) - panic("Out of memory"); - OpenPort(browser->shadow); - browser->shadow->portBits.rowBytes = (((width - 1) / 16) + 1) * 2; - browser->shadow->portBits.baseAddr = - xmalloc((long)browser->shadow->portBits.rowBytes * height); - if (browser->shadow->portBits.baseAddr == NULL) - panic("Out of memory"); - browser->shadow->portBits.bounds.right = width; - browser->shadow->portBits.bounds.bottom = height; - browser->shadow->portBits.rowBytes = (((width - 1) / 16) + 1) * 2; - browser->shadow->portRect = browser->shadow->portBits.bounds; - - RectRgn(browser->shadow->clipRgn, &browser->shadow->portRect); - RectRgn(browser->shadow->visRgn, &browser->shadow->portRect); + scsi_cleanup(); - SetPort(browser->shadow_old_port); + return true; } void -browser_use_shadow(struct browser *browser) +browser_stop_loading_page(struct browser *browser) { - if (++browser->shadow_refcnt != 1) + page_handle pageh; + struct page *page; + + if (!browser->loading_page) return; + + HLock(browser->loading_page); + page = *(browser->loading_page); + page->handler->cleanup(browser->loading_page); + HLock(browser->loading_page); - if (browser->shadow_refcnt == 0) - Debugger(); - - /* TODO: fix controls not drawing on the canvas */ - return; + /* only dispose if uncommitted */ + if (browser->current_page != browser->loading_page) { + HLock(browser->loading_page); + xfree(&(*(browser->loading_page))->uri); + DisposeHandle(browser->loading_page); + } - GetPort(&browser->shadow_old_port); - SetPort(browser->shadow); + browser->loading_page = NULL; + browser_update_buttons(browser); } void -browser_reveal_shadow(struct browser *browser) +browser_follow_redir(struct browser *browser) { - if (browser->shadow_refcnt <= 0) - Debugger(); - - if (--browser->shadow_refcnt != 0) + page_handle pageh; + struct page *page; + struct URI *redir; + + if (!browser->loading_page) return; -/* TODO: fix controls not drawing on the canvas */ -return; + HLock(browser->loading_page); + redir = (*(browser->loading_page))->redir_to; + HUnlock(browser->loading_page); - SetPort(browser->shadow_old_port); - - CopyBits(&browser->shadow->portBits, - &browser->win->portBits, &browser->shadow->portRect, - &browser->win->portRect, srcCopy, NULL); - browser->shadow_old_port = NULL; - ValidRect(&browser->win->portRect); -} + browser_stop_loading_page(browser); -bool -browser_close(struct focusable *focusable) -{ - struct browser *browser = (struct browser *)focusable->cookie; + if (!redir) + return; - browser_stop_request(browser); - - TEDispose(browser->uri_te); - TVDispose(browser->output_tv); - DisposeWindow(browser->win); - - browser_cleanup(browser); - xfree(&browser); - focusable->cookie = NULL; - - scsi_cleanup(); + if (++browser->redirs == 5) + warn("Too many redirections, not following to %s", + redir->str); - return true; + browser_statusf(browser, "Following redirection to %s", redir->str); + browser_go_uri(browser, redir->str); + + xfree(&redir); } void -browser_stop_request(struct browser *browser) +browser_free_links(struct browser *browser) { - if (!browser->request) - return; - - browser->handler->free_request(browser->request); - browser->handler = NULL; - browser->request = NULL; -} - -void -browser_cleanup(struct browser *browser) -{ - size_t n; - struct browser_link *link, *nlink; + struct browser_link *link; - if (browser->first_link) { + while (browser->first_link) { link = browser->first_link; - while (link) { - nlink = link->next_link; - xfree(&link); - link = nlink; - } - browser->first_link = NULL; - browser->last_link = NULL; + browser->first_link = link->next_link; + xfree(&link); } + + browser->first_link = NULL; + browser->last_link = NULL; } void @@ -303,8 +361,8 @@ browser_atexit(struct focusable *focusable) struct browser *browser = (struct browser *)focusable->cookie; if (browser) { - browser_stop_request(browser); - browser_cleanup(browser); + browser_stop_loading_page(browser); + browser_free_links(browser); } scsi_cleanup(); @@ -320,24 +378,70 @@ browser_update_menu(struct browser *browser) TextFont(systemFont); TextSize(12); -#if 0 - te = *(browser->te); + HLock(browser->uri_te); + te = *(browser->uri_te); - DisableItem(edit_menu, EDIT_MENU_CUT_ID); - - if (te->selStart == te->selEnd) + if (te->selStart == te->selEnd) { + DisableItem(edit_menu, EDIT_MENU_CUT_ID); DisableItem(edit_menu, EDIT_MENU_COPY_ID); - else + } else { + EnableItem(edit_menu, EDIT_MENU_CUT_ID); EnableItem(edit_menu, EDIT_MENU_COPY_ID); + } + + EnableItem(edit_menu, EDIT_MENU_PASTE_ID); + EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); + + HUnlock(browser->uri_te); +} - DisableItem(edit_menu, EDIT_MENU_PASTE_ID); - - if (te->nLines == 0) { - DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); +void +browser_update_buttons(struct browser *browser) +{ + TERec *te; + bool is_go; + + if (browser->current_page) { + HLock(browser->current_page); + + if ((*(browser->current_page))->back_page) + HiliteControl(browser->back, 0); + else + HiliteControl(browser->back, 255); + + if ((*(browser->current_page))->fwd_page) + HiliteControl(browser->fwd, 0); + else + HiliteControl(browser->fwd, 255); + + HUnlock(browser->current_page); } else { - EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); + HiliteControl(browser->back, 255); + HiliteControl(browser->fwd, 255); } -#endif + + if (browser->loading_page) { + HiliteControl(browser->go_stop, 0); + HLock(browser->go_stop); + is_go = ((*(browser->go_stop))->contrlTitle[1] == 'G'); + HUnlock(browser->go_stop); + if (is_go) + SetCTitle(browser->go_stop, "\pStop"); + } else { + HLock(browser->uri_te); + te = *(browser->uri_te); + if (te->teLength) + HiliteControl(browser->go_stop, 0); + else + HiliteControl(browser->go_stop, 255); + HUnlock(browser->uri_te); + + HLock(browser->go_stop); + is_go = ((*(browser->go_stop))->contrlTitle[1] == 'G'); + HUnlock(browser->go_stop); + if (!is_go) + SetCTitle(browser->go_stop, "\pGo"); + } } void @@ -353,30 +457,28 @@ browser_update(struct focusable *focusable, EventRecor switch (what) { case -1: case updateEvt: - browser_use_shadow(browser); - - FillRect(&browser->win->portRect, fill_pattern); + FillRgn(browser->header, fill_pattern); + DrawControls(browser->win); + DrawGrowIconOnly(browser->win); + browser_draw_status(browser); + HLock(browser->uri_te); r = (*(browser->uri_te))->viewRect; - HUnlock(browser->uri_te); - FillRect(&r, white); - TEUpdate(&r, browser->uri_te); InsetRect(&r, -1, -1); FrameRect(&r); + TEUpdate(&r, browser->uri_te); + HUnlock(browser->uri_te); HLock(browser->output_tv); r = (*(browser->output_tv))->view; - HUnlock(browser->output_tv); - TVUpdate(browser->output_tv); InsetRect(&r, -1, -1); FrameRect(&r); + HUnlock(browser->output_tv); + TVUpdate(browser->output_tv); - DrawGrowIconOnly(browser->win); - DrawControls(browser->win); - browser_draw_status(browser); - - browser_reveal_shadow(browser); + ValidRect(&browser->win->portRect); + browser_update_menu(browser); break; } @@ -416,8 +518,7 @@ browser_mouse_down(struct focusable *focusable, EventR if (browser->first_link && (off = TVGetOffset(browser->output_tv, p)) >= 0) { - for (link = browser->first_link; link && link->next_link; - link = link->next_link) { + for (link = browser->first_link; link; link = link->next_link) { if ((link->pos <= off) && (off < link->pos + link->len)) { #if 0 if (event->modifiers & cmdKey) { @@ -425,7 +526,7 @@ browser_mouse_down(struct focusable *focusable, EventR break; } #endif - browser_load_url(browser, link->uri); + browser_create_page(browser, link->uri); break; } } @@ -437,8 +538,16 @@ browser_mouse_down(struct focusable *focusable, EventR switch (part = FindControl(p, browser->win, &control)) { case inButton: - if (TrackControl(control, p, 0L) && control == browser->go_button) - browser_go(browser); + if (TrackControl(control, p, 0L) && control == browser->go_stop) { + if (browser->loading_page) { + browser_stop_loading_page(browser); + browser_statusf(browser, "Stopped loading"); + } else + browser_go(browser, 0); + } else if (TrackControl(control, p, 0L) && control == browser->back) + browser_go(browser, -1); + else if (TrackControl(control, p, 0L) && control == browser->fwd) + browser_go(browser, 1); break; case inUpButton: case inDownButton: @@ -468,17 +577,20 @@ void browser_key_down(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)(focusable->cookie); + TERec *te; char k; k = (event->message & charCodeMask); if (k == '\r') { - browser_go(browser); + browser_go(browser, 0); return; } TEKey(k, browser->uri_te); TESelView(browser->uri_te); + + browser_update_buttons(browser); } bool @@ -489,9 +601,15 @@ browser_handle_menu(struct focusable *focusable, short switch (menu) { case EDIT_MENU_ID: switch (item) { + case EDIT_MENU_CUT_ID: + TECut(browser->uri_te); + return true; case EDIT_MENU_COPY_ID: TECopy(browser->uri_te); return true; + case EDIT_MENU_PASTE_ID: + TEPaste(browser->uri_te); + return true; case EDIT_MENU_SELECT_ALL_ID: TESetSelect(0, 1024 * 32, browser->uri_te); return true; @@ -503,57 +621,124 @@ browser_handle_menu(struct focusable *focusable, short } void -browser_go(struct browser *browser) +browser_go_uri(struct browser *browser, char *uristr) { TERec *te; - short len; - char *uristr; + Rect r; + TESetText(uristr, strlen(uristr), browser->uri_te); + TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); + HLock(browser->uri_te); - te = *(browser->uri_te); - HLock(te->hText); - len = te->teLength; - uristr = xmalloc(len + 1); - if (uristr == NULL) { - warn("Out of memory"); - return; - } - memcpy(uristr, *(te->hText), len); - uristr[len] = '\0'; - HUnlock(te->hText); + r = (*(browser->uri_te))->viewRect; HUnlock(browser->uri_te); + TEUpdate(&r, browser->uri_te); + + browser_go(browser, 0); +} - browser_load_url(browser, uristr); - xfree(&uristr); +void +browser_go(struct browser *browser, short dir) +{ + TERec *te; + page_handle pageh, opage; + struct page *page; + short len; + char *uristr; + + if (dir == 0) { + HLock(browser->uri_te); + te = *(browser->uri_te); + len = te->teLength; + uristr = xmalloc(len + 1); + if (uristr == NULL) { + warn("Out of memory"); + return; + } + HLock(te->hText); + memcpy(uristr, *(te->hText), len); + HUnlock(te->hText); + uristr[len] = '\0'; + HUnlock(browser->uri_te); + + browser_create_page(browser, uristr); + xfree(&uristr); + } else { + browser_stop_loading_page(browser); + + pageh = browser->current_page; + if (!pageh) + return; + + HLock(pageh); + page = *pageh; + if (dir == -1) + opage = page->back_page; + else + opage = page->fwd_page; + HUnlock(pageh); + if (!opage) + return; + + HLock(opage); + if ((*opage)->handler->init(opage) && (*opage)->content_len == 0) + /* page didn't load before, try again */ + browser->loading_page = opage; + HLock(opage); + (*opage)->handler->process(opage); + HUnlock(opage); + } } -bool -browser_load_url(struct browser *browser, char *uristr) +page_handle +browser_create_page(struct browser *browser, char *uristr) { - struct request_handler *handler; + page_handle pageh; + struct page *page; + struct uri_handler *handler; struct URI *uri; short n; - browser_stop_request(browser); + browser_stop_loading_page(browser); - for (n = 0; n < nitems(request_handlers); n++) { - handler = request_handlers[n]; + for (n = 0; n < nitems(uri_handlers); n++) { + handler = uri_handlers[n]; - if ((uri = handler->parse_uri(uristr)) != NULL) { - browser->handler = handler; - break; + if ((uri = handler->parse_uri(uristr)) == NULL) + continue; + + pageh = (page_handle)NewHandleClear(sizeof(struct page) + + PAGE_CONTENT_CHUNK_SIZE); + if (pageh == NULL) { + warn("Out of memory for new page"); + return; } + HLock(pageh); + page = *pageh; + page->browser = browser; + page->uri = uri; + page->handler = handler; + page->content_size = PAGE_CONTENT_CHUNK_SIZE; + HUnlock(pageh); + + browser->loading_page = pageh; + browser_update_buttons(browser); + + if (handler->init(pageh)) + return pageh; + + /* handler failed, no point in keeping page */ + browser->loading_page = NULL; + browser_update_buttons(browser); + HLock(pageh); + page = *pageh; + xfree(&page->uri); + DisposeHandle(pageh); + return NULL; } - if (uri == NULL) { - warn("Could not parse URI"); - return false; - } - - if ((browser->request = browser->handler->init_request(browser, uri))) - return true; - - return false; + warn("Could not parse URI \"%s\"", uristr); + return NULL; } size_t @@ -568,9 +753,7 @@ browser_statusf(struct browser *browser, const char *f browser->status_length = sizeof(browser->status_text) - 1; va_end(argptr); - browser_use_shadow(browser); browser_draw_status(browser); - browser_reveal_shadow(browser); } void @@ -631,8 +814,6 @@ browser_print(struct browser *browser, const char *str } else TVAppend(browser->output_tv, &style, (char *)str, len); - //TVUpdateScrollbar(browser->output_tv, browser->output_tv_scroller); - return len; } @@ -670,10 +851,10 @@ browser_print_link(struct browser *browser, const char if (browser->last_link) browser->last_link->next_link = link; - else { + else browser->first_link = link; - browser->last_link = link; - } + + browser->last_link = link; browser->style |= STYLE_LINK; browser_print(browser, link->title, link->len); @@ -683,25 +864,82 @@ browser_print_link(struct browser *browser, const char } void -browser_commit_to_uri(struct browser *browser, struct URI *uri) +browser_commit_to_page(struct browser *browser, page_handle pageh) { - WindowPtr win; + struct page *page; + page_handle tpage, fpage, bpage; Rect r; - GetPort(&win); - SetPort(browser->win); + browser_free_links(browser); + + HLock(pageh); + page = *pageh; - browser_cleanup(browser); + if (browser->current_page == pageh) + Debugger(); + + TESetText(page->uri->str, strlen(page->uri->str), browser->uri_te); + TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); HLock(browser->uri_te); r = (*(browser->uri_te))->viewRect; HUnlock(browser->uri_te); - TESetText(uri->str, strlen(uri->str), browser->uri_te); - TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); TEUpdate(&r, browser->uri_te); TVClear(browser->output_tv); TVUpdateScrollbar(browser->output_tv, browser->output_tv_scroller); - SetPort(win); + if (browser->current_page) { + HLock(browser->current_page); + fpage = (*(browser->current_page))->fwd_page; + bpage = (*(browser->current_page))->back_page; + HUnlock(browser->current_page); + + if (pageh == bpage) { + /* we're going backwards in history */ + } else if (pageh == fpage) { + /* we're going fowards in history */ + } else { + /* purge any forward pages because we're forking history now */ + while (fpage) { + HLock(fpage); + tpage = (*fpage)->fwd_page; + DisposeHandle(fpage); + fpage = tpage; + } + + HLock(browser->current_page); + (*(browser->current_page))->fwd_page = pageh; + HUnlock(browser->current_page); + + page->back_page = browser->current_page; + } + } + + browser->current_page = pageh; + HUnlock(pageh); + + browser_update_buttons(browser); +} + +size_t +browser_grow_page_content(page_handle pageh, size_t len) +{ + unsigned long size; + + HLock(pageh); + size = (*pageh)->content_size + MAX(len, PAGE_CONTENT_CHUNK_SIZE); + HUnlock(pageh); + + SetHandleSize(pageh, sizeof(struct page) + size); + if (MemError() != 0) { + warn("Out of memory growing page handle"); + return 0; + } + + HLock(pageh); + (*pageh)->content_size = size; + HUnlock(pageh); + + return size; } --- browser.h Mon Oct 28 13:45:20 2024 +++ browser.h Sun Nov 3 11:22:13 2024 @@ -18,6 +18,7 @@ #define __BROWSER_H__ #include <stdlib.h> +#include "detritus.h" #include "textview.h" #include "tcp.h" #include "util.h" @@ -43,19 +44,21 @@ struct browser_link { struct browser { WindowPtr win; - GrafPtr shadow; - GrafPtr shadow_old_port; + RgnHandle header; short shadow_refcnt; TEHandle uri_te; - ControlHandle go_button; + ControlHandle back; + ControlHandle fwd; + ControlHandle go_stop; TVHandle output_tv; ControlHandle output_tv_scroller; Rect status_rect; unsigned short status_length; - char status_text[512]; + short redirs; + char status_text[256]; - struct request_handler *handler; - void *request; + page_handle current_page; + page_handle loading_page; unsigned long style; struct browser_link *first_link; @@ -69,6 +72,8 @@ size_t browser_print_link(struct browser *browser, con size_t browser_print(struct browser *browser, const char *str, size_t len); //#define BROWSER_DEBUGF(x) browser_statusf #define BROWSER_DEBUGF(x) -void browser_commit_to_uri(struct browser *browser, struct URI *uri); +void browser_commit_to_page(struct browser *browser, page_handle pageh); +size_t browser_grow_page_content(page_handle pageh, size_t len); +void browser_go_uri(struct browser *browser, char *uristr); #endif \ No newline at end of file --- detritus.h Fri Oct 25 21:47:44 2024 +++ detritus.h Sun Nov 3 11:22:22 2024 @@ -14,10 +14,9 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef __GEMINO_H__ -#define __GEMINO_H__ +#ifndef __DETRITUS_H__ +#define __DETRITUS_H__ -#include "browser.h" #include "util.h" #define PROGRAM_NAME "Detritus" @@ -36,6 +35,10 @@ #define EDIT_MENU_PASTE_ID 3 #define EDIT_MENU_SELECT_ALL_ID 4 +#define HISTORY_MENU_ID 131 + +#define BOOKMARKS_MENU_ID 132 + extern uint8_t tls_req_last_id; struct tls_init_request { @@ -46,27 +49,51 @@ struct tls_init_request { }; struct URI { -#define URI_PROTOCOL_LEN 20 - char protocol[URI_PROTOCOL_LEN + 1]; -#define URI_HOSTNAME_LEN 255 - char hostname[URI_HOSTNAME_LEN + 1]; -#define URI_PATH_LEN 512 - char path[URI_PATH_LEN + 1]; +#define URI_MAX_PROTOCOL_LEN 20 + char *protocol; +#define URI_MAX_HOSTNAME_LEN 255 + char *hostname; +#define URI_MAX_PATH_LEN 512 + char *path; +#define URI_MAX_STR_LEN (URI_MAX_PROTOCOL_LEN + 3 + URI_MAX_HOSTNAME_LEN + \ + URI_MAX_PATH_LEN) + char *str; unsigned short port; - char str[800]; }; -struct request_handler { +typedef struct page **page_handle; +struct page { + struct URI *uri; + struct browser *browser; + page_handle back_page; + page_handle fwd_page; + size_t content_size; +#define PAGE_CONTENT_CHUNK_SIZE 1024 + size_t content_len; + size_t content_pos; + struct uri_handler *handler; + void *handler_cookie; + char type[32]; + short parse_state; + struct URI *redir_to; + char content[]; +}; + +struct uri_handler { struct URI * (*parse_uri)(char *uristr); - void * (*init_request)(struct browser *browser, struct URI *uri); - bool (*process_request)(void *cookie); - void (*free_request)(void *cookie); + /* do any post-init on a new page, like doing a request if no content */ + bool (*init)(page_handle pageh); + /* process any request or parse existing content */ + bool (*process)(page_handle pageh); + /* cleanup when done loading, such as freeing request */ + void (*cleanup)(page_handle pageh); }; -extern MenuHandle file_menu, edit_menu; +extern MenuHandle file_menu, edit_menu, bookmarks_menu; void menu_defaults(void); -struct URI * generic_parse_uri(char *protocol, char *uristr); +struct URI * parse_uri(char *uristr, char *restrict_protocol); +struct URI * build_relative_uri(struct URI *uri, char *relative, size_t len); short scsi_find_tls(void); uint8_t scsi_tls_init(struct tls_init_request *req); --- gemini.c Sat Oct 26 14:54:01 2024 +++ gemini.c Sun Nov 3 20:05:38 2024 @@ -19,6 +19,7 @@ #include <string.h> #include "detritus.h" +#include "browser.h" #define GEMINI_PORT 1965 @@ -29,21 +30,16 @@ enum { }; enum { - GEM_STATE_HEADER, - GEM_STATE_GEMTEXT, - GEM_STATE_REDIRECT, - GEM_STATE_DOWNLOAD + PARSE_STATE_HEADER, + PARSE_STATE_GEMTEXT, + PARSE_STATE_DOWNLOAD }; struct gemini_request { - struct browser *browser; - struct URI *uri; - uint8_t tls_id; unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ unsigned char tcp_input[2048]; - size_t tcp_input_len; - short state; + unsigned long tcp_input_len; TCPiopb tcp_iopb; StreamPtr tcp_stream; @@ -51,48 +47,41 @@ struct gemini_request { TCPStatusPB tcp_status_pb; bool tcp_done_reading; - char message[member_size(struct URI, str) + 3]; + char message[URI_MAX_STR_LEN + 1]; size_t message_len; - short gem_state; - char response[1024]; - size_t response_len; - size_t total_response_len; - - char mime_type[64]; - unsigned long style; + short state; }; struct URI * gemini_parse_uri(char *uristr); -void * gemini_init_request(struct browser *browser, struct URI *uri); -bool gemini_process_request(void *cookie); -void gemini_free(void *cookie); -static bool shuffle_read_tls_ciphertext(struct gemini_request *req); -static bool shuffle_read_tcp_ciphertext(struct gemini_request *req, - short space); -static bool shuffle_tls_send_plaintext(struct gemini_request *req, - short space); -static bool shuffle_tls_read_plaintext(struct gemini_request *req); -static bool parse_response(struct gemini_request *req); -static bool parse_header(struct gemini_request *req, char *str); -static void consume_response(struct gemini_request *req, size_t len); +bool gemini_init_request(page_handle pageh); +bool gemini_process(page_handle pageh); +void gemini_cleanup(page_handle pageh); -struct request_handler gemini_handler = { +static bool shuffle_read_tls_ciphertext(struct page *page); +static bool shuffle_read_tcp_ciphertext(struct page *page, short space); +static bool shuffle_tls_send_plaintext(struct page *page, short space); +static bool shuffle_tls_read_plaintext(struct page *page); +static bool parse_header(struct page *page, char *str, size_t len); +static bool parse_content(struct page *page); + +struct uri_handler gemini_handler = { gemini_parse_uri, gemini_init_request, - gemini_process_request, - gemini_free, + gemini_process, + gemini_cleanup, }; struct URI * gemini_parse_uri(char *uristr) { - return generic_parse_uri("gemini", uristr); + return parse_uri(uristr, "gemini"); } -void * -gemini_init_request(struct browser *browser, struct URI *uri) +bool +gemini_init_request(page_handle pageh) { + struct page *page; struct gemini_request *req = NULL; struct tls_init_request tls_req; char ip_s[12 + 3 + 1]; @@ -100,6 +89,15 @@ gemini_init_request(struct browser *browser, struct UR short err; ip_addr ip, local_ip; tcp_port port, local_port; + + HLock(pageh); + page = *pageh; + if (page->content_len) { + /* already fetched, nothing to do here but reset */ + page->content_pos = 0; + page->parse_state = PARSE_STATE_HEADER; + goto done; + } req = xmalloczero(sizeof(struct gemini_request)); if (req == NULL) { @@ -107,189 +105,235 @@ gemini_init_request(struct browser *browser, struct UR goto fail; } - req->browser = browser; - req->uri = uri; - if (req->uri->port == 0) - req->uri->port = GEMINI_PORT; + if (page->uri->port == 0) + page->uri->port = GEMINI_PORT; - browser_statusf(browser, "Resolving \"%s\"...", req->uri->hostname); + browser_statusf(page->browser, "Resolving \"%s\"...", + page->uri->hostname); - err = DNSResolveName(req->uri->hostname, &ip, NULL); + err = DNSResolveName(page->uri->hostname, &ip, NULL); if (err) { - browser_statusf(browser, "Error: Failed resolving: %d", err); + browser_statusf(page->browser, "Error: Failed resolving: %d", err); goto fail; } long2ip(ip, (char *)&ip_s); - browser_statusf(browser, "Connecting to %s port %d...", ip_s, - req->uri->port); + browser_statusf(page->browser, "Connecting to %s port %d...", ip_s, + page->uri->port); err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf, sizeof(req->tcp_buf), NULL, NULL, NULL, false); if (err) { - browser_statusf(browser, "Error: TCPCreate failed: %d", err); + browser_statusf(page->browser, "Error: TCPCreate failed: %d", err); goto fail; } err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip, - req->uri->port, &local_ip, &local_port, NULL, NULL, false); + page->uri->port, &local_ip, &local_port, NULL, NULL, false); if (err) { - browser_statusf(browser, + browser_statusf(page->browser, "Error: Failed connecting to %s (%s) port %d: %d", - req->uri->hostname, ip_s, req->uri->port, err); + page->uri->hostname, ip_s, page->uri->port, err); goto fail; } err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, NULL, NULL, false); if (err) { - browser_statusf(browser, + browser_statusf(page->browser, "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", - req->uri->hostname, ip_s, req->uri->port, err); + page->uri->hostname, ip_s, page->uri->port, err); goto fail; } req->message_len = snprintf(req->message, sizeof(req->message), - "%s\r\n", req->uri->str); + "%s\r\n", page->uri->str); - browser_statusf(browser, "Connected to %s", req->uri->hostname); + browser_statusf(page->browser, + "Connected to %s, performing TLS negotiation...", + page->uri->hostname); memset(&tls_req, 0, sizeof(tls_req)); - strlcpy(tls_req.hostname, req->uri->hostname, sizeof(tls_req.hostname)); + strlcpy(tls_req.hostname, page->uri->hostname, + sizeof(tls_req.hostname)); time = MAC_TO_UNIX_TIME(Time); tls_req.unix_time[0] = (time >> 24) & 0xff; tls_req.unix_time[1] = (time >> 16) & 0xff; tls_req.unix_time[2] = (time >> 8) & 0xff; tls_req.unix_time[3] = (time) & 0xff; - /* sad */ tls_req.flags[1] = BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY; - - browser_statusf(browser, "Performing TLS handshake..."); + req->tls_id = scsi_tls_init(&tls_req); req->state = REQ_STATE_NEGOTIATING; if (req->tls_id == 0) { - browser_statusf(browser, "TLS handshake failed!"); + browser_statusf(page->browser, "TLS handshake failed!"); goto fail; } + + page->handler_cookie = req; + +done: + HUnlock(pageh); + return true; - return req; - fail: - if (req->tcp_stream) { - _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - } + if (req) { + if (req->tcp_stream) { + _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + } - if (req) xfree(&req); + page->handler_cookie = NULL; + } - return NULL; + HUnlock(pageh); + return false; } void -gemini_free(void *cookie) +gemini_cleanup(page_handle pageh) { - struct gemini_request *req = (struct gemini_request *)cookie; + struct gemini_request *req; + struct page *page; - if (req->tcp_stream) { - _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - } + HLock(pageh); + page = *pageh; - if (req->tls_id) - scsi_tls_close(req->tls_id); + if (page->handler_cookie) { + req = (struct gemini_request *)(page->handler_cookie); + + if (req->tcp_stream) { + _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + } + + if (req->tls_id) + scsi_tls_close(req->tls_id); - xfree(&req->uri); - xfree(&req); + xfree(&page->handler_cookie); + } + + HUnlock(pageh); } bool -gemini_process_request(void *cookie) +gemini_process(page_handle pageh) { - struct gemini_request *req = (struct gemini_request *)cookie; + struct gemini_request *req; + struct page *page; size_t len; unsigned short slen; short err, status; short cipherspace, plainspace, error; + bool ret; + + HLock(pageh); + page = *pageh; + if (page->handler_cookie == NULL) { + ret = parse_content(page); + HUnlock(pageh); + return ret; + } + + req = (struct gemini_request *)(page->handler_cookie); + if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { - BROWSER_DEBUGF((req->browser, "TCP I/O Result %d, disconnecting", + BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting", req->tcp_iopb.ioResult)); - return false; + goto finish_request; } status = scsi_tls_status(req->tls_id, &cipherspace, &plainspace, &error); while (status != 0) { - if ((status & 0x1) && !req->response_len) { + if ((status & 0x1) && page->content_pos == page->content_len) { /* closed */ if (error == 0) - browser_statusf(req->browser, - "Finished reading %ld bytes", req->total_response_len); + browser_statusf(page->browser, + "Finished reading %ld bytes", page->content_len); else - browser_statusf(req->browser, - "Error: TLS handshake failed: %d (TLS 0x%x)", + browser_statusf(page->browser, + "Error: TLS handshake failed: %d (TLS status 0x%x)", error, status); - return false; + goto finish_request; } if (status & 0x2) { /* tls has ciphertext for tcp */ - if (!shuffle_read_tls_ciphertext(req)) - return false; + if (!shuffle_read_tls_ciphertext(page)) + goto finish_request; + HLock(pageh); + page = *pageh; status &= ~0x2; } - if ((status & 0x10) || req->response_len) { + if ((status & 0x10) || page->content_pos < page->content_len) { /* tls has plaintext data for us */ - if (!shuffle_tls_read_plaintext(req)) - return false; + if (!shuffle_tls_read_plaintext(page)) + goto finish_request; + HLock(pageh); + page = *pageh; status &= ~0x10; } if (status & 0x8) { /* tls can read plaintext from us */ - if (!shuffle_tls_send_plaintext(req, plainspace)) - return false; + if (!shuffle_tls_send_plaintext(page, plainspace)) + goto finish_request; + HLock(pageh); + page = *pageh; status &= ~0x8; } if (status & 0x4) { /* tls can read ciphertext from tcp */ - if (!shuffle_read_tcp_ciphertext(req, cipherspace)) - return false; + if (!shuffle_read_tcp_ciphertext(page, cipherspace)) + goto finish_request; + HLock(pageh); + page = *pageh; status &= ~0x4; } if (status) { - browser_statusf(req->browser, "Status is 0x%x?", status); - return false; + browser_statusf(page->browser, "TLS status is 0x%x?", status); + goto finish_request; } } + HUnlock(pageh); return true; + +finish_request: + HUnlock(pageh); + gemini_cleanup(pageh); + return false; } static bool -shuffle_read_tls_ciphertext(struct gemini_request *req) +shuffle_read_tls_ciphertext(struct page *page) { + struct gemini_request *req; size_t len; short err; unsigned char *buf; /* read ciphertext from TLS and send it out TCP */ + + req = (struct gemini_request *)(page->handler_cookie); len = scsi_tls_read(req->tls_id, &buf, 0, true); if (len == 0 || buf == NULL) { - browser_statusf(req->browser, + browser_statusf(page->browser, "Error: No ciphertext read from TLS when expected to"); return false; } - BROWSER_DEBUGF((req->browser, + BROWSER_DEBUGF((page->browser, "Read %lu bytes of TLS ciphertext, forwarding to TCP", len)); if (req->tcp_done_reading) @@ -302,7 +346,7 @@ shuffle_read_tls_ciphertext(struct gemini_request *req err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL, NULL, false); if (err) { - browser_statusf(req->browser, "Error: TCPSend failed: %d", err); + browser_statusf(page->browser, "Error: TCPSend failed: %d", err); return false; } @@ -310,27 +354,30 @@ shuffle_read_tls_ciphertext(struct gemini_request *req } static bool -shuffle_read_tcp_ciphertext(struct gemini_request *req, short space) +shuffle_read_tcp_ciphertext(struct page *page, short space) { + struct gemini_request *req; size_t len, n; short err; unsigned short slen; /* read ciphertext from TCP and send it to TLS */ + + req = (struct gemini_request *)(page->handler_cookie); if (req->tcp_input_len == sizeof(req->tcp_input) || req->tcp_done_reading) goto forward_ciphertext; - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, - &req->tcp_status_pb, NULL, NULL, false); + err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, + NULL, NULL, false); if (err) { req->tcp_done_reading = true; - browser_statusf(req->browser, "Error: Bad TCPStatus: %d", err); + browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err); goto forward_ciphertext; } if (req->tcp_status_pb.connectionState != ConnectionStateEstablished) { - BROWSER_DEBUGF((req->browser, "TCP connection closed (state %d)", + BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)", req->tcp_status_pb.connectionState)); req->tcp_done_reading = true; goto forward_ciphertext; @@ -342,7 +389,7 @@ shuffle_read_tcp_ciphertext(struct gemini_request *req slen = MIN(req->tcp_status_pb.amtUnreadData, sizeof(req->tcp_input) - req->tcp_input_len); if (!slen) { - browser_statusf(req->browser, + browser_statusf(page->browser, "Error: No buffer space available in tcp_input?"); goto forward_ciphertext; } @@ -351,11 +398,11 @@ shuffle_read_tcp_ciphertext(struct gemini_request *req (Ptr)(req->tcp_input + req->tcp_input_len), &slen, NULL, NULL, false); if (err) { - browser_statusf(req->browser, "Error: Failed TCPRcv: %d", err); + browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err); goto forward_ciphertext; } req->tcp_input_len += slen; - BROWSER_DEBUGF((req->browser, + BROWSER_DEBUGF((page->browser, "Read %d bytes of TCP ciphertext, forwarding to TLS", slen)); forward_ciphertext: @@ -365,11 +412,11 @@ forward_ciphertext: slen = req->tcp_input_len; if (slen > space) slen = space; - BROWSER_DEBUGF((req->browser, + BROWSER_DEBUGF((page->browser, "Forwarding %d bytes of TCP ciphertext to TLS", slen)); len = scsi_tls_write(req->tls_id, req->tcp_input, slen, true); if (len == 0) { - browser_statusf(req->browser, + browser_statusf(page->browser, "Error: Failed forwarding %d bytes of ciphertext to TLS", slen); return false; } @@ -381,27 +428,31 @@ forward_ciphertext: req->tcp_input_len - len); req->tcp_input_len -= len; } - BROWSER_DEBUGF((req->browser, + BROWSER_DEBUGF((page->browser, "Wrote %ld bytes of TCP ciphertext to TLS, %ld left", len, req->tcp_input_len)); return true; } static bool -shuffle_tls_send_plaintext(struct gemini_request *req, short space) +shuffle_tls_send_plaintext(struct page *page, short space) { + page_handle pageh; + struct gemini_request *req; size_t slen, len; /* send any plaintext from us to TLS */ + req = (struct gemini_request *)(page->handler_cookie); + if (req->message_len == 0) return true; if (req->state == REQ_STATE_NEGOTIATING) { - browser_statusf(req->browser, "Sending request..."); + browser_statusf(page->browser, "Negotiated, sending request..."); req->state = REQ_STATE_SENDING_REQUEST; } else if (req->state != REQ_STATE_SENDING_REQUEST) { - browser_statusf(req->browser, "In bogus state (%d) instead of " + browser_statusf(page->browser, "In bogus state (%d) instead of " "SENDING_REQUEST, disconnecting", req->state); return false; } @@ -412,7 +463,7 @@ shuffle_tls_send_plaintext(struct gemini_request *req, len = scsi_tls_write(req->tls_id, (unsigned char *)req->message, slen, false); if (!len) { - browser_statusf(req->browser, + browser_statusf(page->browser, "Error: Failed sending %ld bytes of plaintext to TLS", slen); return false; } @@ -424,358 +475,352 @@ shuffle_tls_send_plaintext(struct gemini_request *req, req->message_len - len); req->message_len -= len; } - BROWSER_DEBUGF((req->browser, "Wrote %ld bytes of plaintext to TLS, " + BROWSER_DEBUGF((page->browser, "Wrote %ld bytes of plaintext to TLS, " "%ld left", len, req->message_len)); return true; } static bool -shuffle_tls_read_plaintext(struct gemini_request *req) +shuffle_tls_read_plaintext(struct page *page) { + page_handle pageh; + struct gemini_request *req; size_t len; unsigned char *buf; /* read as much plaintext from TLS as we can buffer */ - if (req->state == REQ_STATE_NEGOTIATING) { - browser_clear(req->browser); - req->state = REQ_STATE_PARSING_RESPONSE; - req->gem_state = GEM_STATE_HEADER; - } + req = (struct gemini_request *)(page->handler_cookie); - len = sizeof(req->response) - req->response_len; - if (len > 0) { - len = scsi_tls_read(req->tls_id, &buf, len, false); - if (len > 0) { - if (len > sizeof(req->response) + req->response_len) - panic("response overflow!"); - memcpy(req->response + req->response_len, buf, len); - req->response_len += len; - req->total_response_len += len; - - browser_statusf(req->browser, - "Read %ld bytes", req->total_response_len); - } + len = scsi_tls_read(req->tls_id, &buf, PAGE_CONTENT_CHUNK_SIZE, false); + if (len == 0) + return true; + + if (page->content_len + len >= page->content_size) { + pageh = (page_handle)RecoverHandle(page); + if (!browser_grow_page_content(pageh, len)) + return false; + HLock(pageh); + page = *pageh; + req = (struct gemini_request *)(page->handler_cookie); } + + memcpy(page->content + page->content_len, buf, len); + page->content_len += len; - if (req->response_len) - return parse_response(req); - - return true; + browser_statusf(page->browser, "Read %ld bytes", page->content_len); + + return parse_content(page); } static bool -parse_response(struct gemini_request *req) +parse_content(struct page *page) { size_t n, trail, skip, len; char c; + bool newline; handle_state: - switch (req->gem_state) { - case GEM_STATE_HEADER: { + switch (page->parse_state) { + case PARSE_STATE_HEADER: { short status; - for (n = 0; n < req->response_len; n++) { - c = req->response[n]; + for (n = page->content_pos; n < page->content_len; n++) { + c = page->content[n]; - if (!(c == '\n' && n && req->response[n - 1] == '\r')) + if (!(c == '\n' && n && page->content[n - 1] == '\r')) continue; - req->response[n] = '\0'; - req->response[n - 1] = '\0'; - - if (!parse_header(req, req->response)) { - BROWSER_DEBUGF((req->browser, + if (!parse_header(page, page->content + page->content_pos, + n - page->content_pos - 2)) { + BROWSER_DEBUGF((page->browser, "Header parsing failed, disconnecting")); return false; } - if (strncmp(req->mime_type, "text/gemini", 11) == 0) - req->gem_state = GEM_STATE_GEMTEXT; - else - req->gem_state = GEM_STATE_DOWNLOAD; + if (strcmp(page->type, "text/gemini") == 0) { + page->parse_state = PARSE_STATE_GEMTEXT; + browser_commit_to_page(page->browser, + (page_handle)RecoverHandle(page)); + } else + page->parse_state = PARSE_STATE_DOWNLOAD; - consume_response(req, n + 1); + page->content_pos = n + 1; /* avoid a round trip through idle handler */ goto handle_state; } + + if (page->handler_cookie == NULL) + /* no more data will be coming */ + return false; + break; } - case GEM_STATE_REDIRECT: + case PARSE_STATE_DOWNLOAD: /* TODO */ break; - case GEM_STATE_DOWNLOAD: - /* TODO */ - break; - case GEM_STATE_GEMTEXT: -restart_parse: - for (n = 0; n <= req->response_len; n++) { - if (n == req->response_len && n < 3) { - /* - * End of buffer with no newline, but not enough chars - * to determine what this line is, skip until we get more. - */ - break; - } - - if (!(n == req->response_len || req->response[n] == '\n')) + case PARSE_STATE_GEMTEXT: + TVAutoCalc(page->browser->output_tv, false); + + for (n = page->content_pos; n <= page->content_len; n++) { + if (!(n == page->content_len || page->content[n] == '\n')) continue; - len = n + 1; + len = n - page->content_pos + 1; trail = 0; skip = 0; + newline = false; - if (n < req->response_len && req->response[n] == '\n') { - req->response[n] = '\r'; - if (n > 0 && req->response[n - 1] == '\r') { + if (n < page->content_len && page->content[n] == '\n') { + len--; + trail = 1; + newline = true; + + if (n > 0 && page->content[n - 1] == '\r') { len--; - trail = 1; + trail++; } - } else if (req->response_len < sizeof(req->response) && - !req->tcp_done_reading) { + } else if (page->handler_cookie != NULL) { /* * No newline found at the end of the buffer, but the * buffer isn't full so wait for more data. */ break; } + + /* check for pre first because the other styles can be in it */ + if (page->content[page->content_pos] == '`' && + page->content[page->content_pos + 1] == '`' && + page->content[page->content_pos + 2] == '`') { + /* ``` toggle */ + if (page->browser->style & STYLE_PRE) + page->browser->style = STYLE_NONE; + else + page->browser->style = STYLE_PRE; + + /* the rest of the line can be a description, ignore */ + trail += len; + len = 0; + newline = false; + goto print_line; + } else if (page->browser->style & STYLE_PRE) + goto print_line; - /* - * If we didn't find a newline but we're at the end of the - * buffer, shift it down first to see if we can fit the whole - * line in the buffer at once */ - - if (req->response[0] == '=' && req->response[1] == '>') { + if (page->content[page->content_pos] == '=' && + page->content[page->content_pos + 1] == '>') { /* link */ + ssize_t link_len = 0; + ssize_t title_len = 0; + ssize_t j, tlen; + char *link = NULL; + char *title = NULL; + char *tcontent; + struct URI *newuri; + skip = 2; len -= 2; - if (req->response[skip] == ' ') { - skip++; + tlen = len; + + tcontent = page->content + page->content_pos + skip; + link = tcontent; + + /* skip leading whitespace */ + while (len && (c = *tcontent) && (c == ' ' || c == '\t')) { + tcontent++; + link++; len--; } - browser_print_link(req->browser, req->response + skip, - len, NULL, 0); - req->browser->style = STYLE_NONE; - consume_response(req, skip + len + trail); - goto restart_parse; + + /* consume link */ + while (len && (c = *tcontent) && c != ' ' && c != '\t') { + tcontent++; + len--; + link_len++; + } + + /* consume whitespace after link */ + while (len && (c = *tcontent) && (c == ' ' || c == '\t')) { + tcontent++; + len--; + } + + if (len) { + title = tcontent; + title_len = len; + + /* trim trailing whitespace from title */ + while (title_len && (c = title[title_len - 1]) && + (c == ' ' || c == '\t')) { + title_len--; + } + + if (title_len == 0) + title = NULL; + } + + newuri = build_relative_uri(page->uri, link, link_len); + if (newuri) { + browser_print_link(page->browser, + newuri->str, strlen(newuri->str), title, title_len); + xfree(&newuri); + } else + browser_print_link(page->browser, + link, link_len, title, title_len); + page->browser->style = STYLE_NONE; + browser_print(page->browser, "\r", 1); + page->content_pos += skip + tlen + trail; + continue; } - if (req->response[0] == '`' && req->response[1] == '`' && - req->response[2] == '`') { - /* ``` toggle */ - req->browser->style = STYLE_PRE; - consume_response(req, len); - goto restart_parse; - } - - if (req->response[0] == '#' && req->response[1] == '#' && - req->response[2] == '#') { + if (page->content[page->content_pos] == '#' && + page->content[page->content_pos + 1] == '#' && + page->content[page->content_pos + 2] == '#') { /* ### h3 */ - req->browser->style = STYLE_H3; + page->browser->style = STYLE_H3; skip = 3; len -= 3; - if (req->response[skip] == ' ') { + while ((c = page->content[page->content_pos + skip]) && + (c == ' ' || c == '\t')) { skip++; len--; } - browser_print(req->browser, req->response + skip, len); - consume_response(req, skip + len + trail); - req->browser->style = STYLE_NONE; - goto restart_parse; + goto print_line; } - if (req->response[0] == '#' && req->response[1] == '#') { + if (page->content[page->content_pos] == '#' && + page->content[page->content_pos + 1] == '#') { /* ## h2 */ - req->browser->style = STYLE_H2; + page->browser->style = STYLE_H2; skip = 2; len -= 2; - if (req->response[skip] == ' ') { + while ((c = page->content[page->content_pos + skip]) && + (c == ' ' || c == '\t')) { skip++; len--; } - browser_print(req->browser, req->response + skip, len); - consume_response(req, skip + len + trail); - req->browser->style = STYLE_NONE; - goto restart_parse; + goto print_line; } - if (req->response[0] == '#') { + if (page->content[page->content_pos] == '#') { /* # h1 */ - req->browser->style = STYLE_H1; + page->browser->style = STYLE_H1; skip = 1; len--; - if (req->response[skip] == ' ') { + while ((c = page->content[page->content_pos + skip]) && + (c == ' ' || c == '\t')) { skip++; len--; } - browser_print(req->browser, req->response + skip, len); - consume_response(req, skip + len + trail); - req->browser->style = STYLE_NONE; - goto restart_parse; + goto print_line; } - if (req->response[0] == '*') { + if (page->content[page->content_pos] == '*') { /* * list item */ - req->browser->style = STYLE_LIST; + page->browser->style = STYLE_LIST; skip = 1; len--; - if (req->response[skip] == ' ') { + while ((c = page->content[page->content_pos + skip]) && + (c == ' ' || c == '\t')) { skip++; len--; } - browser_print(req->browser, req->response + skip, len); - consume_response(req, skip + len + trail); - req->browser->style = STYLE_NONE; - goto restart_parse; + browser_print(page->browser, " • ", 3); + goto print_line; } - if (req->response[0] == '>') { + if (page->content[page->content_pos] == '>') { /* > quote text */ - req->browser->style = STYLE_QUOTE; + page->browser->style = STYLE_QUOTE; skip = 1; len--; - if (req->response[skip] == ' ') { + while ((c = page->content[page->content_pos + skip]) && + (c == ' ' || c == '\t')) { skip++; len--; } - browser_print(req->browser, req->response + skip, len); - consume_response(req, skip + len + trail); - req->browser->style = STYLE_NONE; - goto restart_parse; + goto print_line; } - - if (n == req->response_len) { - /* end of buffer with no start, probably a continuation */ - browser_print(req->browser, req->response, n); - consume_response(req, n); - break; - } - - /* just plain text */ - browser_print(req->browser, req->response, len - trail); - consume_response(req, len + trail); - goto restart_parse; + +print_line: + if (len) + browser_print(page->browser, + page->content + page->content_pos + skip, len); + + page->content_pos += skip + len + trail; + if (!(page->browser->style & STYLE_PRE)) + page->browser->style = STYLE_NONE; + if (newline) + browser_print(page->browser, "\r", 1); } + + TVAutoCalc(page->browser->output_tv, true); + TVCalcLines(page->browser->output_tv); + TVUpdate(page->browser->output_tv); + TVUpdateScrollbar(page->browser->output_tv, + page->browser->output_tv_scroller); break; } return true; } -static void -consume_response(struct gemini_request *req, size_t len) -{ - if (len == req->response_len) { - req->response_len = 0; - memset(req->response, 0, sizeof(req->response)); - } else { - memmove(req->response, req->response + len, - sizeof(req->response) - len); - req->response_len -= len; - memset(req->response + req->response_len, 0, - sizeof(req->response) - req->response_len); - } -} - static bool -parse_header(struct gemini_request *req, char *str) +parse_header(struct page *page, char *str, size_t len) { short status; + char fail[32]; - BROWSER_DEBUGF((req->browser, "Received header: %s", str)); + BROWSER_DEBUGF((page->browser, "Received header: %s", str)); if (!(str[0] >= '0' && str[0] <= '9' && str[1] >= '0' && str[1] <= '9' && str[2] == ' ')) { - browser_statusf(req->browser, "Error: Malformed response %s", str); + browser_statusf(page->browser, "Error: Malformed response %s", str); return false; } status = ((str[0] - '0') * 10) + (str[1] - '0'); + memcpy(fail, str, MIN(len, sizeof(fail))); + fail[MIN(len, sizeof(fail) - 1)] = '\0'; + if (status >= 10 && status <= 19) { /* input, not supported */ - browser_statusf(req->browser, - "Error: Input not supported (%d)", status); + browser_statusf(page->browser, "Error: Input not supported (%d)", + status); return false; } if (status >= 20 && status <= 29) { /* success */ - strlcpy(req->mime_type, str + 3, sizeof(req->mime_type)); + if (len + 3 + 1 > sizeof(page->type)) + len = sizeof(page->type) - 1; + memcpy(page->type, str + 3, len - 2); + page->type[len - 2] = '\0'; return true; } if (status >= 30 && status <= 39) { /* redirect */ - /* TODO */ + page->redir_to = build_relative_uri(page->uri, str + 3, len - 2); + /* TODO: infinite loop detection */ return false; } if (status >= 40 && status <= 49) { /* temp fail */ - browser_statusf(req->browser, "Error: Server reported temporary " - "failure (%d)", status); + browser_statusf(page->browser, "Error: Server reported temporary " + "failure: %s", fail); return false; } if (status >= 50 && status <= 59) { /* perm fail */ - browser_statusf(req->browser, "Error: Server reported failure (%d)", - status); + browser_statusf(page->browser, "Error: Server reported " + "permanent failure: %s", fail); return false; } if (status >= 60 && status <= 69) { /* auth, not supported */ - browser_statusf(req->browser, "Error: Auth not supported (%d)", + browser_statusf(page->browser, "Error: Auth not supported (%d)", status); return false; } - browser_statusf(req->browser, "Error: Unsupported status %d", status); + browser_statusf(page->browser, "Error: Unsupported status: %s", fail); return false; } - -#if 0 -void -browser_click_link(struct browser *browser, struct browser_link *link) -{ - static char new_uri[member_size(struct tcp_request, uri)]; - ssize_t n; - size_t len; - Rect r; - - if (strncmp(link->link, "gemini://", 9) == 0) - len = strlcpy(new_uri, link->link, sizeof(new_uri)); - else { - /* relative, remove any file from path */ - len = 0; - for (n = browser->req->uri_len - 1; n >= 0; n--) { - if (browser->req->uri[n] != '/') - continue; - - /* gemini://host/path -> gemini://host/ */ - len = n + 1; - memcpy(new_uri, browser->req->uri, len); - break; - } - if (len == 0) { - warn("failed making new uri from \"%s\" and \"%s\"", - browser->req->uri, link->link); - return; - } - - n = strlen(link->link); - memcpy(new_uri + len, link->link, n); - new_uri[len + n] = '\0'; - len = len + n; - } - - TESetText(new_uri, len, browser->uri_te); - TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); - - HLock(browser->uri_te); - r = (*(browser->uri_te))->viewRect; - HUnlock(browser->uri_te); - TEUpdate(&r, browser->uri_te); - -bool -browser_load_url(struct browser *browser, char *uristr) - browser_connect(browser); -} - -#endif \ No newline at end of file --- gopher.c Tue Oct 29 11:56:09 2024 +++ gopher.c Fri Nov 1 16:21:32 2024 @@ -19,104 +19,106 @@ #include <string.h> #include "detritus.h" +#include "browser.h" #define GOPHER_PORT 70 struct gopher_request { - struct browser *browser; - struct URI *uri; - char type; - unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ - char response[2048]; - size_t response_len; - size_t total_response_len; TCPiopb tcp_iopb; StreamPtr tcp_stream; wdsEntry tcp_wds[2]; TCPStatusPB tcp_status_pb; - bool tcp_done_reading; - char message[member_size(struct URI, path) + 3]; - size_t message_len; + char selector[URI_MAX_PATH_LEN + 3]; + size_t selector_len; }; struct URI * gopher_parse_uri(char *uristr); -void * gopher_init_request(struct browser *browser, struct URI *uri); -bool gopher_process_request(void *cookie); -void gopher_free(void *cookie); -static bool parse_response(struct gopher_request *req); -static void consume_response(struct gopher_request *req, size_t len); -static void gopher_print_menu(struct gopher_request *req, size_t len); +bool gopher_init_request(page_handle pageh); +bool gopher_process(page_handle pageh); +void gopher_cleanup(page_handle pageh); -struct request_handler gopher_handler = { +static bool parse_content(struct page *page); +static void gopher_print_menu(struct page *page, char *line, size_t len); + +struct uri_handler gopher_handler = { gopher_parse_uri, gopher_init_request, - gopher_process_request, - gopher_free, + gopher_process, + gopher_cleanup, }; struct URI * gopher_parse_uri(char *uristr) { - return generic_parse_uri("gopher", uristr); + return parse_uri(uristr, "gopher"); } -void * -gopher_init_request(struct browser *browser, struct URI *uri) +bool +gopher_init_request(page_handle pageh) { + struct page *page; struct gopher_request *req = NULL; + size_t len; char ip_s[12 + 3 + 1]; short err; ip_addr ip, local_ip; tcp_port port, local_port; + HLock(pageh); + page = *pageh; + if (page->content_len) { + /* already fetched, nothing to do here but reset */ + page->content_pos = 0; + goto done; + } + req = xmalloczero(sizeof(struct gopher_request)); if (req == NULL) { warn("Out of memory"); goto fail; } - req->browser = browser; - req->uri = uri; - if (req->uri->port == 0) - req->uri->port = GOPHER_PORT; + if (page->uri->port == 0) + page->uri->port = GOPHER_PORT; - browser_statusf(browser, "Resolving \"%s\"...", req->uri->hostname); + browser_statusf(page->browser, "Resolving \"%s\"...", + page->uri->hostname); - err = DNSResolveName(req->uri->hostname, &ip, NULL); + err = DNSResolveName(page->uri->hostname, &ip, NULL); if (err) { - browser_statusf(browser, "Error: Failed resolving: %d", err); + browser_statusf(page->browser, "Error: Failed resolving: %d", err); goto fail; } long2ip(ip, (char *)&ip_s); - browser_statusf(browser, "Connecting to %s port %d...", ip_s, - req->uri->port); + browser_statusf(page->browser, "Connecting to %s port %d...", ip_s, + page->uri->port); err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf, sizeof(req->tcp_buf), NULL, NULL, NULL, false); if (err) { - browser_statusf(browser, "Error: TCPCreate failed: %d", err); + browser_statusf(page->browser, "Error: TCPCreate failed: %d", err); goto fail; } err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip, - req->uri->port, &local_ip, &local_port, NULL, NULL, false); + page->uri->port, &local_ip, &local_port, NULL, NULL, false); if (err) { - browser_statusf(browser, + browser_statusf(page->browser, "Error: Failed connecting to %s (%s) port %d: %d", - req->uri->hostname, ip_s, req->uri->port, err); + page->uri->hostname, ip_s, page->uri->port, err); goto fail; } err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, NULL, NULL, false); if (err) { - browser_statusf(browser, + browser_statusf(page->browser, "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", - req->uri->hostname, ip_s, req->uri->port, err); + page->uri->hostname, ip_s, page->uri->port, err); goto fail; } @@ -124,250 +126,287 @@ gopher_init_request(struct browser *browser, struct UR * If we have a path other than /, assume the first character is the * type and should not be sent, per RFC 4266. */ - if (req->uri->path[0] == '\0' || - (req->uri->path[0] == '/' && req->uri->path[1] == '\0')) { - req->type = '1'; - req->message_len = snprintf(req->message, sizeof(req->message), + if (page->uri->path[0] == '\0' || + (page->uri->path[0] == '/' && page->uri->path[1] == '\0')) { + page->type[0] = '1'; + req->selector_len = snprintf(req->selector, sizeof(req->selector), "\r\n"); } else { /* /0/blah -> blah */ - req->type = req->uri->path[1]; - req->message_len = snprintf(req->message, sizeof(req->message), - "%s\r\n", req->uri->path + 2); + page->type[0] = page->uri->path[1]; + req->selector_len = snprintf(req->selector, sizeof(req->selector), + "%s\r\n", page->uri->path + 2); } - browser_commit_to_uri(browser, req->uri); - browser_statusf(browser, "Connected to %s, sending request...", - req->uri->hostname); + browser_statusf(page->browser, "Connected to %s, sending request...", + page->uri->hostname); memset(&req->tcp_wds, 0, sizeof(req->tcp_wds)); - req->tcp_wds[0].ptr = (Ptr)req->message; - req->tcp_wds[0].length = req->message_len; + req->tcp_wds[0].ptr = (Ptr)req->selector; + req->tcp_wds[0].length = req->selector_len; err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL, NULL, false); if (err) { - browser_statusf(req->browser, "Error: TCPSend failed: %d", err); + browser_statusf(page->browser, "Error: TCPSend failed: %d", err); goto fail; } - return req; + page->handler_cookie = req; + +done: + HUnlock(pageh); + browser_commit_to_page(page->browser, pageh); + return true; + fail: - if (req->tcp_stream) { - _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + if (req) { + if (req->tcp_stream) { + _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + } + + xfree(&req); + page->handler_cookie = NULL; } - if (req) - xfree(&req); - - return NULL; + HUnlock(pageh); + return false; } void -gopher_free(void *cookie) +gopher_cleanup(page_handle pageh) { - struct gopher_request *req = (struct gopher_request *)cookie; + struct gopher_request *req; + struct page *page; - if (req->tcp_stream) { - _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + HLock(pageh); + page = *pageh; + req = (struct gopher_request *)(page->handler_cookie); + + if (req) { + if (req->tcp_stream) { + _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + } + + xfree(&page->handler_cookie); } - xfree(&req->uri); - xfree(&req); + HUnlock(pageh); } bool -gopher_process_request(void *cookie) +gopher_process(page_handle pageh) { - struct gopher_request *req = (struct gopher_request *)cookie; - size_t len, n; + struct gopher_request *req; + struct page *page; + size_t size, len, n; short err; unsigned short slen; bool ret; + HLock(pageh); + page = *pageh; + + if (page->handler_cookie == NULL) + goto parse; + + req = (struct gopher_request *)(page->handler_cookie); + if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { - BROWSER_DEBUGF((req->browser, "TCP I/O Result %d, disconnecting", + BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting", req->tcp_iopb.ioResult)); - return false; + goto finish_request; } - if (req->response_len < sizeof(req->response) && - !req->tcp_done_reading) { - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, - &req->tcp_status_pb, NULL, NULL, false); - if (err) { - browser_statusf(req->browser, "Error: Bad TCPStatus: %d", err); + err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, + NULL, NULL, false); + if (err) { + browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err); + goto finish_request; + } + + if (req->tcp_status_pb.connectionState != + ConnectionStateEstablished) { + BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)", + req->tcp_status_pb.connectionState)); + goto finish_request; + } + if (req->tcp_status_pb.amtUnreadData == 0) + goto parse; + + if (page->content_len + req->tcp_status_pb.amtUnreadData >= + page->content_size) { + if (!browser_grow_page_content(pageh, + req->tcp_status_pb.amtUnreadData)) return false; - } - if (req->tcp_status_pb.connectionState != - ConnectionStateEstablished) { - BROWSER_DEBUGF((req->browser, "TCP connection closed " - "(state %d)", req->tcp_status_pb.connectionState)); - req->tcp_done_reading = true; - goto parse; - } + HLock(pageh); + page = *pageh; + req = (struct gopher_request *)(page->handler_cookie); + } + + slen = req->tcp_status_pb.amtUnreadData; + err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, + (Ptr)(page->content + page->content_len), &slen, NULL, NULL, false); + if (err) { + browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err); + goto finish_request; + } + page->content_len += slen; + browser_statusf(page->browser, "Read %ld bytes", page->content_len); - if (req->tcp_status_pb.amtUnreadData == 0) - goto parse; - - slen = MIN(req->tcp_status_pb.amtUnreadData, - sizeof(req->response) - req->response_len); - if (!slen) { - browser_statusf(req->browser, - "Error: No buffer space available in response?"); - return false; - } - - err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, - (Ptr)(req->response + req->response_len), &slen, NULL, NULL, - false); - if (err) { - browser_statusf(req->browser, "Error: Failed TCPRcv: %d", err); - goto parse; - } - req->response_len += slen; - req->total_response_len += slen; - BROWSER_DEBUGF((req->browser, "Read %d bytes of TCP data", slen)); - browser_statusf(req->browser, - "Read %ld bytes", req->total_response_len); + if (page->content_len >= 3 && + (page->content_len == 3 || + page->content[page->content_len - 4] == '\n') && + page->content[page->content_len - 3] == '.' && + page->content[page->content_len - 2] == '\r' && + page->content[page->content_len - 1] == '\n') { + /* ".\r\n" is end of transfer */ + page->content_len -= 3; + goto finish_request; } + goto parse; + +finish_request: + gopher_cleanup(pageh); + parse: - if (req->response_len) { - TVAutoCalc(req->browser->output_tv, false); - ret = parse_response(req); - TVAutoCalc(req->browser->output_tv, true); - TVCalcLines(req->browser->output_tv); - TVUpdate(req->browser->output_tv); - TVUpdateScrollbar(req->browser->output_tv, - req->browser->output_tv_scroller); - if (ret == false) - browser_statusf(req->browser, - "Finished parsing %ld bytes", req->total_response_len); - return ret; + HLock(pageh); + page = *pageh; + + if (page->content_pos == page->content_len) { + if (!page->handler_cookie) { + /* nothing left to do */ + return false; + } + HUnlock(pageh); + return true; } - return true; + TVAutoCalc(page->browser->output_tv, false); + ret = parse_content(page); + TVAutoCalc(page->browser->output_tv, true); + TVCalcLines(page->browser->output_tv); + TVUpdate(page->browser->output_tv); + TVUpdateScrollbar(page->browser->output_tv, + page->browser->output_tv_scroller); + + if (ret) { + HUnlock(pageh); + return true; + } + + browser_statusf(page->browser, "Finished loading %ld bytes", + page->content_len); + + /* release some memory if we can */ + if (page->content_size > page->content_len) { + size = page->content_len; + page->content_size = size; + HUnlock(pageh); + SetHandleSize(pageh, sizeof(struct page) + size); + } + +request_fail: + HUnlock(pageh); + return false; } static bool -parse_response(struct gopher_request *req) +parse_content(struct page *page) { long n; - if (req->type == '1') { + if (page->type[0] == '1') { /* gopher menu */ -parse_menu: - for (n = 0; n < req->response_len; n++) { - if (n > 0 && req->response[n - 1] == '\r' && - req->response[n] == '\n') { + for (n = page->content_pos; n < page->content_len; n++) { + if (n > 0 && page->content[n - 1] == '\r' && + page->content[n] == '\n') { /* print line */ - req->response[n - 1] = '\0'; - if (req->response[0] == '.' && n == 2 && - req->response_len - n == 1) - /* ".\r\n" is end of transfer */ - req->tcp_done_reading = true; - else - gopher_print_menu(req, n + 1); - consume_response(req, n + 1); - goto parse_menu; + gopher_print_menu(page, page->content + + page->content_pos, n - page->content_pos); + page->content_pos = n + 1; } - - /* otherwise leave data in the buffer */ } - if (req->response_len == sizeof(req->response)) { - /* just dump it so we can move on */ - gopher_print_menu(req, req->response_len); - consume_response(req, req->response_len); + + if (!page->handler_cookie && + page->content_pos < page->content_len) { + gopher_print_menu(page, page->content + page->content_pos, + page->content_len - page->content_pos); + page->content_pos = page->content_len; + return false; } - } else if (req->type == '0') { + } else if (page->type[0] == '0') { /* text file, convert newlines and display */ -find_lines: - for (n = 0; n < req->response_len; n++) { - if (n > 0 && req->response[n - 1] == '\r' && - req->response[n] == '\n') { - /* \r\n -> \r */ - if (req->response[0] == '.' && n == 2 && - req->response_len - n == 1) - /* ".\r\n" is end of transfer */ - req->tcp_done_reading = true; - else - browser_print(req->browser, req->response, n); - consume_response(req, n + 1); - goto find_lines; - } else if (n > 0 && req->response[n - 1] == '\r') { - /* lone \r */ - browser_print(req->browser, req->response, n); - consume_response(req, n); - goto find_lines; - } else if (n > 0 && req->response[n] == '\n') { + for (n = page->content_pos; n < page->content_len; n++) { + if (page->content[n] == '\r') { + browser_print(page->browser, + page->content + page->content_pos, + n - page->content_pos + 1); + page->content_pos = n + 1; + if (page->content[n + 1] == '\n') { + page->content_pos++; + n++; + } + } else if (page->content[n] == '\n') { /* lone \n */ - req->response[n] = '\r'; - browser_print(req->browser, req->response, n); - consume_response(req, n); - goto find_lines; + browser_print(page->browser, + page->content + page->content_pos, + n - page->content_pos); + page->content_pos = n + 1; } - /* otherwise leave data in the buffer */ } - if (req->response_len == sizeof(req->response)) { - /* gosh this is one long line */ - browser_print(req->browser, req->response, req->response_len); - consume_response(req, req->response_len); + + if (!page->handler_cookie && + page->content_pos < page->content_len) { + browser_print(page->browser, page->content + page->content_pos, + page->content_len - page->content_pos); + page->content_pos = page->content_len; + return false; } } else { /* anything else, just download it */ - browser_print(req->browser, req->response, req->response_len); - consume_response(req, req->response_len); + browser_print(page->browser, page->content + page->content_pos, + page->content_len - page->content_pos); + page->content_pos = page->content_len; } - - return true; + + if (page->handler_cookie) + return true; + + return false; } static void -consume_response(struct gopher_request *req, size_t len) +gopher_print_menu(struct page *page, char *line, size_t len) { - if (len == req->response_len) { - req->response_len = 0; - memset(req->response, 0, sizeof(req->response)); - } else { - memmove(req->response, req->response + len, - sizeof(req->response) - len); - req->response_len -= len; - memset(req->response + req->response_len, 0, - sizeof(req->response) - req->response_len); - } -} - -static void -gopher_print_menu(struct gopher_request *req, size_t len) -{ - static char uri[member_size(struct URI, str)], prefix[5]; + static char uri[URI_MAX_STR_LEN + 1], prefix[5]; char type, *label, *selector = NULL, *hostname = NULL; unsigned short port = 0; long n; - type = req->response[0]; - label = req->response + 1; - + type = line[0]; + label = line + 1; + for (n = 1; n < len; n++) { - if (req->response[n] != '\t') + if (line[n] != '\t') continue; - req->response[n] = '\0'; + line[n] = '\0'; if (selector == NULL) - selector = req->response + n + 1; + selector = line + n + 1; else if (hostname == NULL) - hostname = req->response + n + 1; + hostname = line + n + 1; else { - port = atoi(req->response + n + 1); + port = atoi(line + n + 1); break; } } - req->browser->style = STYLE_PRE; + page->browser->style = STYLE_PRE; switch (type) { case '0': @@ -382,27 +421,32 @@ gopher_print_menu(struct gopher_request *req, size_t l /* document */ snprintf(prefix, sizeof(prefix), "[%c] ", type); - browser_print(req->browser, prefix, 4); + browser_print(page->browser, prefix, 4); len = snprintf(uri, sizeof(uri), "gopher://%s/%c%s", hostname, type, selector); - browser_print_link(req->browser, uri, len, label, strlen(label)); - browser_print(req->browser, "\r", 1); + browser_print_link(page->browser, uri, len, label, strlen(label)); + browser_print(page->browser, "\r", 1); break; case 'i': /* informational */ - browser_print(req->browser, label, strlen(label)); - browser_print(req->browser, "\r", 1); + browser_print(page->browser, label, strlen(label)); + browser_print(page->browser, "\r", 1); break; default: - browser_print(req->browser, "type: ", 0); - browser_print(req->browser, (char *)&type, 1); - browser_print(req->browser, " label:", 0); - browser_print(req->browser, label, 0); - browser_print(req->browser, " selector:", 0); - browser_print(req->browser, selector, 0); - browser_print(req->browser, " host:", 0); - browser_print(req->browser, hostname, 0); - browser_print(req->browser, "\r", 1); + browser_print(page->browser, "type: ", 0); + browser_print(page->browser, (char *)&type, 1); + //browser_print(page->browser, " label:", 0); + //browser_print(page->browser, label, 0); + //browser_print(page->browser, " selector:", 0); + //browser_print(page->browser, selector, 0); + //browser_print(page->browser, " host:", 0); + //browser_print(page->browser, hostname, 0); + browser_print(page->browser, "\r", 1); + } + + for (n = 1; n < len; n++) { + if (line[n] == '\0') + line[n] = '\t'; } } \ No newline at end of file --- main.c Mon Oct 28 09:36:29 2024 +++ main.c Sat Nov 2 16:44:31 2024 @@ -18,11 +18,12 @@ #include <string.h> #include "detritus.h" +#include "browser.h" #include "focusable.h" #include "tcp.h" #include "util.h" -MenuHandle apple_menu, file_menu, edit_menu; +MenuHandle apple_menu, file_menu, edit_menu, bookmarks_menu; bool quitting = false; bool handle_menu(long menu_id); @@ -58,7 +59,14 @@ main(void) AddResMenu(apple_menu, 'DRVR'); file_menu = GetMHandle(FILE_MENU_ID); edit_menu = GetMHandle(EDIT_MENU_ID); + bookmarks_menu = GetMHandle(BOOKMARKS_MENU_ID); menu_defaults(); + +AppendMenu(bookmarks_menu, "\p."); +SetItem(bookmarks_menu, 1, "\pgemini://geminiprotocol.net/"); +AppendMenu(bookmarks_menu, "\p."); +SetItem(bookmarks_menu, 2, "\pgopher://gopher.floodgap.com/"); + DrawMenuBar(); progress("Finding BlueSCSI TLS Accelerator..."); @@ -210,7 +218,16 @@ handle_menu(long menu_id) break; } break; + case BOOKMARKS_MENU_ID: { + Str255 uri; + struct browser *browser = (struct browser *)focused->cookie; + + GetItem(bookmarks_menu, item, uri); + PtoCstr(uri); + browser_go_uri(browser,(char *)uri); + break; } + } handled: HiliteMenu(0); @@ -233,51 +250,168 @@ handle_exit(void) } struct URI * -generic_parse_uri(char *protocol, char *uristr) +parse_uri(char *uristr, char *restrict_protocol) { + static char protocol[URI_MAX_PROTOCOL_LEN + 1]; + static char hostname[URI_MAX_HOSTNAME_LEN + 1]; + static char path[URI_MAX_PATH_LEN + 1]; + static char str[URI_MAX_STR_LEN + 1]; struct URI *uri; + char *data; + size_t protocol_len, hostname_len, path_len, str_len, size; short count; + unsigned short port; - uri = xmalloczero(sizeof(struct URI)); - if (uri == NULL) - return NULL; - /* protocol://host/path */ if (count = 0, sscanf(uristr, - "%" STR(URI_PROTOCOL_LEN) "[^:]://%" STR(URI_HOSTNAME_LEN) "[^/]%" - STR(URI_PATH_LEN) "s%n", - &uri->protocol, &uri->hostname, &uri->path, &count) == 3 && - count > 10) + "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" + STR(URI_MAX_HOSTNAME_LEN) "[^/]%" + STR(URI_MAX_PATH_LEN) "s%n", + &protocol, &hostname, &path, &count) == 3 && count > 10) goto parse_ok; /* protocol://host/ */ if (count = 0, sscanf(uristr, - "%" STR(URI_PROTOCOL_LEN) "[^:]://%" STR(URI_HOSTNAME_LEN) "[^/]/%n", - &uri->protocol, &uri->hostname, &count) == 2 && count > 10) { - uri->path[0] = '/'; - uri->path[1] = '\0'; + "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" + STR(URI_MAX_HOSTNAME_LEN) "[^/]/%n", + &protocol, &hostname, &count) == 2 && count > 10) { + path[0] = '/'; + path[1] = '\0'; goto parse_ok; } /* gemini://host */ if (count = 0, sscanf(uristr, - "%" STR(URI_PROTOCOL_LEN) "[^:]://%" STR(URI_HOSTNAME_LEN) "[^/]%n", - &uri->protocol, &uri->hostname, &count) == 2 && count > 10) { - uri->path[0] = '/'; - uri->path[1] = '\0'; + "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" + STR(URI_MAX_HOSTNAME_LEN) "[^/]%n", + &protocol, &hostname, &count) == 2 && count > 10) { + path[0] = '/'; + path[1] = '\0'; goto parse_ok; } /* failed */ - xfree(&uri); return NULL; parse_ok: - if (protocol != NULL && strcasecmp(protocol, uri->protocol) != 0) { - xfree(&uri); + if (restrict_protocol != NULL && + strcasecmp(restrict_protocol, protocol) != 0) return NULL; - } - snprintf(uri->str, sizeof(uri->str), "%s://%s%s", + + protocol_len = strlen(protocol); + hostname_len = strlen(hostname); + path_len = strlen(path); + str_len = protocol_len + 3 + hostname_len + path_len; + + size = sizeof(struct URI) + protocol_len + 1 + hostname_len + 1 + + path_len + 1 + str_len + 1; + uri = xmalloc(size); + if (uri == NULL) + return NULL; + + uri->port = 0; + + data = (char *)uri + sizeof(struct URI); + + uri->protocol = data; + memcpy(uri->protocol, protocol, protocol_len); + uri->protocol[protocol_len] = '\0'; + data += protocol_len + 1; + + uri->hostname = data; + memcpy(uri->hostname, hostname, hostname_len); + uri->hostname[hostname_len] = '\0'; + data += hostname_len + 1; + + uri->path = data; + memcpy(uri->path, path, path_len); + uri->path[path_len] = '\0'; + data += path_len + 1; + + uri->str = data; + data += snprintf(uri->str, str_len + 1, "%s://%s%s", uri->protocol, uri->hostname, uri->path); + data++; + + if (data > (char *)uri + size) + panic("URI overflow"); + return uri; +} + +struct URI * +build_relative_uri(struct URI *uri, char *relative, size_t len) +{ + static char path[URI_MAX_PATH_LEN + 1]; + static char str[URI_MAX_STR_LEN + 1]; + size_t slen, plen, n; + + /* http://a.b/c + //d/e -> http://d/e */ + if (relative[0] == '/' && relative[1] == '/') { + /* retain protocol, new host and path */ + slen = snprintf(str, sizeof(str), "%s://", uri->protocol); + while (slen < sizeof(str) - 1 && len) { + str[slen - 1] = *relative++; + slen++; + } + str[slen] = '\0'; + + return parse_uri(str, NULL); + } + + /* http://a.b/c + /d/e -> http://a.b/d/e */ + if (relative[0] == '/') { + /* retain protocol and host, new path */ + slen = snprintf(str, sizeof(str), "%s://%s", uri->protocol, + uri->hostname); + while (slen < sizeof(str) - 1 && len) { + str[slen] = *relative++; + slen++; + } + str[slen] = '\0'; + + return parse_uri(str, NULL); + } + + for (n = 0; n <= len; n++) { + /* http://a.b/c + gemini://d/e -> gemini://d/e */ + if (n < len - 2 && relative[n] == ':' && relative[n + 1] == '/' && + relative[n + 2] == '/') { + /* protocol found, this isn't relative */ + if (len >= sizeof(str)) + len = sizeof(str) - 1; + memcpy(str, relative, len); + str[len] = '\0'; + return parse_uri(str, NULL); + } + + /* http://a.b/c/d.html + e/f.html -> http://a.b/c/e/f.html */ + if (relative[n] == '/' || n == len) { + /* remove last component in uri path up to slash */ + plen = strlcpy(path, uri->path, sizeof(path)); + while (plen && path[plen - 1] != '/') { + path[plen - 1] = '\0'; + plen--; + } + + if (plen == 0) { + path[0] = '/'; + path[1] = '\0'; + plen = 1; + } + + if (plen >= sizeof(path) - 1) + plen = sizeof(path) - 2; + + memcpy(path + plen, relative, len); + path[plen + len] = '\0'; + snprintf(str, sizeof(str), "%s://%s%s", uri->protocol, + uri->hostname, path); + return parse_uri(str, NULL); + } + } + + /* what the heck is this? */ + Debugger(); + return parse_uri(relative, NULL); } --- util.c Tue Oct 1 20:15:14 2024 +++ util.c Sat Oct 26 21:10:37 2024 @@ -1622,7 +1622,8 @@ TEGetWidth(short off, TEHandle te) #define ceildiv(a,b) ((a / b) + (!!(a % b))) void -UpdateScrollbarForTE(GrafPtr win, ControlHandle control, TEHandle te, bool reset) +UpdateScrollbarForTE(GrafPtr win, ControlHandle control, TEHandle te, + bool reset) { size_t vlines, telines; TERec *ter; @@ -1985,14 +1986,21 @@ RestoreHiddenMenuBar(void) bool CommandPeriodPressed(void) { - EventRecord event; + KeyMap km; + bool cmd_down; + GetKeys(&km); + cmd_down = (km[1] & (1 << 15)) != 0; + if (cmd_down) + ;//Debugger(); +#if 0 if (!GetNextEvent(keyDownMask | autoKeyMask, &event)) return false; if ((event.modifiers & cmdKey) && ((event.message & charCodeMask) == '.')) return true; +#endif return false; }