ldap

package
v0.0.0-...-5c3d2fb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2017 License: BSD-3-Clause Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	TagEOC              = 0x00
	TagBoolean          = 0x01
	TagInteger          = 0x02
	TagBitString        = 0x03
	TagOctetString      = 0x04
	TagNULL             = 0x05
	TagObjectIdentifier = 0x06
	TagObjectDescriptor = 0x07
	TagExternal         = 0x08
	TagRealFloat        = 0x09
	TagEnumerated       = 0x0a
	TagEmbeddedPDV      = 0x0b
	TagUTF8String       = 0x0c
	TagRelativeOID      = 0x0d
	TagSequence         = 0x10
	TagSet              = 0x11
	TagNumericString    = 0x12
	TagPrintableString  = 0x13
	TagT61String        = 0x14
	TagVideotexString   = 0x15
	TagIA5String        = 0x16
	TagUTCTime          = 0x17
	TagGeneralizedTime  = 0x18
	TagGraphicString    = 0x19
	TagVisibleString    = 0x1a
	TagGeneralString    = 0x1b
	TagUniversalString  = 0x1c
	TagCharacterString  = 0x1d
	TagBMPString        = 0x1e
)
View Source
const (
	OIDContentSynchControl              = "1.3.6.1.4.1.4203.1.9.1.1" // https://tools.ietf.org/html/rfc4533
	OIDProxiedAuthControl               = "2.16.840.1.113730.3.4.18" // https://tools.ietf.org/html/rfc4370
	OIDNamedSubordinateReferenceControl = "2.16.840.1.113730.3.4.2"  // https://tools.ietf.org/html/rfc3296
)

Controls

View Source
const (
	OIDCancel         = "1.3.6.1.1.8"             // https://tools.ietf.org/html/rfc3909
	OIDStartTLS       = "1.3.6.1.4.1.1466.20037"  // http://www.iana.org/go/rfc4511 - http://www.iana.org/go/rfc4513
	OIDPasswordModify = "1.3.6.1.4.1.4203.1.11.1" // http://www.iana.org/go/rfc3062
	OIDWhoAmI         = "1.3.6.1.4.1.4203.1.11.3" // http://www.iana.org/go/rfc4532
)

Extensions

View Source
const (
	OIDModifyIncrement          = "1.3.6.1.1.14"           // http://www.iana.org/go/rfc4525
	OIDAllOperationalAttributes = "1.3.6.1.4.1.4203.1.5.1" // https://www.rfc-editor.org/rfc/rfc3673.txt
	OIDAttributesByObjectClass  = "1.3.6.1.4.1.4203.1.5.2" // https://tools.ietf.org/html/rfc4529
	OIDTrueFalseFilters         = "1.3.6.1.4.1.4203.1.5.3" // https://tools.ietf.org/html/rfc4526
	OIDLanguageTagOptions       = "1.3.6.1.4.1.4203.1.5.4" // https://tools.ietf.org/html/rfc3866
	OIDLanguageRangeOptions     = "1.3.6.1.4.1.4203.1.5.5" // http://tools.ietf.org/html/rfc3866
)

Features

View Source
const (
	ApplicationBindRequest           = 0
	ApplicationBindResponse          = 1
	ApplicationUnbindRequest         = 2
	ApplicationSearchRequest         = 3
	ApplicationSearchResultEntry     = 4
	ApplicationSearchResultDone      = 5
	ApplicationModifyRequest         = 6
	ApplicationModifyResponse        = 7
	ApplicationAddRequest            = 8
	ApplicationAddResponse           = 9
	ApplicationDelRequest            = 10
	ApplicationDelResponse           = 11
	ApplicationModifyDNRequest       = 12
	ApplicationModifyDNResponse      = 13
	ApplicationCompareRequest        = 14
	ApplicationCompareResponse       = 15
	ApplicationAbandonRequest        = 16
	ApplicationSearchResultReference = 19
	ApplicationExtendedRequest       = 23
	ApplicationExtendedResponse      = 24
)

Variables

