From 0700048de99c4a00094d594dcf321637514f3a6a Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 28 Jul 2020 09:00:05 +0300 Subject: Plaintext documents; unsupported MIME type error --- src/gemini.h | 8 +++--- src/gmdocument.c | 32 ++++++++++++++++++----- src/gmdocument.h | 6 +++++ src/gmrequest.c | 11 +++++++- src/gmutil.c | 5 ++++ src/ui/documentwidget.c | 69 +++++++++++++++++++++++++++++++------------------ 6 files changed, 96 insertions(+), 35 deletions(-) diff --git a/src/gemini.h b/src/gemini.h index ffe269df..863e27f7 100644 --- a/src/gemini.h +++ b/src/gemini.h @@ -4,9 +4,11 @@ /* Response status codes. */ enum iGmStatusCode { - invalidRedirect_GmStatusCode = -3, - invalidHeader_GmStatusCode = -2, - failedToOpenFile_GmStatusCode = -1, + clientSide_GmStatusCode = -100, /* clientside status codes */ + invalidRedirect_GmStatusCode, + invalidHeader_GmStatusCode, + unsupportedMimeType_GmStatusCode, + failedToOpenFile_GmStatusCode, none_GmStatusCode = 0, input_GmStatusCode = 10, sensitiveInput_GmStatusCode = 11, diff --git a/src/gmdocument.c b/src/gmdocument.c index 5389aac7..1a8312f0 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -27,6 +27,7 @@ iDefineTypeConstruction(GmLink) struct Impl_GmDocument { iObject object; + enum iGmDocumentFormat format; iString source; iString localHost; iInt2 size; @@ -49,7 +50,10 @@ enum iGmLineType { max_GmLineType, }; -static enum iGmLineType lineType_Rangecc_(const iRangecc *line) { +static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc *line) { + if (d->format == plainText_GmDocumentFormat) { + return text_GmLineType; + } if (isEmpty_Range(line)) { return text_GmLineType; } @@ -207,6 +211,10 @@ static void doLayout_GmDocument_(iGmDocument *d) { iBool isPreformat = iFalse; iBool isFirstText = iTrue; int preFont = preformatted_FontId; + if (d->format == plainText_GmDocumentFormat) { + isPreformat = iTrue; + isFirstText = iFalse; + } while (nextSplit_Rangecc(&content, "\n", &line)) { iGmRun run; run.color = white_ColorId; @@ -214,7 +222,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { enum iGmLineType type; int indent = 0; if (!isPreformat) { - type = lineType_Rangecc_(&line); + type = lineType_GmDocument_(d, &line); indent = indents[type]; if (type == preformatted_GmLineType) { isPreformat = iTrue; @@ -242,7 +250,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { else { /* Preformatted line. */ type = preformatted_GmLineType; - if (startsWithSc_Rangecc(&line, "```", &iCaseSensitive)) { + if (d->format == gemini_GmDocumentFormat && + startsWithSc_Rangecc(&line, "```", &iCaseSensitive)) { isPreformat = iFalse; preAltText = iNullRange; continue; @@ -254,6 +263,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { if (isEmpty_Range(&line)) { pos.y += lineHeight_Text(run.font); prevType = text_GmLineType; + /* TODO: Extra skip needed here? */ continue; } /* Check the margin vs. previous run. */ @@ -304,6 +314,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { pushBack_Array(&d->layout, &run); } run.color = colors[type]; + if (d->format == plainText_GmDocumentFormat) { + run.color = colors[text_GmLineType]; + } /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ if (type == text_GmLineType && isFirstText) { run.font = firstParagraph_FontId; @@ -349,6 +362,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { } void init_GmDocument(iGmDocument *d) { + d->format = gemini_GmDocumentFormat; init_String(&d->source); init_String(&d->localHost); d->size = zero_I2(); @@ -366,6 +380,10 @@ void deinit_GmDocument(iGmDocument *d) { deinit_String(&d->source); } +void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { + d->format = format; +} + void setWidth_GmDocument(iGmDocument *d, int width) { d->size.x = width; doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ @@ -380,6 +398,9 @@ static void normalize_GmDocument(iGmDocument *d) { iRangecc src = range_String(&d->source); iRangecc line = iNullRange; iBool isPreformat = iFalse; + if (d->format == plainText_GmDocumentFormat) { + isPreformat = iTrue; /* Cannot be turned off. */ + } const int preTabWidth = 8; /* TODO: user-configurable parameter */ while (nextSplit_Rangecc(&src, "\n", &line)) { if (isPreformat) { @@ -397,12 +418,12 @@ static void normalize_GmDocument(iGmDocument *d) { } } appendCStr_String(normalized, "\n"); - if (lineType_Rangecc_(&line) == preformatted_GmLineType) { + if (lineType_GmDocument_(d, &line) == preformatted_GmLineType) { isPreformat = iFalse; } continue; } - if (lineType_Rangecc_(&line) == preformatted_GmLineType) { + if (lineType_GmDocument_(d, &line) == preformatted_GmLineType) { isPreformat = iTrue; appendRange_String(normalized, line); appendCStr_String(normalized, "\n"); @@ -426,7 +447,6 @@ static void normalize_GmDocument(iGmDocument *d) { appendCStr_String(normalized, "\n"); } set_String(&d->source, collect_String(normalized)); -// printf("normalized:\n%s\n", cstr_String(&d->source)); } void setHost_GmDocument(iGmDocument *d, const iString *host) { diff --git a/src/gmdocument.h b/src/gmdocument.h index ebe0ed50..a97af029 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -30,6 +30,12 @@ const char * findLoc_GmRun (const iGmRun *, iInt2 pos); iDeclareClass(GmDocument) iDeclareObjectConstruction(GmDocument) +enum iGmDocumentFormat { + gemini_GmDocumentFormat, + plainText_GmDocumentFormat, +}; + +void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); void setWidth_GmDocument (iGmDocument *, int width); void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ void setSource_GmDocument (iGmDocument *, const iString *source, int width); diff --git a/src/gmrequest.c b/src/gmrequest.c index e05679bd..38702edb 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c @@ -182,8 +182,17 @@ void submit_GmRequest(iGmRequest *d) { iFile * f = new_File(path); if (open_File(f, readOnly_FileMode)) { /* TODO: Check supported file types: images, audio */ + /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ d->code = success_GmStatusCode; - setCStr_String(&d->header, "text/gemini; charset=utf-8"); + if (endsWithCase_String(path, ".gmi")) { + setCStr_String(&d->header, "text/gemini; charset=utf-8"); + } + else if (endsWithCase_String(path, ".txt")) { + setCStr_String(&d->header, "text/plain"); + } + else { + setCStr_String(&d->header, "application/octet-stream"); + } set_Block(&d->body, collect_Block(readAll_File(f))); iNotifyAudience(d, updated, GmRequestUpdated); } diff --git a/src/gmutil.c b/src/gmutil.c index 0270c2a7..dbaf8759 100644 --- a/src/gmutil.c +++ b/src/gmutil.c @@ -45,6 +45,11 @@ const iGmError *get_GmError(enum iGmStatusCode code) { "Failed to Open File", "The requested file does not exist or is inaccessible. " "Please check the file path." } }, + { unsupportedMimeType_GmStatusCode, + { 0x1f47d, /* alien */ + "Unsupported MIME Type", + "The received content is in an unsupported format and cannot be viewed with " + "this application." } }, { invalidHeader_GmStatusCode, { 0x1f4a9, /* pile of poo */ "Invalid Header", diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index d78a9238..882e029a 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -191,13 +191,55 @@ static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) refresh_Widget(as_Widget(d)); } +static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code) { + iString *src = collectNew_String(); + const iGmError *msg = get_GmError(code); + format_String(src, + "# %lc %s\n%s", + msg->icon ? msg->icon : 0x2327, /* X in a box */ + msg->title, + msg->info); + switch (code) { + case failedToOpenFile_GmStatusCode: + case certificateNotValid_GmStatusCode: + appendFormat_String(src, "\n\n%s", cstr_String(meta_GmRequest(d->request))); + break; + case unsupportedMimeType_GmStatusCode: + appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta_GmRequest(d->request))); + break; + case slowDown_GmStatusCode: + appendFormat_String(src, "\n\nWait %s seconds before your next request.", + cstr_String(meta_GmRequest(d->request))); + break; + default: + break; + } + setSource_DocumentWidget_(d, src); +} + static void updateSource_DocumentWidget_(iDocumentWidget *d) { /* TODO: Do this in the background. However, that requires a text metrics calculator that does not try to cache the glyph bitmaps. */ - if (status_GmRequest(d->request) != input_GmStatusCode && - status_GmRequest(d->request) != sensitiveInput_GmStatusCode) { + const enum iGmStatusCode statusCode = status_GmRequest(d->request); + if (statusCode != input_GmStatusCode && + statusCode != sensitiveInput_GmStatusCode) { iString str; initBlock_String(&str, body_GmRequest(d->request)); + if (statusCode == success_GmStatusCode) { + /* Check the MIME type. */ + const iString *mime = meta_GmRequest(d->request); + if (startsWith_String(mime, "text/plain")) { + setFormat_GmDocument(d->doc, plainText_GmDocumentFormat); + } + else if (startsWith_String(mime, "text/gemini")) { + setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); + } + else { + showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode); + deinit_String(&str); + return; + } + } setSource_DocumentWidget_(d, &str); deinit_String(&str); } @@ -317,29 +359,6 @@ static const iString *absoluteUrl_DocumentWidget_(const iDocumentWidget *d, cons return collect_String(absolute); } -static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code) { - iString *src = collectNew_String(); - const iGmError *msg = get_GmError(code); - format_String(src, - "# %lc %s\n%s", - msg->icon ? msg->icon : 0x2327, /* X in a box */ - msg->title, - msg->info); - switch (code) { - case failedToOpenFile_GmStatusCode: - case certificateNotValid_GmStatusCode: - appendFormat_String(src, "\n\n%s", cstr_String(meta_GmRequest(d->request))); - break; - case slowDown_GmStatusCode: - appendFormat_String(src, "\n\nWait %s seconds before your next request.", - cstr_String(meta_GmRequest(d->request))); - break; - default: - break; - } - setSource_DocumentWidget_(d, src); -} - static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { if (!d->request) { return; -- cgit v1.2.3