gophi/client.go

280 lines
7.7 KiB
Go
Raw Normal View History

package main
import (
"log"
"fmt"
"os"
"io"
"net"
"bufio"
"path"
"strings"
)
type ClientMsg int
/* unused for now */
type Client struct {
Message chan ClientMsg
Socket net.Conn
}
const (
GopherMapFile = "/gophermap"
)
func (client *Client) Init(conn *net.Conn) {
client.Message = make(chan ClientMsg)
client.Socket = *conn
}
func (client *Client) Start() {
go func() {
defer func() {
/* Close-up shop */
client.Socket.Close()
close(client.Message)
}()
var count int
var err error
/* Read buffer + final result */
buf := make([]byte, SocketReadBufSize)
received := make([]byte, 0)
/* Buffered read from listener */
iter := 0
for {
/* Buffered read from listener */
count, err = client.Socket.Read(buf)
if err != nil {
client.Log("Error reading from socket %s: %v\n", client.Socket, err.Error())
return
}
/* Only copy non-null bytes */
received = append(received, buf[:count]...)
/* If count is less than expected read size, we've hit EOF */
if count < SocketReadBufSize {
/* EOF */
break
}
/* Hit max read chunk size, send error + close connection */
if iter == MaxSocketReadChunks {
client.SendError("max socket read size reached\n")
client.Log("Reached max socket read size: %d. Closing connection...\n", MaxSocketReadChunks*SocketReadBufSize)
return
}
/* Keep count :) */
iter += 1
}
/* Respond */
gophorErr := serverRespond(client, received)
if gophorErr != nil {
log.Printf(gophorErr.Error() + "\n")
}
}()
}
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...)
response = append(response, []byte(message)...)
response = append(response, []byte(LastLine)...)
/* We're sending an error, if this fails then fuck it lol */
client.Socket.Write(response)
}
func serverRespond(client *Client, data []byte) *GophorError {
/* 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 = "."
}
var response []byte
var gophorErr *GophorError
var err error
/* 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 != "." {
stat, err := fd.Stat()
if err != nil {
client.SendError("%s read fail\n", requestPath) /* Purposely vague errors */
return &GophorError{ FileStatErr, err }
}
switch {
case stat.Mode() & os.ModeDir != 0:
// 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
}
} else {
/* Get directory listing */
client.Log("SERVE DIR: %s\n", fd.Name())
response, gophorErr = listDir(fd)
if gophorErr != nil {
client.SendError("%s dir list fail\n", fd.Name())
return gophorErr
}
}
/* 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 }
}
/* Always finish response with LastLine bytes */
response = append(response, []byte(LastLine)...)
/* Serve response */
count, err := client.Socket.Write(response)
if err != nil {
return &GophorError{ SocketWriteErr, err }
} else if count != len(response) {
return &GophorError{ SocketWriteCountErr, nil }
}
return nil
}
func readFile(fd *os.File) ([]byte, *GophorError) {
var count int
fileContents := make([]byte, 0)
buf := make([]byte, FileReadBufSize)
var err error
reader := bufio.NewReader(fd)
for {
count, err = reader.Read(buf)
if err != nil && err != io.EOF {
return nil, &GophorError{ FileReadErr, err }
}
for i := 0; i < count; i += 1 {
if buf[i] == 0 {
break
}
fileContents = append(fileContents, buf[i])
}
if count < FileReadBufSize {
break
}
}
return fileContents, nil
}
func listDir(fd *os.File) ([]byte, *GophorError) {
files, err := fd.Readdir(-1)
if err != nil {
return nil, &GophorError{ DirListErr, err }
}
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! */
itemPath := path.Join(fd.Name(), file.Name())
entity = newDirEntity(TypeDirectory, file.Name(), "/"+itemPath, *ServerHostname, *ServerPort)
dirContents = append(dirContents, entity.Bytes()...)
case file.Mode() & os.ModeType == 0:
/* Regular file */
itemPath := path.Join(fd.Name(), file.Name())
itemType := getItemType(itemPath)
entity = newDirEntity(itemType, file.Name(), "/"+itemPath, *ServerHostname, *ServerPort)
dirContents = append(dirContents, entity.Bytes()...)
default:
/* Ignore */
}
}
return dirContents, nil
}