View Source
var ApplicationMap = map[uint8]string{
	ApplicationBindRequest:           "Bind Request",
	ApplicationBindResponse:          "Bind Response",
	ApplicationUnbindRequest:         "Unbind Request",
	ApplicationSearchRequest:         "Search Request",
	ApplicationSearchResultEntry:     "Search Result Entry",
	ApplicationSearchResultDone:      "Search Result Done",
	ApplicationModifyRequest:         "Modify Request",
	ApplicationModifyResponse:        "Modify Response",
	ApplicationAddRequest:            "Add Request",
	ApplicationAddResponse:           "Add Response",
	ApplicationDelRequest:            "Del Request",
	ApplicationDelResponse:           "Del Response",
	ApplicationModifyDNRequest:       "Modify DN Request",
	ApplicationModifyDNResponse:      "Modify DN Response",
	ApplicationCompareRequest:        "Compare Request",
	ApplicationCompareResponse:       "Compare Response",
	ApplicationAbandonRequest:        "Abandon Request",
	ApplicationSearchResultReference: "Search Result Reference",
	ApplicationExtendedRequest:       "Extended Request",
	ApplicationExtendedResponse:      "Extended Response",
}
View Source
var ClassNames = map[Class]string{
	ClassUniversal:   "Universal",
	ClassApplication: "Application",
	ClassContext:     "Context",
	ClassPrivate:     "Private",
}
View Source
var DerefMap = map[DerefAliases]string{
	NeverDerefAliases:   "NeverDerefAliases",
	DerefInSearching:    "DerefInSearching",
	DerefFindingBaseObj: "DerefFindingBaseObj",
	DerefAlways:         "DerefAlways",
}
View Source
var ErrAlreadyTLS = errors.New("ldap: connection already using TLS")

ErrAlreadyTLS is returned when trying to start a TLS connection when the connection is already using TLS

View Source
var ResultCodeMap = map[ResultCode]string{
	ResultSuccess:                      "Success",
	ResultOperationsError:              "Operations Error",
	ResultProtocolError:                "Protocol Error",
	ResultTimeLimitExceeded:            "Time Limit Exceeded",
	ResultSizeLimitExceeded:            "Size Limit Exceeded",
	ResultCompareFalse:                 "Compare False",
	ResultCompareTrue:                  "Compare True",
	ResultAuthMethodNotSupported:       "Auth Method Not Supported",
	ResultStrongAuthRequired:           "Strong Auth Required",
	ResultReferral:                     "Referral",
	ResultAdminLimitExceeded:           "Admin Limit Exceeded",
	ResultUnavailableCriticalExtension: "Unavailable Critical Extension",
	ResultConfidentialityRequired:      "Confidentiality Required",
	ResultSaslBindInProgress:           "Sasl Bind In Progress",
	ResultNoSuchAttribute:              "No Such Attribute",
	ResultUndefinedAttributeType:       "Undefined Attribute Type",
	ResultInappropriateMatching:        "Inappropriate Matching",
	ResultConstraintViolation:          "Constraint Violation",
	ResultAttributeOrValueExists:       "Attribute Or Value Exists",
	ResultInvalidAttributeSyntax:       "Invalid Attribute Syntax",
	ResultNoSuchObject:                 "No Such Object",
	ResultAliasProblem:                 "Alias Problem",
	ResultInvalidDNSyntax:              "Invalid DN Syntax",
	ResultAliasDereferencingProblem:    "Alias Dereferencing Problem",
	ResultInappropriateAuthentication:  "Inappropriate Authentication",
	ResultInvalidCredentials:           "Invalid Credentials",
	ResultInsufficientAccessRights:     "Insufficient Access Rights",
	ResultBusy:                         "Busy",
	ResultUnavailable:                  "Unavailable",
	ResultUnwillingToPerform:           "Unwilling To Perform",
	ResultLoopDetect:                   "Loop Detect",
	ResultNamingViolation:              "Naming Violation",
	ResultObjectClassViolation:         "Object Class Violation",
	ResultNotAllowedOnNonLeaf:          "Not Allowed On Non Leaf",
	ResultNotAllowedOnRDN:              "Not Allowed On RDN",
	ResultEntryAlreadyExists:           "Entry Already Exists",
	ResultObjectClassModsProhibited:    "Object Class Mods Prohibited",
	ResultAffectsMultipleDSAs:          "Affects Multiple DSAs",
	ResultOther:                        "Other",
}
View Source
var RootDSE = map[string][]string{
	"supportedLDAPVersion": []string{
		"3",
	},
	"supportedFeatures": []string{
		OIDModifyIncrement,
		OIDAllOperationalAttributes,
	},
	"supportedExtension": []string{
		OIDWhoAmI,
		OIDPasswordModify,
	},
	"supportedSASLMechanisms": []string{},
}
View Source
var ScopeMap = map[Scope]string{
	ScopeBaseObject:   "Base Object",
	ScopeSingleLevel:  "Single Level",
	ScopeWholeSubtree: "Whole Subtree",
	ScopeChildren:     "Children",
}
View Source
var TagNames = map[int]string{
	TagEOC:              "EOC (End-of-Content)",
	TagBoolean:          "Boolean",
	TagInteger:          "Integer",
	TagBitString:        "Bit String",
	TagOctetString:      "Octet String",
	TagNULL:             "NULL",
	TagObjectIdentifier: "Object Identifier",
	TagObjectDescriptor: "Object Descriptor",
	TagExternal:         "External",
	TagRealFloat:        "Real (float)",
	TagEnumerated:       "Enumerated",
	TagEmbeddedPDV:      "Embedded PDV",
	TagUTF8String:       "UTF8 String",
	TagRelativeOID:      "Relative-OID",
	TagSequence:         "Sequence and Sequence of",
	TagSet:              "Set and Set OF",
	TagNumericString:    "Numeric String",
	TagPrintableString:  "Printable String",
	TagT61String:        "T61 String",
	TagVideotexString:   "Videotex String",
	TagIA5String:        "IA5 String",
	TagUTCTime:          "UTC Time",
	TagGeneralizedTime:  "Generalized Time",
	TagGraphicString:    "Graphic String",
	TagVisibleString:    "Visible String",
	TagGeneralString:    "General String",
	TagUniversalString:  "Universal String",
	TagCharacterString:  "Character String",
	TagBMPString:        "BMP String",
}

