summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gemini.h8
-rw-r--r--src/gmdocument.c32
-rw-r--r--src/gmdocument.h6
-rw-r--r--src/gmrequest.c11
-rw-r--r--src/gmutil.c5
-rw-r--r--src/ui/documentwidget.c69
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. */
6enum iGmStatusCode { 6enum 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
28struct Impl_GmDocument { 28struct 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
52static enum iGmLineType lineType_Rangecc_(const iRangecc *line) { 53static 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
351void init_GmDocument(iGmDocument *d) { 364void 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
383void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) {
384 d->format = format;
385}
386
369void setWidth_GmDocument(iGmDocument *d, int width) { 387void 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
432void setHost_GmDocument(iGmDocument *d, const iString *host) { 452void 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);
30iDeclareClass(GmDocument) 30iDeclareClass(GmDocument)
31iDeclareObjectConstruction(GmDocument) 31iDeclareObjectConstruction(GmDocument)
32 32
33enum iGmDocumentFormat {
34 gemini_GmDocumentFormat,
35 plainText_GmDocumentFormat,
36};
37
38void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format);
33void setWidth_GmDocument (iGmDocument *, int width); 39void setWidth_GmDocument (iGmDocument *, int width);
34void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ 40void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */
35void setSource_GmDocument (iGmDocument *, const iString *source, int width); 41void 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
194static 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
194static void updateSource_DocumentWidget_(iDocumentWidget *d) { 220static 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
320static 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
343static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { 362static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) {
344 if (!d->request) { 363 if (!d->request) {
345 return; 364 return;