Permalink
Fetching contributors…
Cannot retrieve contributors at this time
163 lines (140 sloc) 4.25 KB
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package snap
import (
"fmt"
"regexp"
)
// Regular expression describing correct identifiers.
var validSnapName = regexp.MustCompile("^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$")
var validEpoch = regexp.MustCompile("^(?:0|[1-9][0-9]*[*]?)$")
var validHookName = regexp.MustCompile("^[a-z](?:-?[a-z0-9])*$")
// ValidateName checks if a string can be used as a snap name.
func ValidateName(name string) error {
valid := validSnapName.MatchString(name)
if !valid {
return fmt.Errorf("invalid snap name: %q", name)
}
return nil
}
// ValidateEpoch checks if a string can be used as a snap epoch.
func ValidateEpoch(epoch string) error {
valid := validEpoch.MatchString(epoch)
if !valid {
return fmt.Errorf("invalid snap epoch: %q", epoch)
}
return nil
}
// ValidateHook validates the content of the given HookInfo
func ValidateHook(hook *HookInfo) error {
valid := validHookName.MatchString(hook.Name)
if !valid {
return fmt.Errorf("invalid hook name: %q", hook.Name)
}
return nil
}
var validAlias = regexp.MustCompile("^[a-zA-Z0-9][-_.a-zA-Z0-9]*$")
// Validate verifies the content in the info.
func Validate(info *Info) error {
name := info.Name()
if name == "" {
return fmt.Errorf("snap name cannot be empty")
}
err := ValidateName(name)
if err != nil {
return err
}
epoch := info.Epoch
if epoch == "" {
return fmt.Errorf("snap epoch cannot be empty")
}
err = ValidateEpoch(epoch)
if err != nil {
return err
}
// validate app entries
for _, app := range info.Apps {
err := ValidateApp(app)
if err != nil {
return err
}
}
// validate aliases
for alias, app := range info.Aliases {
if !validAlias.MatchString(alias) {
return fmt.Errorf("cannot have %q as alias name for app %q - use only letters, digits, dash, underscore and dot characters", alias, app.Name)
}
}
// validate hook entries
for _, hook := range info.Hooks {
err := ValidateHook(hook)
if err != nil {
return err
}
}
// ensure that plug and slot have unique names
if err := plugsSlotsUniqueNames(info); err != nil {
return err
}
return nil
}
func plugsSlotsUniqueNames(info *Info) error {
// we could choose the smaller collection if we wanted to optimize this check
for plugName := range info.Plugs {
if info.Slots[plugName] != nil {
return fmt.Errorf("cannot have plug and slot with the same name: %q", plugName)
}
}
return nil
}
func validateField(name, cont string, whitelist *regexp.Regexp) error {
if !whitelist.MatchString(cont) {
return fmt.Errorf("app description field '%s' contains illegal %q (legal: '%s')", name, cont, whitelist)
}
return nil
}
// appContentWhitelist is the whitelist of legal chars in the "apps"
// section of snap.yaml
var appContentWhitelist = regexp.MustCompile(`^[A-Za-z0-9/. _#:-]*$`)
var validAppName = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$")
// ValidateApp verifies the content in the app info.
func ValidateApp(app *AppInfo) error {
switch app.Daemon {
case "", "simple", "forking", "oneshot", "dbus", "notify":
// valid
default:
return fmt.Errorf(`"daemon" field contains invalid value %q`, app.Daemon)
}
// Validate app name
if !validAppName.MatchString(app.Name) {
return fmt.Errorf("cannot have %q as app name - use letters, digits, and dash as separator", app.Name)
}
// Validate the rest of the app info
checks := map[string]string{
"command": app.Command,
"stop-command": app.StopCommand,
"post-stop-command": app.PostStopCommand,
"bus-name": app.BusName,
}
for name, value := range checks {
if err := validateField(name, value, appContentWhitelist); err != nil {
return err
}
}
return nil
}