Functions

func IsPrintable

func IsPrintable(v []byte) bool

Types

type AND

type AND struct {
	Filters []Filter
}

func (*AND) Encode

func (a *AND) Encode() (*Packet, error)

func (*AND) String

func (a *AND) String() string

type AddRequest

type AddRequest struct {
	DN         string
	Attributes map[string][][]byte
}

type AddResponse

type AddResponse struct {
	BaseResponse
}

func (*AddResponse) WritePackets

func (r *AddResponse) WritePackets(w io.Writer, msgID int) error

type ApproxMatch

type ApproxMatch AttributeValueAssertion

func (*ApproxMatch) Encode

func (f *ApproxMatch) Encode() (*Packet, error)

func (*ApproxMatch) String

func (f *ApproxMatch) String() string

type AttributeValueAssertion

type AttributeValueAssertion struct {
	Attribute string
	Value     []byte
}

type Backend

type Backend interface {
	Add(Context, *AddRequest) (*AddResponse, error)
	Bind(Context, *BindRequest) (*BindResponse, error)
	Connect(remoteAddr net.Addr) (Context, error)
	Delete(Context, *DeleteRequest) (*DeleteResponse, error)
	Disconnect(Context)
	ExtendedRequest(Context, *ExtendedRequest) (*ExtendedResponse, error)
	Modify(Context, *ModifyRequest) (*ModifyResponse, error)
	ModifyDN(Context, *ModifyDNRequest) (*ModifyDNResponse, error)
	PasswordModify(Context, *PasswordModifyRequest) ([]byte, error)
	Search(Context, *SearchRequest) (*SearchResponse, error)
	Whoami(Context) (string, error)
}

Backend is implemented by an LDAP database to provide the backing store

var DebugBackend Backend = debugBackend{}

DebugBackend is an implementation of a server backend that prints out requests

type BaseResponse

type BaseResponse struct {
	MessageType int
	Code        ResultCode
	MatchedDN   string
	Message     string
}

func (*BaseResponse) Err

func (r *BaseResponse) Err() error

func (*BaseResponse) Error

func (r *BaseResponse) Error() string

func (*BaseResponse) NewPacket

func (r *BaseResponse) NewPacket() *Packet

func (*BaseResponse) WritePackets

func (r *BaseResponse) WritePackets(w io.Writer, msgID int) error

type BindRequest

type BindRequest struct {
	DN       string
	Password []byte
}

