2020-04-09 16:33:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-04-09 19:47:03 +00:00
|
|
|
"log"
|
2020-04-10 15:01:07 +00:00
|
|
|
"fmt"
|
2020-04-09 16:33:17 +00:00
|
|
|
"os"
|
2020-04-09 18:29:32 +00:00
|
|
|
"io"
|
2020-04-09 16:33:17 +00:00
|
|
|
"net"
|
|
|
|
"bufio"
|
|
|
|
"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
|
|
|
type ClientMsg int
|
|
|
|
/* unused for now */
|
2020-04-09 16:33:17 +00:00
|
|
|
|
|
|
|
type Client struct {
|
2020-04-10 19:31:21 +00:00
|
|
|
Message chan ClientMsg
|
|
|
|
Socket net.Conn
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 19:31:21 +00:00
|
|
|
const (
|
|
|
|
GopherMapFile = "/gophermap"
|
|
|
|
)
|
|
|
|
|
2020-04-09 16:33:17 +00:00
|
|
|
func (client *Client) Init(conn *net.Conn) {
|
2020-04-10 19:31:21 +00:00
|
|
|
client.Message = make(chan ClientMsg)
|
|
|
|
client.Socket = *conn
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (client *Client) Start() {
|
|
|
|
go func() {
|
|
|
|
defer func() {
|
|
|
|
/* Close-up shop */
|
|
|
|
client.Socket.Close()
|
2020-04-10 19:31:21 +00:00
|
|
|
close(client.Message)
|
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-09 18:29:32 +00:00
|
|
|
count, err = client.Socket.Read(buf)
|
2020-04-09 16:33:17 +00:00
|
|
|
if err != nil {
|
2020-04-10 15:01:07 +00:00
|
|
|
client.Log("Error reading from socket %s: %v\n", client.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-10 15:11:03 +00:00
|
|
|
client.SendError("max socket read size reached\n")
|
2020-04-10 15:01:07 +00:00
|
|
|
client.Log("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-09 18:29:32 +00:00
|
|
|
gophorErr := serverRespond(client, received)
|
2020-04-09 16:33:17 +00:00
|
|
|
if gophorErr != nil {
|
2020-04-09 19:47:03 +00:00
|
|
|
log.Printf(gophorErr.Error() + "\n")
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-04-10 15:01:07 +00:00
|
|
|
func (client *Client) Log(format string, args ...interface{}) {
|
|
|
|
log.Printf(client.Socket.RemoteAddr().String()+" "+format, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (client *Client) SendError(format string, args ...interface{}) {
|
|
|
|
response := make([]byte, 0)
|
|
|
|
response = append(response, byte(TypeError))
|
|
|
|
|
|
|
|
/* Format error message and append to response */
|
|
|
|
message := fmt.Sprintf(format, args...)
|
2020-04-10 19:31:21 +00:00
|
|
|
response = append(response, []byte(message + CrLf)...)
|
2020-04-10 15:01:07 +00:00
|
|
|
response = append(response, []byte(LastLine)...)
|
|
|
|
|
|
|
|
/* We're sending an error, if this fails then fuck it lol */
|
|
|
|
client.Socket.Write(response)
|
|
|
|
}
|
|
|
|
|
2020-04-09 16:33:17 +00:00
|
|
|
func serverRespond(client *Client, data []byte) *GophorError {
|
2020-04-10 15:01:07 +00:00
|
|
|
/* Clean initial data:
|
|
|
|
* Should usually start with a '/' since the selector response we send
|
|
|
|
* starts with a '/' client formatting reasons.
|
|
|
|
*/
|
|
|
|
dataStr := strings.TrimPrefix(strings.TrimSuffix(string(data), CrLf), "/")
|
|
|
|
|
|
|
|
/* Clean path and get shortest possible from current directory */
|
|
|
|
requestPath := path.Clean(dataStr)
|
|
|
|
|
|
|
|
/* Ensure alway a relative paths + WITHIN ServerDir, serve them root otherwise */
|
|
|
|
if strings.HasPrefix(requestPath, "/") || strings.HasPrefix(requestPath, "..") {
|
|
|
|
client.Log("Illegal path requested: %s\n", dataStr)
|
|
|
|
requestPath = "."
|
|
|
|
}
|
|
|
|
|
2020-04-09 16:33:17 +00:00
|
|
|
var response []byte
|
|
|
|
var gophorErr *GophorError
|
|
|
|
var err error
|
|
|
|
|
2020-04-10 15:01:07 +00:00
|
|
|
/* Open requestPath */
|
|
|
|
fd, err := os.Open(requestPath)
|
|
|
|
if err != nil {
|
|
|
|
client.SendError("%s read fail\n", requestPath) /* Purposely vague errors */
|
|
|
|
return &GophorError{ FileOpenErr, err }
|
|
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
/* 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-09 16:33:17 +00:00
|
|
|
stat, err := fd.Stat()
|
|
|
|
if err != nil {
|
2020-04-10 15:01:07 +00:00
|
|
|
client.SendError("%s read fail\n", requestPath) /* Purposely vague errors */
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle Dir / File / error otherwise */
|
|
|
|
switch fileType {
|
|
|
|
/* Directory */
|
|
|
|
case Dir:
|
|
|
|
/* First try to serve gopher map */
|
|
|
|
requestPath = path.Join(requestPath, GopherMapFile)
|
|
|
|
fd2, err := os.Open(requestPath)
|
|
|
|
defer fd2.Close()
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
/* Read GopherMapFile contents */
|
|
|
|
client.Log("%s SERVER GOPHERMAP: %s\n", fd2.Name())
|
|
|
|
|
|
|
|
response, gophorErr = readFile(fd2)
|
|
|
|
if gophorErr != nil {
|
|
|
|
client.SendError("%s read fail\n", fd2.Name())
|
|
|
|
return gophorErr
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
2020-04-10 15:01:07 +00:00
|
|
|
} else {
|
|
|
|
/* Get directory listing */
|
|
|
|
client.Log("SERVE DIR: %s\n", fd.Name())
|
2020-04-09 16:33:17 +00:00
|
|
|
|
2020-04-10 15:01:07 +00:00
|
|
|
response, gophorErr = listDir(fd)
|
2020-04-09 16:33:17 +00:00
|
|
|
if gophorErr != nil {
|
2020-04-10 15:01:07 +00:00
|
|
|
client.SendError("%s dir list fail\n", fd.Name())
|
2020-04-09 16:33:17 +00:00
|
|
|
return gophorErr
|
|
|
|
}
|
2020-04-10 15:01:07 +00:00
|
|
|
}
|
2020-04-09 16:33:17 +00:00
|
|
|
|
2020-04-10 15:01:07 +00:00
|
|
|
/* Regular file */
|
|
|
|
case File:
|
|
|
|
/* Read file contents */
|
|
|
|
client.Log("SERVE FILE: %s\n", fd.Name())
|
|
|
|
|
|
|
|
response, gophorErr = readFile(fd)
|
|
|
|
if gophorErr != nil {
|
|
|
|
client.SendError("%s read fail\n", fd.Name())
|
|
|
|
return gophorErr
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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-09 18:29:32 +00:00
|
|
|
/* Always finish response with LastLine bytes */
|
2020-04-09 16:33:17 +00:00
|
|
|
response = append(response, []byte(LastLine)...)
|
|
|
|
|
2020-04-09 18:29:32 +00:00
|
|
|
/* Serve response */
|
2020-04-09 16:33:17 +00:00
|
|
|
count, err := client.Socket.Write(response)
|
|
|
|
if err != nil {
|
2020-04-10 15:01:07 +00:00
|
|
|
return &GophorError{ SocketWriteErr, err }
|
2020-04-09 16:33:17 +00:00
|
|
|
} else if count != len(response) {
|
2020-04-10 15:01:07 +00:00
|
|
|
return &GophorError{ SocketWriteCountErr, nil }
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readFile(fd *os.File) ([]byte, *GophorError) {
|
|
|
|
var count int
|
2020-04-09 18:29:32 +00:00
|
|
|
fileContents := make([]byte, 0)
|
|
|
|
buf := make([]byte, FileReadBufSize)
|
2020-04-09 16:33:17 +00:00
|
|
|
|
|
|
|
var err error
|
|
|
|
reader := bufio.NewReader(fd)
|
2020-04-09 18:29:32 +00:00
|
|
|
|
2020-04-09 16:33:17 +00:00
|
|
|
for {
|
2020-04-09 18:29:32 +00:00
|
|
|
count, err = reader.Read(buf)
|
|
|
|
if err != nil && err != io.EOF {
|
2020-04-10 15:01:07 +00:00
|
|
|
return nil, &GophorError{ FileReadErr, err }
|
2020-04-09 18:29:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < count; i += 1 {
|
|
|
|
if buf[i] == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
fileContents = append(fileContents, buf[i])
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
2020-04-09 18:29:32 +00:00
|
|
|
|
2020-04-09 16:33:17 +00:00
|
|
|
if count < FileReadBufSize {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fileContents, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func listDir(fd *os.File) ([]byte, *GophorError) {
|
|
|
|
files, err := fd.Readdir(-1)
|
|
|
|
if err != nil {
|
2020-04-10 15:01:07 +00:00
|
|
|
return nil, &GophorError{ DirListErr, err }
|
2020-04-09 16:33:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var entity *DirEntity
|
|
|
|
dirContents := make([]byte, 0)
|
|
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
if !ShowHidden && file.Name()[0] == '.' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case file.Mode() & os.ModeDir != 0:
|
|
|
|
/* Directory! */
|
2020-04-10 15:01:07 +00:00
|
|
|
itemPath := path.Join(fd.Name(), file.Name())
|
|
|
|
entity = newDirEntity(TypeDirectory, file.Name(), "/"+itemPath, *ServerHostname, *ServerPort)
|
2020-04-09 16:33:17 +00:00
|
|
|
dirContents = append(dirContents, entity.Bytes()...)
|
|
|
|
|
|
|
|
case file.Mode() & os.ModeType == 0:
|
|
|
|
/* Regular file */
|
2020-04-10 15:01:07 +00:00
|
|
|
itemPath := path.Join(fd.Name(), file.Name())
|
|
|
|
itemType := getItemType(itemPath)
|
|
|
|
entity = newDirEntity(itemType, file.Name(), "/"+itemPath, *ServerHostname, *ServerPort)
|
2020-04-09 16:33:17 +00:00
|
|
|
dirContents = append(dirContents, entity.Bytes()...)
|
2020-04-09 18:29:32 +00:00
|
|
|
|
2020-04-09 16:33:17 +00:00
|
|
|
default:
|
|
|
|
/* Ignore */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dirContents, nil
|
|
|
|
}
|