137 lines
3.2 KiB
Go
137 lines
3.2 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"go.dev.pztrn.name/hosts-translator/internal/configuration"
|
|
"go.dev.pztrn.name/hosts-translator/internal/models"
|
|
)
|
|
|
|
var ErrParserError = errors.New("hosts parser")
|
|
|
|
// Parser is a controlling structure for hosts file (both parsed and unparsed data).
|
|
type Parser struct {
|
|
parsedHosts []models.Host
|
|
}
|
|
|
|
// NewParser creates new hosts file parsing controlling structure.
|
|
func NewParser() *Parser {
|
|
p := &Parser{}
|
|
p.initialize()
|
|
|
|
return p
|
|
}
|
|
|
|
// Returns file data as bytes.
|
|
func (p *Parser) getFileData() ([]string, error) {
|
|
// Before everything we should normalize file path.
|
|
filePath := configuration.HostsFilePath
|
|
|
|
// Replace possible "~" in the beginning as file reading function unable
|
|
// to expand it. Also we should check only beginning because tilde is actually
|
|
// a very valid directory or file name.
|
|
if strings.HasPrefix(filePath, "~") {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filePath = strings.Replace(filePath, "~", homeDir, 1)
|
|
}
|
|
|
|
// Get absolute file path.
|
|
absolutePath, err := filepath.Abs(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Println("Reading file data from", absolutePath)
|
|
|
|
// Read file data.
|
|
rawData, err := os.ReadFile(absolutePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data := strings.Split(string(rawData), "\n")
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// GetParsedData returns parsed data slice.
|
|
func (p *Parser) GetParsedData() []models.Host {
|
|
return p.parsedHosts
|
|
}
|
|
|
|
// Initializes internal state of parser as well as CLI flags.
|
|
func (p *Parser) initialize() {
|
|
p.parsedHosts = make([]models.Host, 0)
|
|
}
|
|
|
|
// Parse parses hosts file into internal representation.
|
|
func (p *Parser) Parse() error {
|
|
log.Println("Starting hosts file parsing. File located at", configuration.HostsFilePath)
|
|
|
|
data, err := p.getFileData()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, line := range data {
|
|
// We should skip commented lines.
|
|
if strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
// Every line is a two-or-more-not-empty-strings. First string is always
|
|
// an IP address.
|
|
// Also there are a non-zero possibility that line will contain tabs, so as
|
|
// very first action we should replace them with spaces.
|
|
if strings.Contains(line, "\t") {
|
|
line = strings.Replace(line, "\t", " ", -1)
|
|
}
|
|
|
|
lineSplitted := strings.Split(line, " ")
|
|
|
|
// As one IP address can be bound to multiple domains we should take care
|
|
// of that situation by creating multiple Host structures.
|
|
var address string
|
|
|
|
for _, lineData := range lineSplitted {
|
|
// Also there might be a case when address placed first in line but
|
|
// line itself has spaces in the beginning.
|
|
if address == "" && lineData != "" {
|
|
address = lineData
|
|
|
|
continue
|
|
}
|
|
|
|
if lineData == "" {
|
|
continue
|
|
}
|
|
|
|
domainToAdd := lineData
|
|
|
|
if configuration.DomainPostfix != "" && !strings.HasSuffix(domainToAdd, configuration.DomainPostfix) {
|
|
domainToAdd += "." + configuration.DomainPostfix
|
|
}
|
|
|
|
p.parsedHosts = append(p.parsedHosts, models.Host{
|
|
Domain: domainToAdd,
|
|
Address: address,
|
|
})
|
|
}
|
|
}
|
|
|
|
log.Println("Got", len(p.parsedHosts), "domains from hosts file")
|
|
|
|
// ToDo: hide under CLI parameter like '-debug'?
|
|
// log.Printf("%+v\n", p.parsedHosts)
|
|
|
|
return nil
|
|
}
|