func (*BindRequest) WritePackets

func (r *BindRequest) WritePackets(w io.Writer, msgID int) error

type BindResponse

type BindResponse struct {
	BaseResponse
}

func (*BindResponse) WritePackets

func (r *BindResponse) WritePackets(w io.Writer, msgID int) error

type Class

type Class byte
const (
	ClassUniversal   Class = 0
	ClassApplication Class = 1
	ClassContext     Class = 2
	ClassPrivate     Class = 3
)

func (Class) String

func (c Class) String() string

type Client

type Client struct {
	// contains filtered or unexported fields
}

func Dial

func Dial(network, address string) (*Client, error)

Dial connects to a server that is not using TLS.

func DialTLS

func DialTLS(network, address string, config *tls.Config) (*Client, error)

DialTLS connects to a server that is using TLS.

func NewClient

func NewClient(cn net.Conn, isTLS bool) *Client

NewClient returns a new initialized client using the provided existing connection. The provided connection should be considered owned by the Client and not used after this call.

func (*Client) Bind

func (c *Client) Bind(dn string, pass []byte) error

Bind authenticates using the provided dn and password.

func (*Client) Close

func (c *Client) Close() error

Close closes the underlying connection to the server

func (*Client) Delete

func (c *Client) Delete(dn string) error

Delete a node

func (*Client) Modify

func (c *Client) Modify(dn string, mods []*Mod) error

Modify operation allows a client to request that a modification of an entry be performed on its behalf by a server.

func (*Client) Search

func (c *Client) Search(req *SearchRequest) ([]*SearchResult, error)

Search performs a search query against the LDAP database.

func (*Client) StartTLS

func (c *Client) StartTLS(config *tls.Config) error

StartTLS requests a TLS connection from the server. It must not be called concurrently with other requests.

func (*Client) WhoAmI

func (c *Client) WhoAmI() (string, error)

WhoAmI returns the authzId for the authenticated user on the connection. https://tools.ietf.org/html/rfc4532

type Context

type Context interface{}

Context is passed created by and passed back to a server backend to provide state for a client connection.

type DeleteRequest

type DeleteRequest struct {
	DN string
}

func (*DeleteRequest) WritePackets

func (r *DeleteRequest) WritePackets(w io.Writer, msgID int) error

type DeleteResponse

type DeleteResponse struct {
	BaseResponse
}

func (*DeleteResponse) WritePackets

func (r *DeleteResponse) WritePackets(w io.Writer, msgID int) error

type DerefAliases

type DerefAliases int
const (
	NeverDerefAliases   DerefAliases = 0
	DerefInSearching    DerefAliases = 1
	DerefFindingBaseObj DerefAliases = 2
	DerefAlways         DerefAliases = 3
)

func (DerefAliases) String

func (d DerefAliases) String() string

type EqualityMatch

type EqualityMatch AttributeValueAssertion

func (*EqualityMatch) Encode

func (f *EqualityMatch) Encode() (*Packet, error)

func (*EqualityMatch) String

func (f *EqualityMatch) String() string

type ErrFilterSyntaxError

type ErrFilterSyntaxError struct {
	Pos int
	Msg string
}

func (*ErrFilterSyntaxError) Error

func (e *ErrFilterSyntaxError) Error() string

type ErrInvalidBEREncoding

type ErrInvalidBEREncoding string

func (ErrInvalidBEREncoding) Error

func (e ErrInvalidBEREncoding) Error() string

type ErrProtocolError

type ErrProtocolError string

func (ErrProtocolError) Error

func (e ErrProtocolError) Error() string

type ErrUnsupportedRequestTag

type ErrUnsupportedRequestTag int

func (ErrUnsupportedRequestTag) Error

func (e ErrUnsupportedRequestTag) Error() string

type ExtendedRequest

type ExtendedRequest struct {
	Name  string
	Value []byte
}

func (*ExtendedRequest) WritePackets

func (r *ExtendedRequest) WritePackets(w io.Writer, msgID int) error

type ExtendedResponse

type ExtendedResponse struct {
	BaseResponse
	Name  string
	Value []byte
}

func (*ExtendedResponse) WritePackets

func (r *ExtendedResponse) WritePackets(w io.Writer, msgID int) error

type ExtensibleMatch

