1
1
package plasmactlupdate
2
2
3
3
import (
4
+ "bytes"
4
5
"fmt"
6
+ "net/url"
5
7
"os"
6
8
"path/filepath"
7
- "reflect"
9
+ "strings"
10
+ "text/template"
8
11
9
12
"github.com/launchrctl/launchr"
10
13
"gopkg.in/yaml.v3"
11
14
)
12
15
16
+ const (
17
+ defaultPinnedReleaseTpl = "{{.URL}}/stable_release"
18
+ defaultBinTpl = "{{.URL}}/{{.Version}}/{{.Name}}_{{.OS}}_{{.Arch}}{{.Ext}}"
19
+ )
20
+
13
21
type config struct {
14
22
RepositoryURL string `yaml:"repository_url"`
15
23
PinnedRelease string `yaml:"pinned_release_file"`
@@ -24,9 +32,13 @@ func SetUpdateConfig(cfg *config) {
24
32
updateConfig = cfg
25
33
}
26
34
27
- // GetDefaultConfig returns the global default configuration
35
+ // GetDefaultConfig returns the global default configuration copy
28
36
func getUpdateConfig () * config {
29
- return updateConfig
37
+ if updateConfig == nil {
38
+ return & config {}
39
+ }
40
+ cfgCopy := * updateConfig
41
+ return & cfgCopy
30
42
}
31
43
32
44
// LoadConfigFromBytesAndSet parses the configuration from a byte slice and sets it as the global default configuration.
@@ -68,21 +80,70 @@ func parseConfigFromBytes(data []byte) (*config, error) {
68
80
69
81
// validateConfig checks if all fields are filled and not empty
70
82
func validateConfig (cfg * config ) error {
71
- v := reflect .ValueOf (cfg ).Elem ()
72
- t := reflect .TypeOf (cfg ).Elem ()
73
-
74
- for i := 0 ; i < v .NumField (); i ++ {
75
- field := v .Field (i )
76
- fieldType := t .Field (i )
77
-
78
- // Check if field is empty
79
- if field .Kind () == reflect .String && field .String () == "" {
80
- yamlTag := fieldType .Tag .Get ("yaml" )
81
- if yamlTag == "" {
82
- yamlTag = fieldType .Name
83
- }
84
- return fmt .Errorf ("field '%s' is required and cannot be empty" , yamlTag )
85
- }
83
+ if cfg .RepositoryURL == "" {
84
+ return fmt .Errorf ("field 'repository_url' is required and cannot be empty" )
85
+ }
86
+
87
+ return nil
88
+ }
89
+
90
+ type templateVars struct {
91
+ URL string
92
+ Name string
93
+ Version string
94
+ OS string
95
+ Arch string
96
+ Ext string
97
+ }
98
+
99
+ // formatURL formats a template string with the provided variables
100
+ func formatURL (templateStr string , vars templateVars ) (string , error ) {
101
+ tmpl , err := template .New ("url" ).Parse (templateStr )
102
+ if err != nil {
103
+ return "" , fmt .Errorf ("failed to parse template: %w" , err )
104
+ }
105
+
106
+ var buf bytes.Buffer
107
+ err = tmpl .Execute (& buf , vars )
108
+ if err != nil {
109
+ return "" , fmt .Errorf ("failed to execute template: %w" , err )
110
+ }
111
+
112
+ result := strings .TrimSpace (buf .String ())
113
+
114
+ // Validate the resulting URL
115
+ if err = validateURL (result ); err != nil {
116
+ return "" , fmt .Errorf ("invalid URL generated from template: %w" , err )
117
+ }
118
+
119
+ return result , nil
120
+ }
121
+
122
+ // validateURL checks if the URL is valid and doesn't contain unwanted characters
123
+ func validateURL (rawURL string ) error {
124
+ // Check for newlines, tabs, and other control characters
125
+ if strings .ContainsAny (rawURL , "\n \r \t \v \f " ) {
126
+ return fmt .Errorf ("URL contains invalid control characters" )
127
+ }
128
+
129
+ // Parse the URL to ensure it's valid
130
+ parsedURL , err := url .Parse (rawURL )
131
+ if err != nil {
132
+ return fmt .Errorf ("invalid URL format: %w" , err )
133
+ }
134
+
135
+ // Check if scheme is present and valid
136
+ if parsedURL .Scheme == "" {
137
+ return fmt .Errorf ("URL missing scheme (http/https)" )
138
+ }
139
+
140
+ if parsedURL .Scheme != "http" && parsedURL .Scheme != "https" {
141
+ return fmt .Errorf ("invalid URL scheme: %s (expected http or https)" , parsedURL .Scheme )
142
+ }
143
+
144
+ // Check if host is present
145
+ if parsedURL .Host == "" {
146
+ return fmt .Errorf ("URL missing host" )
86
147
}
87
148
88
149
return nil
0 commit comments