You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
6.5 KiB
Go
256 lines
6.5 KiB
Go
package gopher
|
|
|
|
import (
|
|
"gophi/core"
|
|
"os"
|
|
)
|
|
|
|
var (
|
|
// subgophermapSizeMax specifies the maximum size of an included subgophermap
|
|
subgophermapSizeMax int64
|
|
)
|
|
|
|
// GophermapSection is an interface that specifies individually renderable (and writeable) sections of a gophermap
|
|
type gophermapSection interface {
|
|
RenderAndWrite(*core.Client) core.Error
|
|
}
|
|
|
|
// readGophermap reads a FD and Path as gophermap sections
|
|
func readGophermap(fd *os.File, p *core.Path) ([]gophermapSection, core.Error) {
|
|
// Create return slice
|
|
sections := make([]gophermapSection, 0)
|
|
|
|
// Create hidden files map now in case later requested
|
|
hidden := map[string]bool{
|
|
p.Relative(): true,
|
|
}
|
|
|
|
// Declare variables
|
|
var returnErr core.Error
|
|
titleAlready := false
|
|
|
|
// Perform scan of gophermap FD
|
|
scanErr := core.FileSystem.ScanFile(
|
|
fd,
|
|
func(line string) bool {
|
|
// Parse the line item type and handle
|
|
lineType := parseLineType(line)
|
|
switch lineType {
|
|
case typeInfoNotStated:
|
|
// Append TypeInfo to beginning of line
|
|
sections = append(sections, &TextSection{buildInfoLine(line)})
|
|
return true
|
|
|
|
case typeTitle:
|
|
// Reformat title line to send as info line with appropriate selector
|
|
if !titleAlready {
|
|
sections = append(sections, &TextSection{buildLine(typeInfo, line[1:], "TITLE", nullHost, nullPort)})
|
|
titleAlready = true
|
|
return true
|
|
}
|
|
returnErr = core.NewError(InvalidGophermapErr)
|
|
return false
|
|
|
|
case typeComment:
|
|
// ignore this line
|
|
return true
|
|
|
|
case typeHiddenFile:
|
|
// Add to hidden files map
|
|
hidden[line[1:]] = true
|
|
return true
|
|
|
|
case typeSubGophermap:
|
|
// Parse new Path and parameters
|
|
request := core.ParseInternalRequest(p, line[1:])
|
|
if returnErr != nil {
|
|
return false
|
|
} else if request.Path().Relative() == "" || request.Path().Relative() == p.Relative() {
|
|
returnErr = core.NewError(InvalidGophermapErr)
|
|
return false
|
|
}
|
|
|
|
// Open FD
|
|
var subFD *os.File
|
|
subFD, returnErr = core.FileSystem.OpenFile(request.Path())
|
|
if returnErr != nil {
|
|
return false
|
|
}
|
|
|
|
// Get stat
|
|
stat, err := subFD.Stat()
|
|
if err != nil {
|
|
returnErr = core.WrapError(core.FileStatErr, err)
|
|
return false
|
|
} else if stat.IsDir() {
|
|
returnErr = core.NewError(SubgophermapIsDirErr)
|
|
return false
|
|
}
|
|
|
|
// Handle CGI script
|
|
if core.WithinCGIDir(request.Path()) {
|
|
sections = append(sections, &CGISection{request})
|
|
return true
|
|
}
|
|
|
|
// Error out if file too big
|
|
if stat.Size() > subgophermapSizeMax {
|
|
returnErr = core.NewError(SubgophermapSizeErr)
|
|
return false
|
|
}
|
|
|
|
// Handle regular file
|
|
if !isGophermap(request.Path()) {
|
|
sections = append(sections, &FileSection{request.Path()})
|
|
return true
|
|
}
|
|
|
|
// Handle gophermap
|
|
sections = append(sections, &SubgophermapSection{request.Path()})
|
|
return true
|
|
|
|
case typeEnd:
|
|
// Last line, break-out!
|
|
return false
|
|
|
|
case typeEndBeginList:
|
|
// Append DirectorySection object then break, as-with typeEnd
|
|
dirPath := p.Dir()
|
|
sections = append(sections, &DirectorySection{hidden, dirPath})
|
|
return false
|
|
|
|
default:
|
|
// Default is appending line with replaced strings to sections slice as TextSection
|
|
sections = append(sections, &TextSection{[]byte(replacePlacementStrs(line) + "\r\n")})
|
|
return true
|
|
}
|
|
},
|
|
)
|
|
|
|
// Check the scan didn't exit with error
|
|
if returnErr != nil {
|
|
return nil, returnErr
|
|
} else if scanErr != nil {
|
|
return nil, scanErr
|
|
}
|
|
|
|
return sections, nil
|
|
}
|
|
|
|
// TextSection is a simple implementation that holds line's byte contents as-is
|
|
type TextSection struct {
|
|
contents []byte
|
|
}
|
|
|
|
// RenderAndWrite simply writes the byte slice to the client
|
|
func (s *TextSection) RenderAndWrite(client *core.Client) core.Error {
|
|
return client.Conn().Write(s.contents)
|
|
}
|
|
|
|
// DirectorySection is an implementation that holds a dir path, and map of hidden files, to later list a dir contents
|
|
type DirectorySection struct {
|
|
hidden map[string]bool
|
|
path *core.Path
|
|
}
|
|
|
|
// RenderAndWrite scans and renders a list of the contents of a directory (skipping hidden or restricted files)
|
|
func (s *DirectorySection) RenderAndWrite(client *core.Client) core.Error {
|
|
fd, err := core.FileSystem.OpenFile(s.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Slice to write
|
|
dirContents := make([]byte, 0)
|
|
|
|
// Scan directory and build lines
|
|
err = core.FileSystem.ScanDirectory(fd, s.path, func(file os.FileInfo, p *core.Path) {
|
|
// Ignore hidden files!
|
|
_, ok := s.hidden[file.Name()]
|
|
if ok {
|
|
return
|
|
}
|
|
|
|
// Append new formatted file listing (if correct type)
|
|
dirContents = appendFileListing(dirContents, file, p)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write dirContents to client
|
|
return client.Conn().Write(dirContents)
|
|
}
|
|
|
|
// FileSection is an implementation that holds a file path, and writes the file contents to client
|
|
type FileSection struct {
|
|
path *core.Path
|
|
}
|
|
|
|
// RenderAndWrite simply opens, reads and writes the file contents to the client
|
|
func (s *FileSection) RenderAndWrite(client *core.Client) core.Error {
|
|
// Open FD for the file
|
|
fd, err := core.FileSystem.OpenFile(s.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Byte slice to contain gophermap contents
|
|
b := make([]byte, 0)
|
|
|
|
// Scan the file contents, format for gophermap, append to byte slice
|
|
err = core.FileSystem.ScanFile(
|
|
fd,
|
|
func(line string) bool {
|
|
b = append(b, buildInfoLine(line)...)
|
|
return true
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the file contents to the client
|
|
return client.Conn().Write(b)
|
|
}
|
|
|
|
// SubgophermapSection is an implementation to hold onto a gophermap path, then read, render and write contents to a client
|
|
type SubgophermapSection struct {
|
|
path *core.Path
|
|
}
|
|
|
|
// RenderAndWrite reads, renders and writes the contents of the gophermap to the client
|
|
func (s *SubgophermapSection) RenderAndWrite(client *core.Client) core.Error {
|
|
// Get FD for gophermap
|
|
fd, err := core.FileSystem.OpenFile(s.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Read gophermap into sections
|
|
sections, err := readGophermap(fd, s.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write each of the sections (AAAA COULD BE RECURSIONNNNN)
|
|
for _, section := range sections {
|
|
err := section.RenderAndWrite(client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CGISection is an implementation that holds onto a built request, then processing as a CGI request on request
|
|
type CGISection struct {
|
|
request *core.Request
|
|
}
|
|
|
|
// RenderAndWrite takes the request, and executes the associated CGI script with parameters
|
|
func (s *CGISection) RenderAndWrite(client *core.Client) core.Error {
|
|
return core.ExecuteCGIScript(client, s.request)
|
|
}
|