type ExtensibleMatch struct {
	MatchingRule string // optional
	Attribute    string
	Value        string
	DNAttributes bool
}

type Filter

type Filter interface {
	String() string
	Encode() (*Packet, error)
}

func ParseFilter

func ParseFilter(filter string) (Filter, error)

type GreaterOrEqual

type GreaterOrEqual AttributeValueAssertion

func (*GreaterOrEqual) Encode

func (f *GreaterOrEqual) Encode() (*Packet, error)

func (*GreaterOrEqual) String

func (f *GreaterOrEqual) String() string

type LessOrEqual

type LessOrEqual AttributeValueAssertion

func (*LessOrEqual) Encode

func (f *LessOrEqual) Encode() (*Packet, error)

func (*LessOrEqual) String

func (f *LessOrEqual) String() string

type Mod

type Mod struct {
	Type   ModType
	Name   string
	Values [][]byte
}

type ModType

type ModType int
const (
	Add       ModType = 0
	Delete    ModType = 1
	Replace   ModType = 2
	Increment ModType = 3
)

func (ModType) String

func (mt ModType) String() string

type ModifyDNRequest

type ModifyDNRequest struct {
	DN           string
	NewRDN       string
	DeleteOldRDN bool
	NewSuperior  string
}

type ModifyDNResponse

type ModifyDNResponse struct {
	BaseResponse
}

func (*ModifyDNResponse) WritePackets

func (r *ModifyDNResponse) WritePackets(w io.Writer, msgID int) error

type ModifyRequest

type ModifyRequest struct {
	DN   string
	Mods []*Mod
}

func (*ModifyRequest) WritePackets

func (r *ModifyRequest) WritePackets(w io.Writer, msgID int) error

type ModifyResponse

type ModifyResponse struct {
	BaseResponse
}

func (*ModifyResponse) WritePackets

func (r *ModifyResponse) WritePackets(w io.Writer, msgID int) error

type NOT

type NOT struct {
	Filter
}

func (*NOT) Encode

func (n *NOT) Encode() (*Packet, error)

func (*NOT) String

func (n *NOT) String() string

type OR

type OR struct {
	Filters []Filter
}

func (*OR) Encode

func (o *OR) Encode() (*Packet, error)

func (*OR) String

func (o *OR) String() string

type Packet

type Packet struct {
	Class     Class
	Primitive bool // true=primitive, false=constructed
	Tag       int
	Value     interface{}
	Items     []*Packet
}

func NewPacket

func NewPacket(class Class, primitive bool, tag int, value interface{}) *Packet

func NewRequestPacket

func NewRequestPacket(msgID int) *Packet

func NewResponsePacket

func NewResponsePacket(msgID int) *Packet

func ParsePacket

func ParsePacket(buf []byte) (*Packet, int, error)

func ReadPacket

func ReadPacket(rd io.Reader) (*Packet, int, error)

func (*Packet) AddItem

func (p *Packet) AddItem(it *Packet) *Packet

func (*Packet) Bool

func (p *Packet) Bool() (bool, bool)

func (*Packet) Bytes

func (p *Packet) Bytes() ([]byte, bool)

func (*Packet) Encode

func (p *Packet) Encode() ([]byte, error)

func (*Packet) Format

func (p *Packet) Format(w io.Writer) error

func (*Packet) Int

func (p *Packet) Int() (int, bool)

func (*Packet) Size

func (p *Packet) Size() (int, int, error)

Size returns data size, total size with headers, and an error for unknown types

func (*Packet) Str

func (p *Packet) Str() (string, bool)

func (*Packet) Uint

func (p *Packet) Uint() (uint, bool)

func (*Packet) Write

func (p *Packet) Write(w io.Writer) error

type PasswordModifyRequest

type PasswordModifyRequest struct {
	UserIdentity string
	OldPassword  []byte
	NewPassword  []byte
}

type PasswordModifyResponse

type PasswordModifyResponse struct {
	GenPassword []byte // [0] OCTET STRING OPTIONAL
}

type Present

type Present struct {
	Attribute string
}

func (*Present) Encode

func (f *Present) Encode() (*Packet, error)

func (*Present) String

func (f *Present) String() string

type Request

type Request interface {
	WritePackets(w io.Writer, msgID int) error
}

type Response

