Update server dependencies

This commit is contained in:
Hanzei 2018-09-23 08:18:35 +02:00
parent d0df44c109
commit d918262365
No known key found for this signature in database
GPG key ID: 69A2DEFD98937BA0
31 changed files with 1233 additions and 806 deletions

View file

@ -12,6 +12,7 @@ import (
const (
AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes
AUTHCODE_RESPONSE_TYPE = "code"
IMPLICIT_RESPONSE_TYPE = "token"
DEFAULT_SCOPE = "user"
)
@ -58,7 +59,7 @@ func (ad *AuthData) IsValid() *AppError {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}
if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
if len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest)
}

View file

@ -59,6 +59,9 @@ type ChannelPatch struct {
func (o *Channel) DeepCopy() *Channel {
copy := *o
if copy.SchemeId != nil {
copy.SchemeId = NewString(*o.SchemeId)
}
return &copy
}

View file

@ -397,6 +397,10 @@ func (c *Client4) GetTotalUsersStatsRoute() string {
return fmt.Sprintf(c.GetUsersRoute() + "/stats")
}
func (c *Client4) GetRedirectLocationRoute() string {
return fmt.Sprintf("/redirect_location")
}
func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) {
return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag)
}
@ -1791,6 +1795,15 @@ func (c *Client4) GetChannelByName(channelName, teamId string, etag string) (*Ch
}
}
func (c *Client4) GetChannelByNameIncludeDeleted(channelName, teamId string, etag string) (*Channel, *Response) {
if r, err := c.DoApiGet(c.GetChannelByNameRoute(channelName, teamId)+"?include_deleted=true", etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return ChannelFromJson(r.Body), BuildResponse(r)
}
}
// GetChannelByNameForTeamName returns a channel based on the provided channel name and team name strings.
func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag string) (*Channel, *Response) {
if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName), etag); err != nil {
@ -1801,6 +1814,15 @@ func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag
}
}
func (c *Client4) GetChannelByNameForTeamNameIncludeDeleted(channelName, teamName string, etag string) (*Channel, *Response) {
if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName)+"?include_deleted=true", etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return ChannelFromJson(r.Body), BuildResponse(r)
}
}
// GetChannelMembers gets a page of channel members.
func (c *Client4) GetChannelMembers(channelId string, page, perPage int, etag string) (*ChannelMembers, *Response) {
query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
@ -2118,8 +2140,27 @@ func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, et
// SearchPosts returns any posts with matching terms string.
func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*PostList, *Response) {
params := SearchParameter{
Terms: &terms,
IsOrSearch: &isOrSearch,
}
return c.SearchPostsWithParams(teamId, &params)
}
// SearchPosts returns any posts with matching terms string.
func (c *Client4) SearchPostsWithParams(teamId string, params *SearchParameter) (*PostList, *Response) {
if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", params.SearchParameterToJson()); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return PostListFromJson(r.Body), BuildResponse(r)
}
}
// SearchPosts returns any posts with matching terms string including deleted channels.
func (c *Client4) SearchPostsIncludeDeletedChannels(teamId string, terms string, isOrSearch bool) (*PostList, *Response) {
requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch}
if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", StringInterfaceToJson(requestBody)); err != nil {
if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search?include_deleted_channels=true", StringInterfaceToJson(requestBody)); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
@ -3742,3 +3783,14 @@ func (c *Client4) UpdateTeamScheme(teamId, schemeId string) (bool, *Response) {
return CheckStatusOK(r), BuildResponse(r)
}
}
// GetRedirectLocation retrieves the value of the 'Location' header of an HTTP response for a given URL.
func (c *Client4) GetRedirectLocation(urlParam, etag string) (string, *Response) {
url := fmt.Sprintf("%s?url=%s", c.GetRedirectLocationRoute(), url.QueryEscape(urlParam))
if r, err := c.DoApiGet(url, etag); err != nil {
return "", BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return MapFromJson(r.Body)["location"], BuildResponse(r)
}
}

View file

