refac
This commit is contained in:
parent
7818103d5d
commit
9203b99dcc
13
go.mod
13
go.mod
@ -2,4 +2,15 @@ module git.asdf.cafe/abs3nt/fbisender
|
|||||||
|
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
require gopkg.in/yaml.v2 v2.4.0
|
require (
|
||||||
|
git.asdf.cafe/abs3nt/gunner v0.0.1
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cristalhq/aconfig v0.18.5 // indirect
|
||||||
|
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
|
||||||
|
github.com/joho/godotenv v1.4.0 // indirect
|
||||||
|
github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
|
)
|
||||||
|
15
go.sum
15
go.sum
@ -1,4 +1,19 @@
|
|||||||
|
git.asdf.cafe/abs3nt/gunner v0.0.1 h1:N6kCe7fH83kzm1Sjp/5uZbl8FM5s7KoYCfmhO8qyQbA=
|
||||||
|
git.asdf.cafe/abs3nt/gunner v0.0.1/go.mod h1:Q4zhiPfmffCVAb5xIzZn6Momm91uf/deqRVd2/vdjd4=
|
||||||
|
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||||
|
github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
|
||||||
|
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||||
|
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 h1:HG2ql5fGe4FLL2fUv6o+o0YRyF1mWEcYkNfWGWD82k4=
|
||||||
|
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1/go.mod h1:gQIKkh+HkVcODvMNz/cLbH65Pk9b0r4tfolCOsI8G9I=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y=
|
||||||
|
github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
|
278
main.go
278
main.go
@ -2,282 +2,30 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"git.asdf.cafe/abs3nt/fbisender/src/sender"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultHostPort = 8080
|
|
||||||
defaultTargetPort = "5000"
|
|
||||||
)
|
|
||||||
|
|
||||||
var acceptedExtensions = []string{".cia", ".tik", ".cetk", ".3dsx"}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
TargetIP string `yaml:"target_ip"`
|
|
||||||
HostIP string `yaml:"host_ip"`
|
|
||||||
HostPort int `yaml:"host_port"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
|
||||||
config, err := loadConfig()
|
app := &cli.Command{
|
||||||
if err != nil {
|
Name: "fbisender",
|
||||||
log.Fatalf("Error loading configuration: %v", err)
|
Usage: "send files to FBI over network with an HTTP server",
|
||||||
}
|
UsageText: "fbisender [global options] <target file or directory>",
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
targetPath, err := parseArguments()
|
if !c.Args().Present() {
|
||||||
if err != nil {
|
return fmt.Errorf("target file or directory is required as an argument")
|
||||||
log.Fatalf("Error parsing arguments: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileListPayload, directory, err := prepareFileListPayload(config, targetPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error preparing file list payload: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := changeDirectory(directory); err != nil {
|
|
||||||
log.Fatalf("Error changing directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("\nURLs:")
|
|
||||||
fmt.Println(fileListPayload + "\n")
|
|
||||||
|
|
||||||
server := startHTTPServer(config.HostPort)
|
|
||||||
|
|
||||||
conn, err := sendPayload(config.TargetIP, fileListPayload)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error sending payload: %v", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := waitForInstallation(ctx, conn); err != nil {
|
|
||||||
log.Printf("Installation process error: %v", err)
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case sig := <-sigChan:
|
|
||||||
fmt.Printf("\nReceived signal %v. Shutting down...\n", sig)
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer shutdownCancel()
|
|
||||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
|
||||||
log.Printf("HTTP server shutdown error: %v", err)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
fmt.Println("Server gracefully shut down.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig() (*Config, error) {
|
|
||||||
configDirectory, err := os.UserConfigDir()
|
|
||||||
if err != nil {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting home directory: %w", err)
|
|
||||||
}
|
|
||||||
configDirectory = filepath.Join(homeDir, ".config")
|
|
||||||
}
|
|
||||||
data, err := os.ReadFile(filepath.Join(configDirectory, "fbisender", "config.yaml"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var config Config
|
|
||||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TargetIP == "" {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.HostIP == "" {
|
|
||||||
log.Println("Detecting host IP...")
|
|
||||||
hostIP, err := detectHostIP()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.HostIP = hostIP
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.HostPort == 0 {
|
|
||||||
config.HostPort = defaultHostPort
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseArguments() (string, error) {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
return "", fmt.Errorf("usage: %s <file / directory>", os.Args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
targetPath := strings.TrimSpace(os.Args[1])
|
|
||||||
if _, err := os.Stat(targetPath); os.IsNotExist(err) {
|
|
||||||
return "", fmt.Errorf("%s: no such file or directory", targetPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareFileListPayload(config *Config, targetPath string) (string, string, error) {
|
|
||||||
fmt.Println("Preparing data...")
|
|
||||||
|
|
||||||
baseURL := config.HostIP + ":" + strconv.Itoa(config.HostPort) + "/"
|
|
||||||
var fileListPayload string
|
|
||||||
var directory string
|
|
||||||
|
|
||||||
fileInfo, err := os.Stat(targetPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileInfo.IsDir() {
|
|
||||||
directory = targetPath
|
|
||||||
files, err := os.ReadDir(directory)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("reading directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
if !file.IsDir() && hasAcceptedExtension(file.Name()) {
|
|
||||||
encodedFileName := url.PathEscape(file.Name())
|
|
||||||
fileListPayload += baseURL + encodedFileName + "\n"
|
|
||||||
}
|
}
|
||||||
}
|
return sender.SendFiles(ctx, c.Args().First())
|
||||||
} else {
|
},
|
||||||
if hasAcceptedExtension(fileInfo.Name()) {
|
|
||||||
encodedFileName := url.PathEscape(fileInfo.Name())
|
|
||||||
fileListPayload = baseURL + encodedFileName
|
|
||||||
directory = filepath.Dir(targetPath)
|
|
||||||
} else {
|
|
||||||
return "", "", fmt.Errorf("unsupported file extension. Supported extensions are: %v", acceptedExtensions)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileListPayload == "" {
|
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||||
return "", "", fmt.Errorf("no files to serve")
|
log.Fatal(err)
|
||||||
}
|
|
||||||
|
|
||||||
return fileListPayload, directory, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeDirectory(directory string) error {
|
|
||||||
if directory != "" && directory != "." {
|
|
||||||
if err := os.Chdir(directory); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func startHTTPServer(port int) *http.Server {
|
|
||||||
fmt.Println("Starting HTTP server on port", port)
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: ":" + strconv.Itoa(port),
|
|
||||||
Handler: http.FileServer(http.Dir(".")),
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatalf("Error starting HTTP server: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendPayload(targetIP, fileListPayload string) (net.Conn, error) {
|
|
||||||
fmt.Printf("Sending URL(s) to %s on port %s...\n", targetIP, defaultTargetPort)
|
|
||||||
|
|
||||||
conn, err := net.Dial("tcp", net.JoinHostPort(targetIP, defaultTargetPort))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dialing target device: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
payloadBytes := []byte(fileListPayload)
|
|
||||||
length := uint32(len(payloadBytes))
|
|
||||||
lengthBytes := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(lengthBytes, length)
|
|
||||||
|
|
||||||
if _, err := conn.Write(append(lengthBytes, payloadBytes...)); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, fmt.Errorf("writing to connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForInstallation(ctx context.Context, conn net.Conn) error {
|
|
||||||
fmt.Println("Waiting for the installation to complete...")
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
_, err := conn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF || strings.Contains(err.Error(), "connection reset by peer") {
|
|
||||||
fmt.Println("Installation completed. Connection closed by target device.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("reading from connection: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectHostIP() (string, error) {
|
|
||||||
conn, err := net.Dial("udp", "8.8.8.8:53")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("detecting host IP: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
|
||||||
return localAddr.IP.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasAcceptedExtension(fileName string) bool {
|
|
||||||
ext := strings.ToLower(filepath.Ext(fileName))
|
|
||||||
for _, acceptedExt := range acceptedExtensions {
|
|
||||||
if ext == acceptedExt {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
16
src/config/config.go
Normal file
16
src/config/config.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "git.asdf.cafe/abs3nt/gunner"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
TargetIP string `yaml:"target_ip"`
|
||||||
|
TargetPort string `yaml:"target_port" default:"5000"`
|
||||||
|
HostIP string `yaml:"host_ip"`
|
||||||
|
HostPort int `yaml:"host_port" default:"8080"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig() *Config {
|
||||||
|
c := &Config{}
|
||||||
|
gunner.LoadApp(c, "fbisender")
|
||||||
|
return c
|
||||||
|
}
|
37
src/fileutils/fileutils.go
Normal file
37
src/fileutils/fileutils.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package fileutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChangeDirectory(directory string) error {
|
||||||
|
if directory != "" && directory != "." {
|
||||||
|
if err := os.Chdir(directory); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var acceptedExtensions = map[string]struct{}{
|
||||||
|
".cia": {},
|
||||||
|
".tik": {},
|
||||||
|
".cetk": {},
|
||||||
|
".3dsx": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasAcceptedExtension(fileName string) bool {
|
||||||
|
ext := strings.ToLower(filepath.Ext(fileName))
|
||||||
|
_, ok := acceptedExtensions[ext]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSupportedExtensions() []string {
|
||||||
|
var supportedExtensions []string
|
||||||
|
for ext := range acceptedExtensions {
|
||||||
|
supportedExtensions = append(supportedExtensions, ext)
|
||||||
|
}
|
||||||
|
return supportedExtensions
|
||||||
|
}
|
136
src/sender/sender.go
Normal file
136
src/sender/sender.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package sender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.asdf.cafe/abs3nt/fbisender/src/config"
|
||||||
|
"git.asdf.cafe/abs3nt/fbisender/src/fileutils"
|
||||||
|
"git.asdf.cafe/abs3nt/fbisender/src/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SendFiles(ctx context.Context, targetPath string) error {
|
||||||
|
config := config.NewConfig()
|
||||||
|
|
||||||
|
fileListPayload, directory, err := prepareFileListPayload(config, targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("preparing file list payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fileutils.ChangeDirectory(directory); err != nil {
|
||||||
|
return fmt.Errorf("changing directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\nURLs:")
|
||||||
|
fmt.Println(fileListPayload + "\n")
|
||||||
|
|
||||||
|
httpServer := server.StartHTTPServer(config.HostPort)
|
||||||
|
defer server.ShutdownHTTPServer(httpServer)
|
||||||
|
|
||||||
|
conn, err := sendPayload(config.TargetIP, config.TargetPort, fileListPayload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sending payload: %w", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := server.WaitForInstallation(ctx, conn); err != nil {
|
||||||
|
log.Printf("installation process error: %v", err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case sig := <-sigChan:
|
||||||
|
fmt.Printf("\nReceived signal %v. Initiating shutdown...\n", sig)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
fmt.Println("Server shut down successfully.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareFileListPayload(config *config.Config, targetPath string) (string, string, error) {
|
||||||
|
fmt.Println("Preparing data...")
|
||||||
|
|
||||||
|
baseURL := fmt.Sprintf("%s:%d/", config.HostIP, config.HostPort)
|
||||||
|
fileInfo, err := os.Stat(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("stat target path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
return prepareDirectoryPayload(baseURL, targetPath)
|
||||||
|
} else if fileutils.HasAcceptedExtension(fileInfo.Name()) {
|
||||||
|
encodedPath := encodeFilePath(baseURL, fileInfo.Name())
|
||||||
|
return encodedPath, filepath.Dir(targetPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", fmt.Errorf("unsupported file extension. Supported extensions are: %v", fileutils.GetSupportedExtensions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareDirectoryPayload(baseURL, directory string) (string, string, error) {
|
||||||
|
files, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("reading directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadBuilder strings.Builder
|
||||||
|
for _, file := range files {
|
||||||
|
if !file.IsDir() && fileutils.HasAcceptedExtension(file.Name()) {
|
||||||
|
payloadBuilder.WriteString(encodeFilePath(baseURL, file.Name()) + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if payloadBuilder.Len() == 0 {
|
||||||
|
return "", "", fmt.Errorf("no files with supported extensions to serve")
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadBuilder.String(), directory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeFilePath(baseURL, fileName string) string {
|
||||||
|
return baseURL + url.PathEscape(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPayload(targetIP, targetPort, fileListPayload string) (net.Conn, error) {
|
||||||
|
fmt.Printf("Sending URL(s) to %s on port %s...\n", targetIP, targetPort)
|
||||||
|
|
||||||
|
conn, err := net.Dial("tcp", net.JoinHostPort(targetIP, targetPort))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dialing target device: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes := []byte(fileListPayload)
|
||||||
|
length := uint32(len(payloadBytes))
|
||||||
|
lengthBytes := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(lengthBytes, length)
|
||||||
|
|
||||||
|
if _, err := conn.Write(append(lengthBytes, payloadBytes...)); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("writing to connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
58
src/server/server.go
Normal file
58
src/server/server.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartHTTPServer(port int) *http.Server {
|
||||||
|
fmt.Println("Starting HTTP server on port", port)
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: ":" + strconv.Itoa(port),
|
||||||
|
Handler: http.FileServer(http.Dir(".")),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Fatalf("Error starting HTTP server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func WaitForInstallation(ctx context.Context, conn net.Conn) error {
|
||||||
|
fmt.Println("Waiting for the installation to complete...")
|
||||||
|
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
_, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF || strings.Contains(err.Error(), "connection reset by peer") {
|
||||||
|
fmt.Println("Installation completed. Connection closed by target device.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("reading from connection: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShutdownHTTPServer(httpServer *http.Server) {
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
||||||
|
log.Printf("HTTP server shutdown error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user