package main
import (
"errors"
"fmt"
//"github.com/slasyz/panda/handle"
"bufio"
"io"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
//"strings"
)
// HandleFunc is handler function for HTTP request type
type HandleFunc func(request http.Request) (response http.Response)
type SizeParameter int
// GlobalParameters is variable containing all panda parameters
var GlobalParameters struct {
Listen []int
User string
AccessLogger *log.Logger
ErrorLogger *log.Logger
PathToTPL string
Servers []*Server
}
var OpenedFiles map[string]*log.Logger
// Server is type containing each server parameters
type Server struct {
IP string
Port int
Hostname string
Type string
Handler HandleFunc
}
// configError is type containing config error (Captain Obvious at your
// service)
type configError struct {
fileName string
lineNumber int
message string
}
func (err *configError) Error() string {
return fmt.Sprintf("%s:%d: %s", err.fileName, err.lineNumber, err.message)
}
var lineContentRegexp *regexp.Regexp
var virtualHostRegexp *regexp.Regexp
var fieldRegexp *regexp.Regexp
var commandRegexp *regexp.Regexp
var integerRegexp *regexp.Regexp
var sizeRegexp *regexp.Regexp
func getRegexpSubmatches(reg *regexp.Regexp, names []string, s string) (submatches []string) {
index := reg.FindStringSubmatchIndex(s)
submatches = make([]string, len(names), len(names))
for i, name := range names {
submatches[i] = string(reg.ExpandString([]byte{}, "$"+name, s, index))
}
return
}
func openLogFile(fileName string) (logger *log.Logger, err error) {
logger, ok := OpenedFiles[fileName]
if ok {
return
} else {
file, err := os.Open(fileName)
if err != nil {
err = errors.New("cannot open file " + fileName)
}
logger = log.New(file, "", log.LstdFlags)
}
return
}
func getTypeName(typ string) string {
switch typ {
case "**log.Logger":
return "a string"
case "*string":
return "a string"
case "*int":
return "an integer"
case "*bool":
return "true or false"
case "*SizeParameter":
return "data size, e.g. 1000B, 100KB, 10MB, 1GB"
default:
return typ
}
}
func setConfigValue(name string, sign string, value string, configVariable interface{}, currentFile string) (err error) {
var neededType string
var resultValue interface{}
if integerRegexp.MatchString(value) {
switch t := configVariable.(type) {
case *int, *[]int:
resultValue, _ = strconv.Atoi(value)
default:
neededType = fmt.Sprintf("%T", t)
}
} else if sizeRegexp.MatchString(value) {
submatches := getRegexpSubmatches(sizeRegexp, []string{"value", "unit"}, value)
resultValue, _ := strconv.Atoi(submatches[0])
unit := submatches[1]
factor := 1
switch unit {
case "GB":
factor *= 1024
fallthrough
case "MB":
factor *= 1024
fallthrough
case "KB":
factor *= 1024
}
switch t := configVariable.(type) {
case *int, *[]int, *SizeParameter:
resultValue *= factor
default:
neededType = fmt.Sprintf("%T", t)
}
} else if value[0] == '"' && value[len(value)-1] == '"' {
stringValue := value[1 : len(value)-1] // remove the quotes
switch t := configVariable.(type) {
case *string, *[]string:
resultValue = stringValue
case **log.Logger:
abspath, _ := filepath.Abs(filepath.Join(filepath.Dir(currentFile), stringValue))
resultValue, err = openLogFile(abspath)
if err != nil {
return
}
default:
neededType = fmt.Sprintf("%T", t)
}
} else if value == "true" {
switch t := configVariable.(type) {
case bool:
resultValue = true
default:
neededType = fmt.Sprintf("%T", t)
}
} else if value == "false" {
switch t := configVariable.(type) {
case bool:
resultValue = false
default:
neededType = fmt.Sprintf("%T", t)
}
}
if sign == "=" {
switch configVariable.(type) {
case *int, *string, *bool, **log.Logger:
configVariable = resultValue
default:
err = errors.New("you can only append to this type (must be \"+=\" instead of \"=\"")
return
}
} else if sign == "+=" {
switch configVariable.(type) {
case *[]int:
configVariable = append(*configVariable.(*[]int), resultValue.(int))
case *[]string:
configVariable = append(*configVariable.(*[]string), resultValue.(string))
default:
err = errors.New("you can only assign to this type (must be \"=\" instead of \"+=\"")
return
}
}
if neededType != "" {
err = errors.New("wrong type of " + name + " field: it must be " + neededType)
}
return
}
func parseGlobalParameter(name, sign, value, currentFile string) (err error) {
switch name {
case "User":
err = setConfigValue(name, sign, value, &GlobalParameters.User, currentFile)
case "Listen":
err = setConfigValue(name, sign, value, &GlobalParameters.Listen, currentFile)
case "AccessLog":
err = setConfigValue(name, sign, value, &GlobalParameters.AccessLogger, currentFile)
}
return
}
func parseServerParameter(name, sign, value string, serverNumber int, currentFileName string) (err error) {
return
}
func parseConfig(file io.Reader, currentFileName string, currentServer *int) (errs []configError) {
scanner := bufio.NewScanner(file)
currentLineNumber := 0
for scanner.Scan() {
currentLineNumber++
currentLine := scanner.Text()
if !lineContentRegexp.MatchString(currentLine) {
errs = append(errs, configError{currentFileName, currentLineNumber, "syntax error (unpaired quotation mark?)"})
}
currentLine = getRegexpSubmatches(lineContentRegexp, []string{"content"}, currentLine)[0]
switch {
case currentLine == "":
continue
case virtualHostRegexp.MatchString(currentLine):
submatches := getRegexpSubmatches(virtualHostRegexp, []string{"ip", "port"}, currentLine)
ip := submatches[0]
port := submatches[1]
// TODO
Debug("%s:%d: found new virtualhost with IP %s and port %s", currentFileName, currentLineNumber, ip, port)
case fieldRegexp.MatchString(currentLine):
submatches := getRegexpSubmatches(fieldRegexp, []string{"name", "sign", "value"}, currentLine)
name := submatches[0]
sign := submatches[1]
value := submatches[2]
Debug("%s:%d: found field %s with value %s", currentFileName, currentLineNumber, name, value)
var err error
if *currentServer == -1 {
err = parseGlobalParameter(name, sign, value, currentFileName)
} else {
err = parseServerParameter(name, sign, value, *currentServer, currentFileName)
}
if err != nil {
errs = append(errs, configError{currentFileName, currentLineNumber, err.Error()})
}
case commandRegexp.MatchString(currentLine):
submatches := getRegexpSubmatches(commandRegexp, []string{"command", "argument"}, currentLine)
command := submatches[0]
argument := submatches[1]
if command == "include" {
includedFileName := filepath.Join(filepath.Dir(currentFileName), argument)
includedFile, err := os.Open(includedFileName)
if err != nil {
errs = append(errs, configError{currentFileName, currentLineNumber, "file " + includedFileName + " can not be open"})
}
Debug("%s:%d: enter the file %s", currentFileName, currentLineNumber, includedFileName)
errs = append(errs, parseConfig(includedFile, includedFileName, currentServer)...)
} else if command == "include_dir" {
// TODO
}
default:
errs = append(errs, configError{currentFileName, currentLineNumber, "wrong line format"})
}
}
return
}
func ParseConfigStart(fileName string) (errs []error) {
file, err := os.Open(fileName)
if err != nil {
return []error{errors.New(fmt.Sprintf("Cannot read config file %s (%s)", fileName, err))}
}
defer file.Close()
// Compile the regexps once
lineContentRegexp = regexp.MustCompile(`^\s*` +
`(?P<content>(("[^"]*")|([^"]))*?)` +
`\s*(\/\/.*)?\s*$`)
virtualHostRegexp = regexp.MustCompile(`^` +
`\[VirtualHost\s*\"` +
`(?P<ip>[^"]+)` +
`:` +
`(?P<port>[^":]+)` +
`\"\]` +
`$`)
fieldRegexp = regexp.MustCompile(`^` +
`(?P<name>[a-zA-Z0-9_]+)` +
`\s*` +
`(?P<sign>\+?=)` +
`\s*` +
`(?P<value>\"[^"]*\"|\d+(B|KB|MB|GB)?|true|false)` + // string, integer or bool fields
`$`)
commandRegexp = regexp.MustCompile(`^` +
`(?P<command>\w+)` +
`\(` +
`(?P<argument>[^)]*)` +
`\)$`)
integerRegexp = regexp.MustCompile(`^\d+$`)
sizeRegexp = regexp.MustCompile(`^(?P<value>\d+)(?P<unit>B|KB|MB|GB)`)
currentServer := -1
configErrors := parseConfig(file, fileName, ¤tServer)
if len(configErrors) != 0 {
errs = make([]error, len(configErrors), len(configErrors))
for i, err := range configErrors {
errs[i] = errors.New(err.Error())
}
}
return
// and join several errors into one
}