--- /dev/null
+BSD 3-Clause License
+
+Copyright (c) 2016, Urban Wallasch
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+/*
+ * This file is part of the gogopherd project.
+ *
+ * Copyright 2019 Urban Wallasch <irrwahn35@freenet.de>
+ * See LICENSE file for more details.
+ *
+ */
+/*
+ * Gogopherd- a minimalistic gopher server written in Go.
+ *
+ * Changelog:
+ * 2019-04-03 Initial version.
+ */
+
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "os"
+ "strings"
+)
+
+var (
+ index []string
+ logger *log.Logger
+ tracer *log.Logger
+)
+
+var cfg = struct {
+ bindIface string
+ bindPortTcp string
+ fqdn string
+ docRoot string
+ indexFile string
+ verbose bool
+}{
+ bindIface: "localhost",
+ bindPortTcp: "7070",
+ fqdn: "localhost",
+ docRoot: ".",
+ indexFile: "index.gox",
+ verbose: false,
+}
+
+func initialize() {
+ flag.StringVar(&cfg.bindIface, "i", cfg.bindIface, "interface to bind to")
+ flag.StringVar(&cfg.bindPortTcp, "p", cfg.bindPortTcp, "TCP port to listen on")
+ flag.StringVar(&cfg.docRoot, "d", cfg.docRoot, "document root directory")
+ flag.StringVar(&cfg.indexFile, "x", cfg.indexFile, "index file")
+ flag.BoolVar(&cfg.verbose, "v", cfg.verbose, "produce verbose output")
+ flag.Parse()
+ if 0 != len(flag.Args()) {
+ fmt.Println("unrecognized options: ", flag.Args())
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
+ if cfg.verbose {
+ tracer = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
+ } else {
+ tracer = log.New(ioutil.Discard, "", 0)
+ }
+
+ tracer.Print("interface: ", cfg.bindIface)
+ tracer.Print("TCP port: ", cfg.bindPortTcp)
+ tracer.Print("doc root: ", cfg.docRoot)
+ tracer.Print("index file: ", cfg.indexFile)
+ tracer.Print("verbose: ", cfg.verbose)
+}
+
+func checkFatal(err error, msg string) {
+ if err != nil {
+ logger.Print(msg, ": ", err.Error())
+ os.Exit(1)
+ }
+}
+
+func check(err error, msg string)(error) {
+ if err != nil {
+ tracer.Print(msg, ": ", err.Error())
+ }
+ return err
+}
+
+func getErrorString(code string) (string) {
+ // TODO flesh this out
+ res := "Error: " + code + " Resource not found. "
+ return res;
+}
+
+func sanitizeSelector(selector string) (string) {
+ selector = strings.Replace(selector, "\r", "", -1)
+ selector = strings.Replace(selector, "\n", "", -1)
+ selector = strings.Replace(selector, "../", "/", -1)
+ selector = strings.Replace(selector, "//", "/", -1)
+ selector = strings.Replace(selector, "//", "/", -1)
+ return selector
+}
+
+func getResource(selector string) ([]byte, error) {
+ if ( selector == "" || selector == "/" ) {
+ selector = cfg.indexFile
+ }
+ resPath := cfg.docRoot + "/" + selector
+ res, err := ioutil.ReadFile(resPath)
+ if check(err,"ReadFile " + resPath) != nil { res = []byte(""); }
+ return res, err;
+}
+
+func handleRequest(conn net.Conn) {
+ defer conn.Close()
+ reply := []byte("")
+ req, err := bufio.NewReader(conn).ReadString('\n')
+ if ( check(err,"ReadString") != nil ) {
+ return
+ } else {
+ tracer.Print("request: '", req, "'")
+ selector := sanitizeSelector(req)
+ tracer.Print("selector: '", selector, "'")
+ reply, err = getResource(selector)
+ if ( err != nil ) { reply = []byte(getErrorString("404") + selector) }
+ }
+ conn.Write(reply)
+}
+
+func serveTCP(sock net.Listener) {
+ for {
+ conn, err := sock.Accept()
+ checkFatal(err, "Accept")
+ tracer.Print("TCP connect from ", conn.RemoteAddr())
+ go handleRequest(conn)
+ }
+}
+
+func main() {
+ initialize()
+ bindaddr := cfg.bindIface + ":" + cfg.bindPortTcp
+ tsock, err := net.Listen("tcp", bindaddr)
+ checkFatal(err, "net.Listen tcp " + bindaddr)
+ defer tsock.Close()
+ tracer.Print("listening on TCP ", bindaddr)
+ go serveTCP(tsock)
+ // send ready signal
+ fmt.Println("")
+ // exit on any input on stdin
+ reader := bufio.NewReader(os.Stdin)
+ reader.ReadRune()
+}
+
+/* EOF */