2020-04-09 16:33:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-04-10 15:01:07 +00:00
|
|
|
"fmt"
|
2020-04-09 16:33:17 +00:00
|
|
|
"os"
|
|
|
|
"net"
|
|
|
|
"path"
|
2020-04-09 18:29:32 +00:00
|
|
|
"strings"
|
2020-04-09 16:33:17 +00:00
|
|
|
)
|
|
|
|
|
2020-04-10 19:31:21 +00:00
|
|
|
const (
|
2020-04-15 18:41:00 +00:00
|
|
|
SocketReadBufSize = 256 /* Supplied selector shouldn't be longer than this anyways */
|
2020-04-11 18:32:35 +00:00
|
|
|
MaxSocketReadChunks = 4
|
2020-04-15 18:41:00 +00:00
|
|
|
FileReadBufSize = 1024
|
2020-04-10 19:31:21 +00:00
|
|
|
)
|
|
|
|
|
2020-04-11 18:32:35 +00:00
|
|
|
type Worker struct {
|
2020-04-12 20:32:09 +00:00
|
|
|
Socket net.Conn
|
2020-04-11 18:32:35 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 20:32:09 +00:00
|
|
|
func NewWorker(socket *net.Conn) *Worker {
|
|
|
|
worker := new(Worker)
|
2020-04-11 18:32:35 +00:00
|
|
|
worker.Socket = *socket
|
2020-04-12 20:32:09 +00:00
|
|
|
return worker
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
|
2020-04-11 18:32:35 +00:00
|
|
|
func (worker *Worker) Serve() {
|
2020-04-09 16:33:17 +00:00
|
|
|
go func() {
|
|
|
|
defer func() {
|
|
|
|
/* Close-up shop */
|
2020-04-11 18:32:35 +00:00
|
|
|
worker.Socket.Close()
|
2020-04-09 16:33:17 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
var count int
|
|
|
|
var err error
|
|
|
|
|
|
|
|
/* Read buffer + final result */
|
2020-04-09 18:29:32 +00:00
|
|
|
buf := make([]byte, SocketReadBufSize)
|
|
|
|
received := make([]byte, 0)
|
2020-04-09 16:33:17 +00:00
|
|
|
|
|
|
|
/* Buffered read from listener */
|
|
|
|
iter := 0
|
|
|
|
for {
|
2020-04-10 15:09:31 +00:00
|
|
|
/* Buffered read from listener */
|
2020-04-11 18:32:35 +00:00
|
|
|
count, err = worker.Socket.Read(buf)
|
2020-04-09 16:33:17 +00:00
|
|
|
if err != nil {
|
2020-04-11 18:32:35 +00:00
|
|
|
logSystemError("Error reading from socket %s: %s\n", worker.Socket, err.Error())
|
2020-04-09 16:33:17 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-10 15:09:31 +00:00
|
|
|
/* Only copy non-null bytes */
|
2020-04-10 15:11:03 +00:00
|
|
|
received = append(received, buf[:count]...)
|
2020-04-09 18:29:32 +00:00
|
|
|
|
2020-04-10 15:09:31 +00:00
|
|
|
/* If count is less than expected read size, we've hit EOF */
|
2020-04-09 18:29:32 +00:00
|
|
|
if count < SocketReadBufSize {
|
|
|
|
/* EOF */
|
2020-04-09 16:33:17 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-04-10 15:09:31 +00:00
|
|
|
/* Hit max read chunk size, send error + close connection */
|
2020-04-09 16:33:17 +00:00
|
|
|
if iter == MaxSocketReadChunks {
|
2020-04-11 18:32:35 +00:00
|
|
|
worker.SendErrorType("max socket read size reached\n")
|
2020-04-11 10:42:34 +00:00
|
|
|
logSystemError("Reached max socket read size %d. Closing connection...\n", MaxSocketReadChunks*SocketReadBufSize)
|
2020-04-09 16:33:17 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-10 15:09:31 +00:00
|
|
|
/* Keep count :) */
|
2020-04-09 16:33:17 +00:00
|
|
|
iter += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Respond */
|
2020-04-12 20:32:09 +00:00
|
|
|
gophorErr := worker.Respond(received)
|
2020-04-09 16:33:17 +00:00
|
|
|
if gophorErr != nil {
|
2020-04-11 10:42:34 +00:00
|
|
|
logSystemError("%s\n", gophorErr.Error())
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-04-11 18:32:35 +00:00
|
|
|
func (worker *Worker) SendErrorType(format string, args ...interface{}) {
|
2020-04-11 20:42:41 +00:00
|
|
|
worker.SendRaw([]byte(fmt.Sprintf(string(TypeError)+"Error: "+format+LastLine, args...)))
|
2020-04-11 18:32:35 +00:00
|
|
|
}
|
2020-04-10 15:01:07 +00:00
|
|
|
|
2020-04-11 20:42:41 +00:00
|
|
|
func (worker *Worker) SendErrorText(format string, args ...interface{}) {
|
|
|
|
worker.SendRaw([]byte(fmt.Sprintf("Error: "+format, args...)))
|
2020-04-11 18:32:35 +00:00
|
|
|
}
|
2020-04-10 15:01:07 +00:00
|
|
|
|
2020-04-11 20:42:41 +00:00
|
|
|
func (worker *Worker) SendRaw(b []byte) *GophorError {
|
|
|
|
count, err := worker.Socket.Write(b)
|
|
|
|
if err != nil {
|
|
|
|
return &GophorError{ SocketWriteErr, err }
|
|
|
|
} else if count != len(b) {
|
|
|
|
return &GophorError{ SocketWriteCountErr, nil }
|
|
|
|
}
|
|
|
|
return nil
|
2020-04-10 15:01:07 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 20:32:09 +00:00
|
|
|
func (worker *Worker) Log(format string, args ...interface{}) {
|
|
|
|
logAccess(worker.Socket.RemoteAddr().String()+" "+format, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (worker *Worker) LogError(format string, args ...interface{}) {
|
|
|
|
logAccessError(worker.Socket.RemoteAddr().String()+" "+format, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (worker *Worker) SanitizePath(dataStr string) string {
|
|
|
|
/* Clean path and trim '/' prefix if still exists */
|
|
|
|
requestPath := strings.TrimPrefix(path.Clean(dataStr), "/")
|
|
|
|
|
2020-04-13 13:39:35 +00:00
|
|
|
if !strings.HasPrefix(requestPath, "/") {
|
|
|
|
requestPath = "/" + requestPath
|
2020-04-12 20:32:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return requestPath
|
|
|
|
}
|
|
|
|
|
|
|
|
func (worker *Worker) Respond(data []byte) *GophorError {
|
|
|
|
/* Only read up to first tab or cr-lf */
|
2020-04-11 20:42:41 +00:00
|
|
|
dataStr := ""
|
|
|
|
dataLen := len(data)
|
|
|
|
for i := 0; i < dataLen; i += 1 {
|
2020-04-17 15:39:25 +00:00
|
|
|
if data[i] == '\t' {
|
2020-04-11 20:42:41 +00:00
|
|
|
break
|
2020-04-17 17:51:30 +00:00
|
|
|
} else if data[i] == DOSLineEnd[0] {
|
2020-04-11 20:42:41 +00:00
|
|
|
if i == dataLen-1 {
|
|
|
|
/* Chances are we'll NEVER reach here, still need to check */
|
|
|
|
return &GophorError{ InvalidRequestErr, nil }
|
2020-04-17 17:51:30 +00:00
|
|
|
} else if data[i+1] == DOSLineEnd[1] {
|
2020-04-11 20:42:41 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dataStr += string(data[i])
|
|
|
|
}
|
2020-04-10 15:01:07 +00:00
|
|
|
|
2020-04-12 20:32:09 +00:00
|
|
|
/* Sanitize supplied path */
|
|
|
|
requestPath := worker.SanitizePath(dataStr)
|
2020-04-10 15:01:07 +00:00
|
|
|
|
2020-04-19 20:28:15 +00:00
|
|
|
/* Handle policy files */
|
|
|
|
switch requestPath {
|
|
|
|
case "/"+CapsTxtStr:
|
|
|
|
return worker.SendRaw(generateCapsTxt())
|
|
|
|
|
|
|
|
case "/"+RobotsTxtStr:
|
|
|
|
return worker.SendRaw(generateRobotsTxt())
|
|
|
|
}
|
|
|
|
|
2020-04-10 15:01:07 +00:00
|
|
|
/* Open requestPath */
|
2020-04-12 20:32:09 +00:00
|
|
|
file, err := os.Open(requestPath)
|
2020-04-10 15:01:07 +00:00
|
|
|
if err != nil {
|
2020-04-12 20:32:09 +00:00
|
|
|
worker.SendErrorType("read fail\n") /* Purposely vague errors */
|
2020-04-10 15:01:07 +00:00
|
|
|
return &GophorError{ FileOpenErr, err }
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Leads to some more concise code below */
|
|
|
|
type FileType int
|
|
|
|
const(
|
|
|
|
File FileType = iota
|
|
|
|
Dir FileType = iota
|
|
|
|
Bad FileType = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
/* If not empty requestPath, check file type */
|
|
|
|
fileType := Dir
|
|
|
|
if requestPath != "." {
|
2020-04-12 20:32:09 +00:00
|
|
|
stat, err := file.Stat()
|
2020-04-09 16:33:17 +00:00
|
|
|
if err != nil {
|
2020-04-11 18:32:35 +00:00
|
|
|
worker.SendErrorType("read fail\n") /* Purposely vague errors */
|
2020-04-10 15:01:07 +00:00
|
|
|
return &GophorError{ FileStatErr, err }
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case stat.Mode() & os.ModeDir != 0:
|
2020-04-10 15:01:07 +00:00
|
|
|
// do nothing :)
|
|
|
|
case stat.Mode() & os.ModeType == 0:
|
|
|
|
fileType = File
|
|
|
|
default:
|
|
|
|
fileType = Bad
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-13 12:15:12 +00:00
|
|
|
/* Don't need the file handle anymore */
|
|
|
|
file.Close()
|
|
|
|
|
2020-04-15 18:41:00 +00:00
|
|
|
/* TODO: work on efficiency */
|
|
|
|
|
2020-04-11 20:42:41 +00:00
|
|
|
/* Handle file type */
|
2020-04-13 13:39:35 +00:00
|
|
|
response := make([]byte, 0)
|
2020-04-10 15:01:07 +00:00
|
|
|
switch fileType {
|
|
|
|
/* Directory */
|
|
|
|
case Dir:
|
|
|
|
/* First try to serve gopher map */
|
2020-04-15 09:52:03 +00:00
|
|
|
gophermapPath := path.Join(requestPath, "/"+GophermapFileStr)
|
2020-04-17 15:39:25 +00:00
|
|
|
fileContents, gophorErr := GlobalFileCache.FetchGophermap(gophermapPath)
|
2020-04-13 12:15:12 +00:00
|
|
|
if gophorErr != nil {
|
|
|
|
/* Get directory listing instead */
|
2020-04-13 13:39:35 +00:00
|
|
|
fileContents, gophorErr = listDir(requestPath, map[string]bool{})
|
2020-04-10 15:01:07 +00:00
|
|
|
if gophorErr != nil {
|
2020-04-13 12:15:12 +00:00
|
|
|
worker.SendErrorType("dir list failed\n")
|
2020-04-10 15:01:07 +00:00
|
|
|
return gophorErr
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
2020-04-13 13:39:35 +00:00
|
|
|
|
|
|
|
/* Add fileContents to response */
|
2020-04-15 09:52:03 +00:00
|
|
|
response = append(response, fileContents...)
|
2020-04-13 13:39:35 +00:00
|
|
|
worker.Log("serve dir: %s\n", requestPath)
|
2020-04-09 16:33:17 +00:00
|
|
|
|
2020-04-12 15:12:56 +00:00
|
|
|
/* Finish directory listing with LastLine */
|
|
|
|
response = append(response, []byte(LastLine)...)
|
2020-04-13 12:15:12 +00:00
|
|
|
} else {
|
2020-04-13 13:39:35 +00:00
|
|
|
/* Successfully loaded gophermap, add fileContents to response */
|
|
|
|
response = append(response, fileContents...)
|
2020-04-15 09:52:03 +00:00
|
|
|
worker.Log("serve gophermap: %s\n", gophermapPath)
|
2020-04-12 15:12:56 +00:00
|
|
|
}
|
2020-04-12 08:09:06 +00:00
|
|
|
|
2020-04-10 15:01:07 +00:00
|
|
|
/* Regular file */
|
|
|
|
case File:
|
|
|
|
/* Read file contents */
|
2020-04-17 15:39:25 +00:00
|
|
|
fileContents, gophorErr := GlobalFileCache.FetchRegular(requestPath)
|
2020-04-10 15:01:07 +00:00
|
|
|
if gophorErr != nil {
|
2020-04-11 20:42:41 +00:00
|
|
|
worker.SendErrorText("file read fail\n")
|
2020-04-10 15:01:07 +00:00
|
|
|
return gophorErr
|
|
|
|
}
|
2020-04-13 13:39:35 +00:00
|
|
|
|
|
|
|
/* Append fileContents to response */
|
|
|
|
response = append(response, fileContents...)
|
|
|
|
worker.Log("serve file: %s\n", requestPath)
|
2020-04-10 15:01:07 +00:00
|
|
|
|
|
|
|
/* Unsupport file type */
|
|
|
|
default:
|
|
|
|
return &GophorError{ FileTypeErr, nil }
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
2020-04-10 15:01:07 +00:00
|
|
|
|
2020-04-13 13:46:13 +00:00
|
|
|
/* Append lastline */
|
|
|
|
response = append(response, []byte(LastLine)...)
|
|
|
|
|
2020-04-09 18:29:32 +00:00
|
|
|
/* Serve response */
|
2020-04-11 20:42:41 +00:00
|
|
|
return worker.SendRaw(response)
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|