type Response interface {
	WritePackets(w io.Writer, msgID int) error
}

type ResultCode

type ResultCode byte
const (
	ResultSuccess                      ResultCode = 0
	ResultOperationsError              ResultCode = 1
	ResultProtocolError                ResultCode = 2
	ResultTimeLimitExceeded            ResultCode = 3
	ResultSizeLimitExceeded            ResultCode = 4
	ResultCompareFalse                 ResultCode = 5
	ResultCompareTrue                  ResultCode = 6
	ResultAuthMethodNotSupported       ResultCode = 7
	ResultStrongAuthRequired           ResultCode = 8
	ResultReferral                     ResultCode = 10
	ResultAdminLimitExceeded           ResultCode = 11
	ResultUnavailableCriticalExtension ResultCode = 12
	ResultConfidentialityRequired      ResultCode = 13
	ResultSaslBindInProgress           ResultCode = 14
	ResultNoSuchAttribute              ResultCode = 16
	ResultUndefinedAttributeType       ResultCode = 17
	ResultInappropriateMatching        ResultCode = 18
	ResultConstraintViolation          ResultCode = 19
	ResultAttributeOrValueExists       ResultCode = 20
	ResultInvalidAttributeSyntax       ResultCode = 21
	ResultNoSuchObject                 ResultCode = 32
	ResultAliasProblem                 ResultCode = 33
	ResultInvalidDNSyntax              ResultCode = 34
	ResultAliasDereferencingProblem    ResultCode = 36
	ResultInappropriateAuthentication  ResultCode = 48
	ResultInvalidCredentials           ResultCode = 49
	ResultInsufficientAccessRights     ResultCode = 50
	ResultBusy                         ResultCode = 51
	ResultUnavailable                  ResultCode = 52
	ResultUnwillingToPerform           ResultCode = 53
	ResultLoopDetect                   ResultCode = 54
	ResultNamingViolation              ResultCode = 64
	ResultObjectClassViolation         ResultCode = 65
	ResultNotAllowedOnNonLeaf          ResultCode = 66
	ResultNotAllowedOnRDN              ResultCode = 67
	ResultEntryAlreadyExists           ResultCode = 68
	ResultObjectClassModsProhibited    ResultCode = 69
	ResultAffectsMultipleDSAs          ResultCode = 71
	ResultOther                        ResultCode = 80
)

func (ResultCode) String

func (c ResultCode) String() string

type Scope

type Scope int
const (
	ScopeBaseObject   Scope = 0
	ScopeSingleLevel  Scope = 1
	ScopeWholeSubtree Scope = 2
	ScopeChildren     Scope = 3 // used by ldapsearch/ldaptools (-s children) but not part of the standard
)

func (Scope) String

func (sc Scope) String() string

type SearchRequest

type SearchRequest struct {
	BaseDN       string
	Scope        Scope
	DerefAliases DerefAliases
	SizeLimit    int
	TimeLimit    int
	TypesOnly    bool
	Filter       Filter
	Attributes   map[string]bool
}

func (*SearchRequest) WritePackets

func (r *SearchRequest) WritePackets(w io.Writer, msgID int) error

type SearchResponse

type SearchResponse struct {
	BaseResponse
	Results []*SearchResult
}

func (*SearchResponse) WritePackets

func (r *SearchResponse) WritePackets(w io.Writer, msgID int) error

type SearchResult

type SearchResult struct {
	DN         string
	Attributes map[string][][]byte
}

func (*SearchResult) ToLDIF

func (r *SearchResult) ToLDIF(w io.Writer) error

type Server

type Server struct {
	Backend Backend
	RootDSE map[string][]string
	// contains filtered or unexported fields
}

func NewServer

func NewServer(be Backend, tlsConfig *tls.Config) (*Server, error)

func (*Server) Serve

func (srv *Server) Serve(network, addr string) error

func (*Server) ServeTLS

func (srv *Server) ServeTLS(network, addr string, tlsConfig *tls.Config) error

func (*Server) Shutdown

func (srv *Server) Shutdown() error

type Substrings

type Substrings struct {
	Attribute string
	Initial   string
	Final     string
	Any       []string
}

func (*Substrings) Encode

func (f *Substrings) Encode() (*Packet, error)

func (*Substrings) String

func (s *Substrings) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL