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.
gophi/gopher/gophermap.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)
}