diff options
-rw-r--r-- | src/gemini.h | 8 | ||||
-rw-r--r-- | src/gmdocument.c | 32 | ||||
-rw-r--r-- | src/gmdocument.h | 6 | ||||
-rw-r--r-- | src/gmrequest.c | 11 | ||||
-rw-r--r-- | src/gmutil.c | 5 | ||||
-rw-r--r-- | 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 @@ | |||
4 | 4 | ||
5 | /* Response status codes. */ | 5 | /* Response status codes. */ |
6 | enum iGmStatusCode { | 6 | enum iGmStatusCode { |
7 | invalidRedirect_GmStatusCode = -3, | 7 | clientSide_GmStatusCode = -100, /* clientside status codes */ |
8 | invalidHeader_GmStatusCode = -2, | 8 | invalidRedirect_GmStatusCode, |
9 | failedToOpenFile_GmStatusCode = -1, | 9 | invalidHeader_GmStatusCode, |
10 | unsupportedMimeType_GmStatusCode, | ||
11 | failedToOpenFile_GmStatusCode, | ||
10 | none_GmStatusCode = 0, | 12 | none_GmStatusCode = 0, |
11 | input_GmStatusCode = 10, | 13 | input_GmStatusCode = 10, |
12 | sensitiveInput_GmStatusCode = 11, | 14 | 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) | |||
27 | 27 | ||
28 | struct Impl_GmDocument { | 28 | struct Impl_GmDocument { |
29 | iObject object; | 29 | iObject object; |
30 | enum iGmDocumentFormat format; | ||
30 | iString source; | 31 | iString source; |
31 | iString localHost; | 32 | iString localHost; |
32 | iInt2 size; | 33 | iInt2 size; |
@@ -49,7 +50,10 @@ enum iGmLineType { | |||
49 | max_GmLineType, | 50 | max_GmLineType, |
50 | }; | 51 | }; |
51 | 52 | ||
52 | static enum iGmLineType lineType_Rangecc_(const iRangecc *line) { | 53 | static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc *line) { |
54 | if (d->format == plainText_GmDocumentFormat) { | ||
55 | return text_GmLineType; | ||
56 | } | ||
53 | if (isEmpty_Range(line)) { | 57 | if (isEmpty_Range(line)) { |
54 | return text_GmLineType; | 58 | return text_GmLineType; |
55 | } | 59 | } |
@@ -207,6 +211,10 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
207 | iBool isPreformat = iFalse; | 211 | iBool isPreformat = iFalse; |
208 | iBool isFirstText = iTrue; | 212 | iBool isFirstText = iTrue; |
209 | int preFont = preformatted_FontId; | 213 | int preFont = preformatted_FontId; |
214 | if (d->format == plainText_GmDocumentFormat) { | ||
215 | isPreformat = iTrue; | ||
216 | isFirstText = iFalse; | ||
217 | } | ||
210 | while (nextSplit_Rangecc(&content, "\n", &line)) { | 218 | while (nextSplit_Rangecc(&content, "\n", &line)) { |
211 | iGmRun run; | 219 | iGmRun run; |
212 | run.color = white_ColorId; | 220 | run.color = white_ColorId; |
@@ -214,7 +222,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
214 | enum iGmLineType type; | 222 | enum iGmLineType type; |
215 | int indent = 0; | 223 | int indent = 0; |
216 | if (!isPreformat) { | 224 | if (!isPreformat) { |
217 | type = lineType_Rangecc_(&line); | 225 | type = lineType_GmDocument_(d, &line); |
218 | indent = indents[type]; | 226 | indent = indents[type]; |
219 | if (type == preformatted_GmLineType) { | 227 | if (type == preformatted_GmLineType) { |
220 | isPreformat = iTrue; | 228 | isPreformat = iTrue; |
@@ -242,7 +250,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
242 | else { | 250 | else { |
243 | /* Preformatted line. */ | 251 | /* Preformatted line. */ |
244 | type = preformatted_GmLineType; | 252 | type = preformatted_GmLineType; |
245 | if (startsWithSc_Rangecc(&line, "```", &iCaseSensitive)) { | 253 | if (d->format == gemini_GmDocumentFormat && |
254 | startsWithSc_Rangecc(&line, "```", &iCaseSensitive)) { | ||
246 | isPreformat = iFalse; | 255 | isPreformat = iFalse; |
247 | preAltText = iNullRange; | 256 | preAltText = iNullRange; |
248 | continue; | 257 | continue; |
@@ -254,6 +263,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
254 | if (isEmpty_Range(&line)) { | 263 | if (isEmpty_Range(&line)) { |
255 | pos.y += lineHeight_Text(run.font); | 264 | pos.y += lineHeight_Text(run.font); |
256 | prevType = text_GmLineType; | 265 | prevType = text_GmLineType; |
266 | /* TODO: Extra skip needed here? */ | ||
257 | continue; | 267 | continue; |
258 | } | 268 | } |
259 | /* Check the margin vs. previous run. */ | 269 | /* Check the margin vs. previous run. */ |
@@ -304,6 +314,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
304 | pushBack_Array(&d->layout, &run); | 314 | pushBack_Array(&d->layout, &run); |
305 | } | 315 | } |
306 | run.color = colors[type]; | 316 | run.color = colors[type]; |
317 | if (d->format == plainText_GmDocumentFormat) { | ||
318 | run.color = colors[text_GmLineType]; | ||
319 | } | ||
307 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ | 320 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ |
308 | if (type == text_GmLineType && isFirstText) { | 321 | if (type == text_GmLineType && isFirstText) { |
309 | run.font = firstParagraph_FontId; | 322 | run.font = firstParagraph_FontId; |
@@ -349,6 +362,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
349 | } | 362 | } |
350 | 363 | ||
351 | void init_GmDocument(iGmDocument *d) { | 364 | void init_GmDocument(iGmDocument *d) { |
365 | d->format = gemini_GmDocumentFormat; | ||
352 | init_String(&d->source); | 366 | init_String(&d->source); |
353 | init_String(&d->localHost); | 367 | init_String(&d->localHost); |
354 | d->size = zero_I2(); | 368 | d->size = zero_I2(); |
@@ -366,6 +380,10 @@ void deinit_GmDocument(iGmDocument *d) { | |||
366 | deinit_String(&d->source); | 380 | deinit_String(&d->source); |
367 | } | 381 | } |
368 | 382 | ||
383 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | ||
384 | d->format = format; | ||
385 | } | ||
386 | |||
369 | void setWidth_GmDocument(iGmDocument *d, int width) { | 387 | void setWidth_GmDocument(iGmDocument *d, int width) { |
370 | d->size.x = width; | 388 | d->size.x = width; |
371 | doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ | 389 | doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ |
@@ -380,6 +398,9 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
380 | iRangecc src = range_String(&d->source); | 398 | iRangecc src = range_String(&d->source); |
381 | iRangecc line = iNullRange; | 399 | iRangecc line = iNullRange; |
382 | iBool isPreformat = iFalse; | 400 | iBool isPreformat = iFalse; |
401 | if (d->format == plainText_GmDocumentFormat) { | ||
402 | isPreformat = iTrue; /* Cannot be turned off. */ | ||
403 | } | ||
383 | const int preTabWidth = 8; /* TODO: user-configurable parameter */ | 404 | const int preTabWidth = 8; /* TODO: user-configurable parameter */ |
384 | while (nextSplit_Rangecc(&src, "\n", &line)) { | 405 | while (nextSplit_Rangecc(&src, "\n", &line)) { |
385 | if (isPreformat) { | 406 | if (isPreformat) { |
@@ -397,12 +418,12 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
397 | } | 418 | } |
398 | } | 419 | } |
399 | appendCStr_String(normalized, "\n"); | 420 | appendCStr_String(normalized, "\n"); |
400 | if (lineType_Rangecc_(&line) == preformatted_GmLineType) { | 421 | if (lineType_GmDocument_(d, &line) == preformatted_GmLineType) { |
401 | isPreformat = iFalse; | 422 | isPreformat = iFalse; |
402 | } | 423 | } |
403 | continue; | 424 | continue; |
404 | } | 425 | } |
405 | if (lineType_Rangecc_(&line) == preformatted_GmLineType) { | 426 | if (lineType_GmDocument_(d, &line) == preformatted_GmLineType) { |
406 | isPreformat = iTrue; | 427 | isPreformat = iTrue; |
407 | appendRange_String(normalized, line); | 428 | appendRange_String(normalized, line); |
408 | appendCStr_String(normalized, "\n"); | 429 | appendCStr_String(normalized, "\n"); |
@@ -426,7 +447,6 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
426 | appendCStr_String(normalized, "\n"); | 447 | appendCStr_String(normalized, "\n"); |
427 | } | 448 | } |
428 | set_String(&d->source, collect_String(normalized)); | 449 | set_String(&d->source, collect_String(normalized)); |
429 | // printf("normalized:\n%s\n", cstr_String(&d->source)); | ||
430 | } | 450 | } |
431 | 451 | ||
432 | void setHost_GmDocument(iGmDocument *d, const iString *host) { | 452 | 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); | |||
30 | iDeclareClass(GmDocument) | 30 | iDeclareClass(GmDocument) |
31 | iDeclareObjectConstruction(GmDocument) | 31 | iDeclareObjectConstruction(GmDocument) |
32 | 32 | ||
33 | enum iGmDocumentFormat { | ||
34 | gemini_GmDocumentFormat, | ||
35 | plainText_GmDocumentFormat, | ||
36 | }; | ||
37 | |||
38 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); | ||
33 | void setWidth_GmDocument (iGmDocument *, int width); | 39 | void setWidth_GmDocument (iGmDocument *, int width); |
34 | void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ | 40 | void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ |
35 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 41 | 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) { | |||
182 | iFile * f = new_File(path); | 182 | iFile * f = new_File(path); |
183 | if (open_File(f, readOnly_FileMode)) { | 183 | if (open_File(f, readOnly_FileMode)) { |
184 | /* TODO: Check supported file types: images, audio */ | 184 | /* TODO: Check supported file types: images, audio */ |
185 | /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ | ||
185 | d->code = success_GmStatusCode; | 186 | d->code = success_GmStatusCode; |
186 | setCStr_String(&d->header, "text/gemini; charset=utf-8"); | 187 | if (endsWithCase_String(path, ".gmi")) { |
188 | setCStr_String(&d->header, "text/gemini; charset=utf-8"); | ||
189 | } | ||
190 | else if (endsWithCase_String(path, ".txt")) { | ||
191 | setCStr_String(&d->header, "text/plain"); | ||
192 | } | ||
193 | else { | ||
194 | setCStr_String(&d->header, "application/octet-stream"); | ||
195 | } | ||
187 | set_Block(&d->body, collect_Block(readAll_File(f))); | 196 | set_Block(&d->body, collect_Block(readAll_File(f))); |
188 | iNotifyAudience(d, updated, GmRequestUpdated); | 197 | iNotifyAudience(d, updated, GmRequestUpdated); |
189 | } | 198 | } |
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) { | |||
45 | "Failed to Open File", | 45 | "Failed to Open File", |
46 | "The requested file does not exist or is inaccessible. " | 46 | "The requested file does not exist or is inaccessible. " |
47 | "Please check the file path." } }, | 47 | "Please check the file path." } }, |
48 | { unsupportedMimeType_GmStatusCode, | ||
49 | { 0x1f47d, /* alien */ | ||
50 | "Unsupported MIME Type", | ||
51 | "The received content is in an unsupported format and cannot be viewed with " | ||
52 | "this application." } }, | ||
48 | { invalidHeader_GmStatusCode, | 53 | { invalidHeader_GmStatusCode, |
49 | { 0x1f4a9, /* pile of poo */ | 54 | { 0x1f4a9, /* pile of poo */ |
50 | "Invalid Header", | 55 | "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) | |||
191 | refresh_Widget(as_Widget(d)); | 191 | refresh_Widget(as_Widget(d)); |
192 | } | 192 | } |
193 | 193 | ||
194 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code) { | ||
195 | iString *src = collectNew_String(); | ||
196 | const iGmError *msg = get_GmError(code); | ||
197 | format_String(src, | ||
198 | "# %lc %s\n%s", | ||
199 | msg->icon ? msg->icon : 0x2327, /* X in a box */ | ||
200 | msg->title, | ||
201 | msg->info); | ||
202 | switch (code) { | ||
203 | case failedToOpenFile_GmStatusCode: | ||
204 | case certificateNotValid_GmStatusCode: | ||
205 | appendFormat_String(src, "\n\n%s", cstr_String(meta_GmRequest(d->request))); | ||
206 | break; | ||
207 | case unsupportedMimeType_GmStatusCode: | ||
208 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta_GmRequest(d->request))); | ||
209 | break; | ||
210 | case slowDown_GmStatusCode: | ||
211 | appendFormat_String(src, "\n\nWait %s seconds before your next request.", | ||
212 | cstr_String(meta_GmRequest(d->request))); | ||
213 | break; | ||
214 | default: | ||
215 | break; | ||
216 | } | ||
217 | setSource_DocumentWidget_(d, src); | ||
218 | } | ||
219 | |||
194 | static void updateSource_DocumentWidget_(iDocumentWidget *d) { | 220 | static void updateSource_DocumentWidget_(iDocumentWidget *d) { |
195 | /* TODO: Do this in the background. However, that requires a text metrics calculator | 221 | /* TODO: Do this in the background. However, that requires a text metrics calculator |
196 | that does not try to cache the glyph bitmaps. */ | 222 | that does not try to cache the glyph bitmaps. */ |
197 | if (status_GmRequest(d->request) != input_GmStatusCode && | 223 | const enum iGmStatusCode statusCode = status_GmRequest(d->request); |
198 | status_GmRequest(d->request) != sensitiveInput_GmStatusCode) { | 224 | if (statusCode != input_GmStatusCode && |
225 | statusCode != sensitiveInput_GmStatusCode) { | ||
199 | iString str; | 226 | iString str; |
200 | initBlock_String(&str, body_GmRequest(d->request)); | 227 | initBlock_String(&str, body_GmRequest(d->request)); |
228 | if (statusCode == success_GmStatusCode) { | ||
229 | /* Check the MIME type. */ | ||
230 | const iString *mime = meta_GmRequest(d->request); | ||
231 | if (startsWith_String(mime, "text/plain")) { | ||
232 | setFormat_GmDocument(d->doc, plainText_GmDocumentFormat); | ||
233 | } | ||
234 | else if (startsWith_String(mime, "text/gemini")) { | ||
235 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); | ||
236 | } | ||
237 | else { | ||
238 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode); | ||
239 | deinit_String(&str); | ||
240 | return; | ||
241 | } | ||
242 | } | ||
201 | setSource_DocumentWidget_(d, &str); | 243 | setSource_DocumentWidget_(d, &str); |
202 | deinit_String(&str); | 244 | deinit_String(&str); |
203 | } | 245 | } |
@@ -317,29 +359,6 @@ static const iString *absoluteUrl_DocumentWidget_(const iDocumentWidget *d, cons | |||
317 | return collect_String(absolute); | 359 | return collect_String(absolute); |
318 | } | 360 | } |
319 | 361 | ||
320 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code) { | ||
321 | iString *src = collectNew_String(); | ||
322 | const iGmError *msg = get_GmError(code); | ||
323 | format_String(src, | ||
324 | "# %lc %s\n%s", | ||
325 | msg->icon ? msg->icon : 0x2327, /* X in a box */ | ||
326 | msg->title, | ||
327 | msg->info); | ||
328 | switch (code) { | ||
329 | case failedToOpenFile_GmStatusCode: | ||
330 | case certificateNotValid_GmStatusCode: | ||
331 | appendFormat_String(src, "\n\n%s", cstr_String(meta_GmRequest(d->request))); | ||
332 | break; | ||
333 | case slowDown_GmStatusCode: | ||
334 | appendFormat_String(src, "\n\nWait %s seconds before your next request.", | ||
335 | cstr_String(meta_GmRequest(d->request))); | ||
336 | break; | ||
337 | default: | ||
338 | break; | ||
339 | } | ||
340 | setSource_DocumentWidget_(d, src); | ||
341 | } | ||
342 | |||
343 | static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | 362 | static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { |
344 | if (!d->request) { | 363 | if (!d->request) { |
345 | return; | 364 | return; |