@@ -5,13 +5,13 @@ import (
55 "encoding/base64"
66 "encoding/json"
77 "fmt"
8- "io/ioutil "
8+ "io"
99 "log"
1010 "net"
1111 "net/http"
1212 "os"
1313 "path"
14- "path/filepath "
14+ "regexp "
1515 "strconv"
1616 "strings"
1717 "time"
@@ -24,6 +24,7 @@ import (
2424 "github.com/MakeNowJust/heredoc/v2"
2525 "github.com/briandowns/spinner"
2626 "github.com/gerald1248/httpscerts"
27+ "github.com/go-playground/validator/v10"
2728 "github.com/spf13/cobra"
2829 "golang.org/x/net/websocket"
2930)
@@ -40,6 +41,9 @@ var SSLFlag bool
4041// LocalFlag can be set to false to emulate a remote environment
4142var LocalFlag bool
4243
44+ // Valditor for input validation
45+ var validate * validator.Validate
46+
4347func checkPortAvailability (port int ) bool {
4448 address := fmt .Sprintf ("localhost:%d" , port )
4549 conn , err := net .Dial ("tcp" , address )
@@ -195,16 +199,28 @@ func init() {
195199 serveCmd .Flags ().StringVarP (& ConfigFileFlag , "config" , "c" , "plenti.json" , "use a custom sitewide configuration file" )
196200}
197201
202+ // Validate user supplied values
198203type localChange struct {
199- Action string
200- Encoding string
201- File string
202- Contents string
204+ Action string `json:"action" validate:"required,oneof=create update delete"`
205+ Encoding string `json:"encoding" validate:"required,oneof=base64 text"`
206+ File string `json:"file" validate:"file-path"`
207+ Contents string `json:"contents" validate:"required"`
208+ }
209+
210+ // Custom validation for file path. Only allow files in the layouts and content directories.
211+ func FilePathValidation (fl validator.FieldLevel ) bool {
212+ reFilePath := regexp .MustCompile (`^(content)[a-zA-Z0-9_\-\/]*(.json)$` )
213+ fmt .Println (fl .Field ().String ())
214+ return reFilePath .MatchString (fl .Field ().String ())
203215}
204216
205217func postLocal (w http.ResponseWriter , r * http.Request ) {
218+ // Register custom rules to validator
219+ validate = validator .New ()
220+ validate .RegisterValidation ("file-path" , FilePathValidation )
221+
206222 if r .Method == "POST" {
207- b , err := ioutil .ReadAll (r .Body )
223+ b , err := io .ReadAll (r .Body )
208224 if err != nil {
209225 fmt .Printf ("Could not read 'body' from local edit: %v" , err )
210226 }
@@ -213,10 +229,16 @@ func postLocal(w http.ResponseWriter, r *http.Request) {
213229 if err != nil {
214230 fmt .Printf ("Could not unmarshal JSON data: %v" , err )
215231 }
232+
216233 var contents []byte
217- currentDir , _ := os .Getwd ()
218234 for _ , change := range localChanges {
219- change .File = filepath .Join (currentDir , filepath .Clean ("/" + change .File ))
235+
236+ // Validate user input, there is any error, return 400 Bad Request
237+ err := validate .Struct (change )
238+ if err != nil {
239+ http .Error (w , err .Error (), http .StatusBadRequest )
240+ return
241+ }
220242
221243 if change .Action == "create" || change .Action == "update" {
222244 contents = []byte (change .Contents )
@@ -267,7 +289,7 @@ func serveSSL(port int) {
267289 Handler : nil ,
268290 ReadTimeout : 10 * time .Second ,
269291 WriteTimeout : 10 * time .Second ,
270- ErrorLog : log .New (ioutil .Discard , "" , 0 ),
292+ ErrorLog : log .New (io .Discard , "" , 0 ),
271293 MaxHeaderBytes : 1 << 20 ,
272294 TLSConfig : cfg ,
273295 }
0 commit comments