💾 Archived View for 54.203.8.106 › Gemini.Mod captured on 2022-07-16 at 13:38:38.

View Raw

More Information

-=-=-=-=-=-=-

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.