💾 Archived View for 54.203.8.106 › Gemini.Mod captured on 2022-07-16 at 13:38:38.
-=-=-=-=-=-=-
MODULE Gemini; (* uses nested procedures, all of which could be in module scope instead. *) IMPORT Files, IO, OS; CONST GeminiURL = "gemini://54.203.8.106"; LogFile = "gemini.log"; NL = 0AX; VAR F: Files.File; R: Files.Rider; N: Files.File; PROCEDURE P(VAR str: ARRAY OF CHAR; t: INTEGER); BEGIN str[2] := 0X; str[1] := CHR(t MOD 10 + ORD("0")); t := t DIV 10; str[0] := CHR(t MOD 10 + ORD("0")) END P; PROCEDURE WriteString(s: ARRAY OF CHAR); VAR i: INTEGER; BEGIN i := 0; WHILE (i < LEN(s)) & (s[i] # 0X) DO Files.Write(R, s[i]); INC(i) END END WriteString; PROCEDURE LogMeta; VAR t: INTEGER; buf: ARRAY 20H OF CHAR; d: ARRAY 6 OF INTEGER; BEGIN buf[0] := 0X; OS.Getenv("NCAT_REMOTE_ADDR", buf); WriteString(buf); WriteString(" "); t := Files.Date(N); d[5] := t MOD 40H; t := t DIV 40H; d[4] := t MOD 40H; t := t DIV 40H; d[3] := t MOD 20H; t := t DIV 20H; d[2] := t MOD 20H; t := t DIV 20H; d[1] := t MOD 10H; t := t DIV 10H; d[0] := t; INC(d[1]); (* month *) buf[2] := 0X; FOR t := 0 TO 5 DO P(buf, d[t]); WriteString(buf); END; WriteString(" ") END LogMeta; PROCEDURE Log(str: ARRAY OF CHAR; newline: BOOLEAN); VAR t: INTEGER; tstamp: ARRAY 20 OF CHAR; BEGIN WriteString(str); IF newline THEN Files.Write(R, NL) END END Log; PROCEDURE xEmit(s: ARRAY OF CHAR); BEGIN IO.WriteString(s); IO.WriteLn END xEmit; PROCEDURE Emit(s: ARRAY OF CHAR; newline: BOOLEAN); BEGIN IO.WriteString(s); Log(s, newline) END Emit; PROCEDURE Serve*; CONST UrlLen = 1024; VAR request: ARRAY UrlLen + 1 OF CHAR; PROCEDURE StartsWith(this, that: ARRAY OF CHAR): BOOLEAN; VAR i: INTEGER; BEGIN i := 0; WHILE this[i] = that[i] DO INC(i) END; RETURN that[i] = 0X END StartsWith; PROCEDURE Dispatch(VAR request: ARRAY OF CHAR); VAR f: Files.File; i, j: INTEGER; fileName: ARRAY 32 + 1 OF CHAR; isGemini: BOOLEAN; where: INTEGER; PROCEDURE SkipToSlash(request: ARRAY OF CHAR; VAR i: INTEGER); BEGIN WHILE (request[i] # 0X) & (request[i] # "/") DO INC(i) END; IF request[i] # 0X THEN INC(i) END END SkipToSlash; PROCEDURE EndsWith(this, that: ARRAY OF CHAR): BOOLEAN; VAR i, j: INTEGER; yep: BOOLEAN; BEGIN i := 0; j := 0; WHILE this[i] # 0X DO INC(i) END; WHILE that[j] # 0X DO INC(j) END; REPEAT DEC(i); DEC(j) UNTIL (j < 0) OR (i < 0) OR (this[i] # that[j]); RETURN j < 0 END EndsWith; PROCEDURE Contains(this, that: ARRAY OF CHAR; VAR where: INTEGER): BOOLEAN; VAR i, j: INTEGER; yep: BOOLEAN; BEGIN i := 0; yep := FALSE; WHILE ~yep & (this[i] # 0X) DO j := 0; WHILE (that[j] # 0X) & (this[i+j] = that[j]) DO INC(j) END; IF that[j] = 0X THEN where := i; yep := TRUE ELSE INC(i) END END; RETURN yep END Contains; PROCEDURE EmitFile(f: Files.File); VAR r: Files.Rider; ch: CHAR; BEGIN Files.Set(r, f, 0); Files.Read(r, ch); WHILE ~r.eof DO IO.Write(ch); Files.Read(r, ch) END END EmitFile; BEGIN LogMeta; i := 0; FOR j := 1 TO 3 DO SkipToSlash(request, i) END; j := 0; WHILE (request[i] # 0X) & (request[i] # "/") & (j < 32) DO fileName[j] := request[i]; INC(i); INC(j) END; IF request[i] # 0X THEN f := NIL ELSE fileName[j] := 0X; IF fileName = "" THEN fileName := "powered.by.gmi" END; f := Files.Old(fileName) END; IF f = NIL THEN IF EndsWith(request, "/feedback") THEN Emit("10 Feedback? (privately logged)", TRUE) ELSIF Contains(request, "/feedback?", where) THEN request[where] := 0X; Emit("30 ", FALSE); Emit(request, TRUE) ELSE Emit("51 NO SUCH FILENAME", TRUE) END ELSIF fileName = LogFile THEN Emit("51 NO SUCH FILENAME", TRUE) ELSE isGemini := FALSE; IF EndsWith(fileName, ".gmi") THEN isGemini := TRUE; Emit("20 text/gemini; charset=UTF-8", TRUE) ELSIF EndsWith(fileName, ".txt") OR EndsWith(fileName, ".Mod") THEN Emit("20 text/plain; charset=US-ASCII", TRUE) ELSIF EndsWith(fileName, ".mp3") THEN Emit("20 audio/mpeg", TRUE) ELSE Emit("20 application/octet-stream", TRUE) END; IO.WriteEOL; EmitFile(f); IF isGemini THEN IO.WriteEOL; IO.WriteString(""); IO.WriteEOL; IO.WriteString("=> "); IO.WriteString(request); IO.WriteString("/feedback Feedback? Thanks!") END; Files.Close(f) END END Dispatch; BEGIN (* WHILE TRUE DO - if this didn't get restarted each time from smol-web *) F := Files.Old(LogFile); IF F = NIL THEN F := Files.New(LogFile) END; IF F # NIL THEN Files.Set(R, F, Files.Length(F)); IO.ReadLine(request); LogMeta; Log(request, TRUE); IF IO.Overflow THEN LogMeta; Emit("59 URL IS TOO LONG", TRUE) ELSIF StartsWith(request, GeminiURL) THEN Dispatch(request) ELSE LogMeta; Emit("59 BAD PROTOCOL", TRUE) END; IO.WriteEOL; Files.Register(F); Files.Close(F) END (* END *) END Serve; BEGIN N := Files.New("") END Gemini.