mirror of
				https://gitlab.com/NebulousLabs/go-upnp.git
				synced 2025-10-31 10:06:18 +08:00 
			
		
		
		
	 29a8814b4f
			
		
	
	29a8814b4f
	
	
	
		
			
			Because github.com/huin/goupnp was modified for specific use by this repo, it became difficult to correctly vendor this using a tool. To avoid this issue, github.com/huin/goupnp was taken out of the vendor dir and included as a packge in the repo. Signed-off-by: Derek McQuay <derekmcquay@gmail.com>
		
			
				
	
	
		
			520 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			520 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package soap
 | |
| 
 | |
| import (
 | |
| 	"encoding/base64"
 | |
| 	"encoding/hex"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 	"unicode/utf8"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// localLoc acts like time.Local for this package, but is faked out by the
 | |
| 	// unit tests to ensure that things stay constant (especially when running
 | |
| 	// this test in a place where local time is UTC which might mask bugs).
 | |
| 	localLoc = time.Local
 | |
| )
 | |
| 
 | |
| func MarshalUi1(v uint8) (string, error) {
 | |
| 	return strconv.FormatUint(uint64(v), 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalUi1(s string) (uint8, error) {
 | |
| 	v, err := strconv.ParseUint(s, 10, 8)
 | |
| 	return uint8(v), err
 | |
| }
 | |
| 
 | |
| func MarshalUi2(v uint16) (string, error) {
 | |
| 	return strconv.FormatUint(uint64(v), 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalUi2(s string) (uint16, error) {
 | |
| 	v, err := strconv.ParseUint(s, 10, 16)
 | |
| 	return uint16(v), err
 | |
| }
 | |
| 
 | |
| func MarshalUi4(v uint32) (string, error) {
 | |
| 	return strconv.FormatUint(uint64(v), 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalUi4(s string) (uint32, error) {
 | |
| 	v, err := strconv.ParseUint(s, 10, 32)
 | |
| 	return uint32(v), err
 | |
| }
 | |
| 
 | |
| func MarshalI1(v int8) (string, error) {
 | |
| 	return strconv.FormatInt(int64(v), 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalI1(s string) (int8, error) {
 | |
| 	v, err := strconv.ParseInt(s, 10, 8)
 | |
| 	return int8(v), err
 | |
| }
 | |
| 
 | |
| func MarshalI2(v int16) (string, error) {
 | |
| 	return strconv.FormatInt(int64(v), 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalI2(s string) (int16, error) {
 | |
| 	v, err := strconv.ParseInt(s, 10, 16)
 | |
| 	return int16(v), err
 | |
| }
 | |
| 
 | |
| func MarshalI4(v int32) (string, error) {
 | |
| 	return strconv.FormatInt(int64(v), 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalI4(s string) (int32, error) {
 | |
| 	v, err := strconv.ParseInt(s, 10, 32)
 | |
| 	return int32(v), err
 | |
| }
 | |
| 
 | |
| func MarshalInt(v int64) (string, error) {
 | |
| 	return strconv.FormatInt(v, 10), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalInt(s string) (int64, error) {
 | |
| 	return strconv.ParseInt(s, 10, 64)
 | |
| }
 | |
| 
 | |
| func MarshalR4(v float32) (string, error) {
 | |
| 	return strconv.FormatFloat(float64(v), 'G', -1, 32), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalR4(s string) (float32, error) {
 | |
| 	v, err := strconv.ParseFloat(s, 32)
 | |
| 	return float32(v), err
 | |
| }
 | |
| 
 | |
| func MarshalR8(v float64) (string, error) {
 | |
| 	return strconv.FormatFloat(v, 'G', -1, 64), nil
 | |
| }
 | |
| 
 | |
| func UnmarshalR8(s string) (float64, error) {
 | |
| 	v, err := strconv.ParseFloat(s, 64)
 | |
| 	return float64(v), err
 | |
| }
 | |
| 
 | |
| // MarshalFixed14_4 marshals float64 to SOAP "fixed.14.4" type.
 | |
| func MarshalFixed14_4(v float64) (string, error) {
 | |
| 	if v >= 1e14 || v <= -1e14 {
 | |
| 		return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
 | |
| 	}
 | |
| 	return strconv.FormatFloat(v, 'f', 4, 64), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalFixed14_4 unmarshals float64 from SOAP "fixed.14.4" type.
 | |
| func UnmarshalFixed14_4(s string) (float64, error) {
 | |
| 	v, err := strconv.ParseFloat(s, 64)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	if v >= 1e14 || v <= -1e14 {
 | |
| 		return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
 | |
| 	}
 | |
| 	return v, nil
 | |
| }
 | |
| 
 | |
| // MarshalChar marshals rune to SOAP "char" type.
 | |
| func MarshalChar(v rune) (string, error) {
 | |
| 	if v == 0 {
 | |
| 		return "", errors.New("soap char: rune 0 is not allowed")
 | |
| 	}
 | |
| 	return string(v), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalChar unmarshals rune from SOAP "char" type.
 | |
| func UnmarshalChar(s string) (rune, error) {
 | |
| 	if len(s) == 0 {
 | |
| 		return 0, errors.New("soap char: got empty string")
 | |
| 	}
 | |
| 	r, n := utf8.DecodeRune([]byte(s))
 | |
| 	if n != len(s) {
 | |
| 		return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
 | |
| 	}
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| func MarshalString(v string) (string, error) {
 | |
| 	return v, nil
 | |
| }
 | |
| 
 | |
| func UnmarshalString(v string) (string, error) {
 | |
| 	return v, nil
 | |
| }
 | |
| 
 | |
| func parseInt(s string, err *error) int {
 | |
| 	v, parseErr := strconv.ParseInt(s, 10, 64)
 | |
| 	if parseErr != nil {
 | |
| 		*err = parseErr
 | |
| 	}
 | |
| 	return int(v)
 | |
| }
 | |
| 
 | |
| var dateRegexps = []*regexp.Regexp{
 | |
| 	// yyyy[-mm[-dd]]
 | |
| 	regexp.MustCompile(`^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$`),
 | |
| 	// yyyy[mm[dd]]
 | |
| 	regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
 | |
| }
 | |
| 
 | |
| func parseDateParts(s string) (year, month, day int, err error) {
 | |
| 	var parts []string
 | |
| 	for _, re := range dateRegexps {
 | |
| 		parts = re.FindStringSubmatch(s)
 | |
| 		if parts != nil {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if parts == nil {
 | |
| 		err = fmt.Errorf("soap date: value %q is not in a recognized ISO8601 date format", s)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	year = parseInt(parts[1], &err)
 | |
| 	month = 1
 | |
| 	day = 1
 | |
| 	if len(parts[2]) != 0 {
 | |
| 		month = parseInt(parts[2], &err)
 | |
| 		if len(parts[3]) != 0 {
 | |
| 			day = parseInt(parts[3], &err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		err = fmt.Errorf("soap date: %q: %v", s, err)
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| var timeRegexps = []*regexp.Regexp{
 | |
| 	// hh[:mm[:ss]]
 | |
| 	regexp.MustCompile(`^(\d{2})(?::(\d{2})(?::(\d{2}))?)?$`),
 | |
| 	// hh[mm[ss]]
 | |
| 	regexp.MustCompile(`^(\d{2})(?:(\d{2})(?:(\d{2}))?)?$`),
 | |
| }
 | |
| 
 | |
| func parseTimeParts(s string) (hour, minute, second int, err error) {
 | |
| 	var parts []string
 | |
| 	for _, re := range timeRegexps {
 | |
| 		parts = re.FindStringSubmatch(s)
 | |
| 		if parts != nil {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if parts == nil {
 | |
| 		err = fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	hour = parseInt(parts[1], &err)
 | |
| 	if len(parts[2]) != 0 {
 | |
| 		minute = parseInt(parts[2], &err)
 | |
| 		if len(parts[3]) != 0 {
 | |
| 			second = parseInt(parts[3], &err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		err = fmt.Errorf("soap time: %q: %v", s, err)
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // (+|-)hh[[:]mm]
 | |
| var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2})(?::?(\d{2}))?$`)
 | |
| 
 | |
| func parseTimezone(s string) (offset int, err error) {
 | |
| 	if s == "Z" {
 | |
| 		return 0, nil
 | |
| 	}
 | |
| 	parts := timezoneRegexp.FindStringSubmatch(s)
 | |
| 	if parts == nil {
 | |
| 		err = fmt.Errorf("soap timezone: value %q is not in ISO8601 timezone format", s)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	offset = parseInt(parts[2], &err) * 3600
 | |
| 	if len(parts[3]) != 0 {
 | |
| 		offset += parseInt(parts[3], &err) * 60
 | |
| 	}
 | |
| 	if parts[1] == "-" {
 | |
| 		offset = -offset
 | |
| 	}
 | |
| 
 | |
| 	if err != nil {
 | |
| 		err = fmt.Errorf("soap timezone: %q: %v", s, err)
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| var completeDateTimeZoneRegexp = regexp.MustCompile(`^([^T]+)(?:T([^-+Z]+)(.+)?)?$`)
 | |
| 
 | |
| // splitCompleteDateTimeZone splits date, time and timezone apart from an
 | |
| // ISO8601 string. It does not ensure that the contents of each part are
 | |
| // correct, it merely splits on certain delimiters.
 | |
| // e.g "2010-09-08T12:15:10+0700" => "2010-09-08", "12:15:10", "+0700".
 | |
| // Timezone can only be present if time is also present.
 | |
| func splitCompleteDateTimeZone(s string) (dateStr, timeStr, zoneStr string, err error) {
 | |
| 	parts := completeDateTimeZoneRegexp.FindStringSubmatch(s)
 | |
| 	if parts == nil {
 | |
| 		err = fmt.Errorf("soap date/time/zone: value %q is not in ISO8601 datetime format", s)
 | |
| 		return
 | |
| 	}
 | |
| 	dateStr = parts[1]
 | |
| 	timeStr = parts[2]
 | |
| 	zoneStr = parts[3]
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
 | |
| // to local time, and discards the time-of-day components.
 | |
| func MarshalDate(v time.Time) (string, error) {
 | |
| 	return v.In(localLoc).Format("2006-01-02"), nil
 | |
| }
 | |
| 
 | |
| var dateFmts = []string{"2006-01-02", "20060102"}
 | |
| 
 | |
| // UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
 | |
| // date as midnight in the local time zone.
 | |
| func UnmarshalDate(s string) (time.Time, error) {
 | |
| 	year, month, day, err := parseDateParts(s)
 | |
| 	if err != nil {
 | |
| 		return time.Time{}, err
 | |
| 	}
 | |
| 	return time.Date(year, time.Month(month), day, 0, 0, 0, 0, localLoc), nil
 | |
| }
 | |
| 
 | |
| // TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
 | |
| type TimeOfDay struct {
 | |
| 	// Duration of time since midnight.
 | |
| 	FromMidnight time.Duration
 | |
| 
 | |
| 	// Set to true if Offset is specified. If false, then the timezone is
 | |
| 	// unspecified (and by ISO8601 - implies some "local" time).
 | |
| 	HasOffset bool
 | |
| 
 | |
| 	// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
 | |
| 	// non-zero, then it is regarded as a UTC offset in seconds. Note that the
 | |
| 	// sub-minutes is ignored by the marshal function.
 | |
| 	Offset int
 | |
| }
 | |
| 
 | |
| // MarshalTimeOfDay marshals TimeOfDay to the "time" type.
 | |
| func MarshalTimeOfDay(v TimeOfDay) (string, error) {
 | |
| 	d := int64(v.FromMidnight / time.Second)
 | |
| 	hour := d / 3600
 | |
| 	d = d % 3600
 | |
| 	minute := d / 60
 | |
| 	second := d % 60
 | |
| 
 | |
| 	return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
 | |
| func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
 | |
| 	t, err := UnmarshalTimeOfDayTz(s)
 | |
| 	if err != nil {
 | |
| 		return TimeOfDay{}, err
 | |
| 	} else if t.HasOffset {
 | |
| 		return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone")
 | |
| 	}
 | |
| 	return t, nil
 | |
| }
 | |
| 
 | |
| // MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
 | |
| func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
 | |
| 	d := int64(v.FromMidnight / time.Second)
 | |
| 	hour := d / 3600
 | |
| 	d = d % 3600
 | |
| 	minute := d / 60
 | |
| 	second := d % 60
 | |
| 
 | |
| 	tz := ""
 | |
| 	if v.HasOffset {
 | |
| 		if v.Offset == 0 {
 | |
| 			tz = "Z"
 | |
| 		} else {
 | |
| 			offsetMins := v.Offset / 60
 | |
| 			sign := '+'
 | |
| 			if offsetMins < 1 {
 | |
| 				offsetMins = -offsetMins
 | |
| 				sign = '-'
 | |
| 			}
 | |
| 			tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
 | |
| func UnmarshalTimeOfDayTz(s string) (tod TimeOfDay, err error) {
 | |
| 	zoneIndex := strings.IndexAny(s, "Z+-")
 | |
| 	var timePart string
 | |
| 	var hasOffset bool
 | |
| 	var offset int
 | |
| 	if zoneIndex == -1 {
 | |
| 		hasOffset = false
 | |
| 		timePart = s
 | |
| 	} else {
 | |
| 		hasOffset = true
 | |
| 		timePart = s[:zoneIndex]
 | |
| 		if offset, err = parseTimezone(s[zoneIndex:]); err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	hour, minute, second, err := parseTimeParts(timePart)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second
 | |
| 
 | |
| 	// ISO8601 special case - values up to 24:00:00 are allowed, so using
 | |
| 	// strictly greater-than for the maximum value.
 | |
| 	if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
 | |
| 		return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
 | |
| 	}
 | |
| 
 | |
| 	return TimeOfDay{
 | |
| 		FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
 | |
| 		HasOffset:    hasOffset,
 | |
| 		Offset:       offset,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // MarshalDateTime marshals time.Time to SOAP "dateTime" type. Note that this
 | |
| // converts to local time.
 | |
| func MarshalDateTime(v time.Time) (string, error) {
 | |
| 	return v.In(localLoc).Format("2006-01-02T15:04:05"), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalDateTime unmarshals time.Time from the SOAP "dateTime" type. This
 | |
| // returns a value in the local timezone.
 | |
| func UnmarshalDateTime(s string) (result time.Time, err error) {
 | |
| 	dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if len(zoneStr) != 0 {
 | |
| 		err = fmt.Errorf("soap datetime: unexpected timezone in %q", s)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	year, month, day, err := parseDateParts(dateStr)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var hour, minute, second int
 | |
| 	if len(timeStr) != 0 {
 | |
| 		hour, minute, second, err = parseTimeParts(timeStr)
 | |
| 		if err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	result = time.Date(year, time.Month(month), day, hour, minute, second, 0, localLoc)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // MarshalDateTimeTz marshals time.Time to SOAP "dateTime.tz" type.
 | |
| func MarshalDateTimeTz(v time.Time) (string, error) {
 | |
| 	return v.Format("2006-01-02T15:04:05-07:00"), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalDateTimeTz unmarshals time.Time from the SOAP "dateTime.tz" type.
 | |
| // This returns a value in the local timezone when the timezone is unspecified.
 | |
| func UnmarshalDateTimeTz(s string) (result time.Time, err error) {
 | |
| 	dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	year, month, day, err := parseDateParts(dateStr)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var hour, minute, second int
 | |
| 	var location *time.Location = localLoc
 | |
| 	if len(timeStr) != 0 {
 | |
| 		hour, minute, second, err = parseTimeParts(timeStr)
 | |
| 		if err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 		if len(zoneStr) != 0 {
 | |
| 			var offset int
 | |
| 			offset, err = parseTimezone(zoneStr)
 | |
| 			if offset == 0 {
 | |
| 				location = time.UTC
 | |
| 			} else {
 | |
| 				location = time.FixedZone("", offset)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	result = time.Date(year, time.Month(month), day, hour, minute, second, 0, location)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // MarshalBoolean marshals bool to SOAP "boolean" type.
 | |
| func MarshalBoolean(v bool) (string, error) {
 | |
| 	if v {
 | |
| 		return "1", nil
 | |
| 	}
 | |
| 	return "0", nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBoolean unmarshals bool from the SOAP "boolean" type.
 | |
| func UnmarshalBoolean(s string) (bool, error) {
 | |
| 	switch s {
 | |
| 	case "0", "false", "no":
 | |
| 		return false, nil
 | |
| 	case "1", "true", "yes":
 | |
| 		return true, nil
 | |
| 	}
 | |
| 	return false, fmt.Errorf("soap boolean: %q is not a valid boolean value", s)
 | |
| }
 | |
| 
 | |
| // MarshalBinBase64 marshals []byte to SOAP "bin.base64" type.
 | |
| func MarshalBinBase64(v []byte) (string, error) {
 | |
| 	return base64.StdEncoding.EncodeToString(v), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBinBase64 unmarshals []byte from the SOAP "bin.base64" type.
 | |
| func UnmarshalBinBase64(s string) ([]byte, error) {
 | |
| 	return base64.StdEncoding.DecodeString(s)
 | |
| }
 | |
| 
 | |
| // MarshalBinHex marshals []byte to SOAP "bin.hex" type.
 | |
| func MarshalBinHex(v []byte) (string, error) {
 | |
| 	return hex.EncodeToString(v), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBinHex unmarshals []byte from the SOAP "bin.hex" type.
 | |
| func UnmarshalBinHex(s string) ([]byte, error) {
 | |
| 	return hex.DecodeString(s)
 | |
| }
 | |
| 
 | |
| // MarshalURI marshals *url.URL to SOAP "uri" type.
 | |
| func MarshalURI(v *url.URL) (string, error) {
 | |
| 	return v.String(), nil
 | |
| }
 | |
| 
 | |
| // UnmarshalURI unmarshals *url.URL from the SOAP "uri" type.
 | |
| func UnmarshalURI(s string) (*url.URL, error) {
 | |
| 	return url.Parse(s)
 | |
| }
 |