@ -6,9 +6,12 @@ package model
import (
"encoding/json"
"io"
"math"
"net"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
@ -117,6 +120,7 @@ const (
LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME = ""
SAML_SETTINGS_DEFAULT_ID_ATTRIBUTE = ""
SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = ""
@ -1122,6 +1126,7 @@ type TeamSettings struct {
MaxNotificationsPerChannel *int64
EnableConfirmNotificationsToChannel *bool
TeammateNameDisplay *string
ExperimentalViewArchivedChannels *bool
ExperimentalEnableAutomaticReplies *bool
ExperimentalHideTownSquareinLHS *bool
ExperimentalTownSquareIsReadOnly *bool
@ -1251,6 +1256,9 @@ func (s *TeamSettings) SetDefaults() {
s.EnableUserCreation = NewBool(true)
}
if s.ExperimentalViewArchivedChannels == nil {
s.ExperimentalViewArchivedChannels = NewBool(false)
}
}
type ClientRequirements struct {
@ -1449,8 +1457,9 @@ func (s *LocalizationSettings) SetDefaults() {
type SamlSettings struct {
// Basic
Enable *bool
EnableSyncWithLdap *bool
Enable *bool
EnableSyncWithLdap *bool
EnableSyncWithLdapIncludeAuth *bool
Verify *bool
Encrypt *bool
@ -1467,6 +1476,7 @@ type SamlSettings struct {
PrivateKeyFile *string
// User Mapping
IdAttribute *string
FirstNameAttribute *string
LastNameAttribute *string
EmailAttribute *string
@ -1491,6 +1501,10 @@ func (s *SamlSettings) SetDefaults() {
s.EnableSyncWithLdap = NewBool(false)
}
if s.EnableSyncWithLdapIncludeAuth == nil {
s.EnableSyncWithLdapIncludeAuth = NewBool(false)
}
if s.Verify == nil {
s.Verify = NewBool(true)
}
@ -1535,6 +1549,10 @@ func (s *SamlSettings) SetDefaults() {
s.LoginButtonText = NewString(USER_AUTH_SERVICE_SAML_TEXT)
}
if s.IdAttribute == nil {
s.IdAttribute = NewString(SAML_SETTINGS_DEFAULT_ID_ATTRIBUTE)
}
if s.FirstNameAttribute == nil {
s.FirstNameAttribute = NewString(SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE)
}
@ -2352,7 +2370,15 @@ func (ss *ServiceSettings) isValid() *AppError {
}
}
if len(*ss.ListenAddress) == 0 {
host, port, err := net.SplitHostPort(*ss.ListenAddress)
var isValidHost bool
if host == "" {
isValidHost = true
} else {
isValidHost = (net.ParseIP(host) != nil) || IsDomainName(host)
}
portInt, err := strconv.Atoi(port)
if err != nil || !isValidHost || portInt < 0 || portInt > math.MaxUint16 {
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
}

View file

@ -5,6 +5,7 @@ package model
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
@ -151,6 +152,9 @@ type ManifestWebapp struct {
// The path to your webapp bundle. This should be relative to the root of your bundle and the
// location of the manifest file.
BundlePath string `json:"bundle_path" yaml:"bundle_path"`
// BundleHash is the 64-bit FNV-1a hash of the webapp bundle, computed when the plugin is loaded
BundleHash []byte `json:"-"`
}
func (m *Manifest) ToJson() string {
@ -188,7 +192,7 @@ func (m *Manifest) ClientManifest() *Manifest {
if cm.Webapp != nil {
cm.Webapp = new(ManifestWebapp)
*cm.Webapp = *m.Webapp
cm.Webapp.BundlePath = "/static/" + m.Id + "_bundle.js"
cm.Webapp.BundlePath = "/static/" + m.Id + "/" + fmt.Sprintf("%s_%x_bundle.js", m.Id, m.Webapp.BundleHash)
}
return cm
}

View file

@ -109,7 +109,6 @@ func (a *OAuthApp) PreUpdate() {
a.UpdateAt = GetMillis()
}
// ToJson convert a User to a json string
func (a *OAuthApp) ToJson() string {
b, _ := json.Marshal(a)
return string(b)
@ -135,7 +134,6 @@ func (a *OAuthApp) IsValidRedirectURL(url string) bool {
return false
}
// OAuthAppFromJson will decode the input and return a User
func OAuthAppFromJson(data io.Reader) *OAuthApp {
var app *OAuthApp
json.NewDecoder(data).Decode(&app)

View file

@ -50,6 +50,8 @@ const (
PROPS_ADD_CHANNEL_MEMBER = "add_channel_member"
POST_PROPS_ADDED_USER_ID = "addedUserId"
POST_PROPS_DELETE_BY = "deleteBy"
POST_ACTION_TYPE_BUTTON = "button"
POST_ACTION_TYPE_SELECT = "select"
)
type Post struct {
@ -94,6 +96,12 @@ type PostPatch struct {
HasReactions *bool `json:"has_reactions"`
}
type SearchParameter struct {
Terms *string `json:"terms"`
IsOrSearch *bool `json:"is_or_search"`
TimeZoneOffset *int `json:"time_zone_offset"`
}
func (o *PostPatch) WithRewrittenImageURLs(f func(string) string) *PostPatch {
copy := *o
if copy.Message != nil {
@ -108,20 +116,35 @@ type PostForIndexing struct {
ParentCreateAt *int64 `json:"parent_create_at"`
}
type DoPostActionRequest struct {
SelectedOption string `json:"selected_option"`
}
type PostAction struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
DataSource string `json:"data_source"`
Options []*PostActionOptions `json:"options"`
Integration *PostActionIntegration `json:"integration,omitempty"`
}
type PostActionOptions struct {
Text string `json:"text"`
Value string `json:"value"`
}
type PostActionIntegration struct {
URL string `json:"url,omitempty"`
Context StringInterface `json:"context,omitempty"`
}
type PostActionIntegrationRequest struct {
UserId string `json:"user_id"`
Context StringInterface `json:"context,omitempty"`
UserId string `json:"user_id"`
PostId string `json:"post_id"`
Type string `json:"type"`
DataSource string `json:"data_source"`
Context StringInterface `json:"context,omitempty"`
}
type PostActionIntegrationResponse struct {
@ -342,6 +365,26 @@ func PostPatchFromJson(data io.Reader) *PostPatch {
return &post
}
func (o *SearchParameter) SearchParameterToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func SearchParameterFromJson(data io.Reader) *SearchParameter {
decoder := json.NewDecoder(data)
var searchParam SearchParameter
err := decoder.Decode(&searchParam)
if err != nil {
return nil
}
return &searchParam
}
func (o *Post) ChannelMentions() []string {
return ChannelMentions(o.Message)
}
@ -351,6 +394,29 @@ func (r *PostActionIntegrationRequest) ToJson() string {
return string(b)
}
func PostActionIntegrationRequesteFromJson(data io.Reader) *PostActionIntegrationRequest {
var o *PostActionIntegrationRequest
err := json.NewDecoder(data).Decode(&o)
if err != nil {
return nil
}
return o
}
func (r *PostActionIntegrationResponse) ToJson() string {
b, _ := json.Marshal(r)
return string(b)
}
func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse {
var o *PostActionIntegrationResponse
err := json.NewDecoder(data).Decode(&o)
if err != nil {
return nil
}
return o
}
func (o *Post) Attachments() []*SlackAttachment {
if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
return attachments
@ -431,6 +497,12 @@ func (o *PostEphemeral) ToUnsanitizedJson() string {
return string(b)
}
func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest {
var o *DoPostActionRequest
json.NewDecoder(data).Decode(&o)
return o
}
// RewriteImageURLs takes a message and returns a copy that has all of the image URLs replaced
// according to the function f. For each image URL, f will be invoked, and the resulting markdown
// will contain the URL returned by that invocation instead.

View file

@ -6,20 +6,50 @@ package model
import (
"regexp"
"strings"
"time"
)
var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`)
var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`)
type SearchParams struct {
Terms string
IsHashtag bool
InChannels []string
FromUsers []string
OrTerms bool
Terms string
IsHashtag bool
InChannels []string
FromUsers []string
AfterDate string
BeforeDate string
OnDate string
OrTerms bool
IncludeDeletedChannels bool
TimeZoneOffset int
}
var searchFlags = [...]string{"from", "channel", "in"}
// Returns the epoch timestamp of the start of the day specified by SearchParams.AfterDate
func (p *SearchParams) GetAfterDateMillis() int64 {
date := ParseDateFilterToTime(p.AfterDate)
// travel forward 1 day
oneDay := time.Hour * 24
afterDate := date.Add(oneDay)
return GetStartOfDayMillis(afterDate, p.TimeZoneOffset)
}
// Returns the epoch timestamp of the end of the day specified by SearchParams.BeforeDate
func (p *SearchParams) GetBeforeDateMillis() int64 {
date := ParseDateFilterToTime(p.BeforeDate)
// travel back 1 day
oneDay := time.Hour * -24
beforeDate := date.Add(oneDay)
return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset)
}
// Returns the epoch timestamps of the start and end of the day specified by SearchParams.OnDate
func (p *SearchParams) GetOnDateMillis() (int64, int64) {
date := ParseDateFilterToTime(p.OnDate)
return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
}
var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on"}
func splitWords(text string) []string {
words := []string{}
@ -100,7 +130,7 @@ func parseSearchFlags(input []string) ([]string, [][2]string) {
return words, flags
}
func ParseSearchParams(text string) []*SearchParams {
func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
words, flags := parseSearchFlags(splitWords(text))
hashtagTermList := []string{}
@ -119,6 +149,9 @@ func ParseSearchParams(text string) []*SearchParams {
inChannels := []string{}
fromUsers := []string{}
afterDate := ""
beforeDate := ""
onDate := ""
for _, flagPair := range flags {
flag := flagPair[0]
@ -128,6 +161,12 @@ func ParseSearchParams(text string) []*SearchParams {
inChannels = append(inChannels, value)
} else if flag == "from" {
fromUsers = append(fromUsers, value)
} else if flag == "after" {
afterDate = value
} else if flag == "before" {
beforeDate = value
} else if flag == "on" {
onDate = value
}
}
@ -135,29 +174,41 @@ func ParseSearchParams(text string) []*SearchParams {
if len(plainTerms) > 0 {
paramsList = append(paramsList, &SearchParams{
Terms: plainTerms,
IsHashtag: false,
InChannels: inChannels,
FromUsers: fromUsers,
Terms: plainTerms,
IsHashtag: false,
InChannels: inChannels,
FromUsers: fromUsers,
AfterDate: afterDate,
BeforeDate: beforeDate,
OnDate: onDate,
TimeZoneOffset: timeZoneOffset,
})
}
if len(hashtagTerms) > 0 {
paramsList = append(paramsList, &SearchParams{
Terms: hashtagTerms,
IsHashtag: true,
InChannels: inChannels,
FromUsers: fromUsers,
Terms: hashtagTerms,
IsHashtag: true,
InChannels: inChannels,
FromUsers: fromUsers,
AfterDate: afterDate,
BeforeDate: beforeDate,
OnDate: onDate,
TimeZoneOffset: timeZoneOffset,
})
}
// special case for when no terms are specified but we still have a filter
if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0) {
if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0 || len(afterDate) != 0 || len(beforeDate) != 0 || len(onDate) != 0) {
paramsList = append(paramsList, &SearchParams{
Terms: "",
IsHashtag: false,
InChannels: inChannels,
FromUsers: fromUsers,
Terms: "",
IsHashtag: false,
InChannels: inChannels,
FromUsers: fromUsers,
AfterDate: afterDate,
BeforeDate: beforeDate,
OnDate: onDate,
TimeZoneOffset: timeZoneOffset,
})
}

View file

@ -135,6 +135,20 @@ func (me *Session) GetUserRoles() []string {
return strings.Fields(me.Roles)
}
func (me *Session) GenerateCSRF() string {
token := NewId()
me.AddProp("csrf", token)
return token
}
func (me *Session) GetCSRF() string {
if me.Props == nil {
return ""
}
return me.Props["csrf"]
}
func SessionsToJson(o []*Session) string {
if b, err := json.Marshal(o); err != nil {
return "[]"

View file

@ -16,6 +16,7 @@ const (
SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId"
SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime"
SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey"
SYSTEM_INSTALLATION_DATE_KEY = "InstallationDate"
)
type System struct {

View file

@ -47,6 +47,7 @@ type TeamPatch struct {
DisplayName *string `json:"display_name"`
Description *string `json:"description"`
CompanyName *string `json:"company_name"`
AllowedDomains *string `json:"allowed_domains"`
InviteId *string `json:"invite_id"`
AllowOpenInvite *bool `json:"allow_open_invite"`
}
@ -241,7 +242,6 @@ func CleanTeamName(s string) string {
func (o *Team) Sanitize() {
o.Email = ""
o.AllowedDomains = ""
}
func (t *Team) Patch(patch *TeamPatch) {
@ -257,6 +257,10 @@ func (t *Team) Patch(patch *TeamPatch) {
t.CompanyName = *patch.CompanyName
}
if patch.AllowedDomains != nil {
t.AllowedDomains = *patch.AllowedDomains
}
if patch.InviteId != nil {
t.InviteId = *patch.InviteId
}

View file

@ -132,7 +132,7 @@ func (u *User) IsValid() *AppError {
return InvalidUserError("username", u.Id)
}
if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 {
if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 || !IsValidEmail(u.Email) {
return InvalidUserError("email", u.Id)
}

View file

@ -15,11 +15,9 @@ import (
"net/http"
"net/mail"
"net/url"
"reflect"
"regexp"
"strconv"
"strings"
"testing"
"time"
"unicode"
@ -148,6 +146,46 @@ func GetMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
// GetMillisForTime is a convience method to get milliseconds since epoch for provided Time.
func GetMillisForTime(thisTime time.Time) int64 {
return thisTime.UnixNano() / int64(time.Millisecond)
}
// ParseDateFilterToTime is a convience method to get Time from string
func ParseDateFilterToTime(filterString string) time.Time {
resultTime, err := time.Parse("2006-01-02", PadDateStringZeros(filterString))
if err != nil {
return time.Now()
}
return resultTime
}
// PadDateStringZeros is a convience method to pad 2 digit date parts with zeros to meet ISO 8601 format
func PadDateStringZeros(dateString string) string {
parts := strings.Split(dateString, "-")
for index, part := range parts {
if len(part) == 1 {
parts[index] = "0" + part
}
}
dateString = strings.Join(parts[:], "-")
return dateString
}
// GetStartOfDayMillis is a convience method to get milliseconds since epoch for provided date's start of day
func GetStartOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 {
localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset)
resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, localSearchTimeZone)
return GetMillisForTime(resultTime)
}
// GetEndOfDayMillis is a convience method to get milliseconds since epoch for provided date's end of day
func GetEndOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 {
localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset)
resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 23, 59, 59, 999999999, localSearchTimeZone)
return GetMillisForTime(resultTime)
}
func CopyStringMap(originalMap map[string]string) map[string]string {
copyMap := make(map[string]string)
for k, v := range originalMap {
@ -263,7 +301,7 @@ func GetServerIpAddress() string {
} else {
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() {
if ip.IP.To4() != nil {
return ip.IP.String()
}
@ -279,16 +317,18 @@ func IsLower(s string) bool {
}
func IsValidEmail(email string) bool {
if !IsLower(email) {
return false
}
if _, err := mail.ParseAddress(email); err == nil {
return true
if addr, err := mail.ParseAddress(email); err != nil {
return false
} else if addr.Name != "" {
// mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow
return false
}
return false
return true
}
var reservedName = []string{
@ -480,59 +520,56 @@ func IsValidId(value string) bool {
return true
}
// checkNowhereNil checks that the given interface value is not nil, and if a struct, that all of
// its public fields are also nowhere nil
func checkNowhereNil(t *testing.T, name string, value interface{}) bool {
if value == nil {
// Copied from https://golang.org/src/net/dnsclient.go#L119
func IsDomainName(s string) bool {
// See RFC 1035, RFC 3696.
// Presentation format has dots before every label except the first, and the
// terminal empty label is optional here because we assume fully-qualified
// (absolute) input. We must therefore reserve space for the first and last
// labels' length octets in wire format, where they are necessary and the
// maximum total length is 255.
// So our _effective_ maximum is 253, but 254 is not rejected if the last
// character is a dot.
l := len(s)
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
return false
}
v := reflect.ValueOf(value)
switch v.Type().Kind() {
case reflect.Ptr:
if v.IsNil() {
t.Logf("%s was nil", name)
last := byte('.')
ok := false // Ok once we've seen a letter.
partlen := 0
for i := 0; i < len(s); i++ {
c := s[i]
switch {
default:
return false
}
return checkNowhereNil(t, fmt.Sprintf("(*%s)", name), v.Elem().Interface())
case reflect.Map:
if v.IsNil() {
t.Logf("%s was nil", name)
return false
}
// Don't check map values
return true
case reflect.Struct:
nowhereNil := true
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
// Ignore unexported fields
if v.Type().Field(i).PkgPath != "" {
continue
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
ok = true
partlen++
case '0' <= c && c <= '9':
// fine
partlen++
case c == '-':
// Byte before dash cannot be dot.
if last == '.' {
return false
}
nowhereNil = nowhereNil && checkNowhereNil(t, fmt.Sprintf("%s.%s", name, v.Type().Field(i).Name), f.Interface())
partlen++
case c == '.':
// Byte before dot cannot be dot, dash.
if last == '.' || last == '-' {
return false
}
if partlen > 63 || partlen == 0 {
return false
}
partlen = 0
}
return nowhereNil
case reflect.Array:
fallthrough
case reflect.Chan:
fallthrough
case reflect.Func:
fallthrough
case reflect.Interface:
fallthrough
case reflect.UnsafePointer:
t.Logf("unhandled field %s, type: %s", name, v.Type().Kind())
return false
default:
return true
last = c
}
if last == '-' || partlen > 63 {
return false
}
return ok
}

View file

@ -13,6 +13,8 @@ import (
// It should be maintained in chronological order with most current
// release at the front of the list.
var versions = []string{
"5.3.0",
"5.2.0",
"5.1.0",
"5.0.0",
"4.10.0",