mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-25 17:44:32 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			192 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package ssh_config
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| type sshParser struct {
 | |
| 	flow          chan token
 | |
| 	config        *Config
 | |
| 	tokensBuffer  []token
 | |
| 	currentTable  []string
 | |
| 	seenTableKeys []string
 | |
| 	// /etc/ssh parser or local parser - used to find the default for relative
 | |
| 	// filepaths in the Include directive
 | |
| 	system bool
 | |
| 	depth  uint8
 | |
| }
 | |
| 
 | |
| type sshParserStateFn func() sshParserStateFn
 | |
| 
 | |
| // Formats and panics an error message based on a token
 | |
| func (p *sshParser) raiseErrorf(tok *token, msg string, args ...interface{}) {
 | |
| 	// TODO this format is ugly
 | |
| 	panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...))
 | |
| }
 | |
| 
 | |
| func (p *sshParser) raiseError(tok *token, err error) {
 | |
| 	if err == ErrDepthExceeded {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	// TODO this format is ugly
 | |
| 	panic(tok.Position.String() + ": " + err.Error())
 | |
| }
 | |
| 
 | |
| func (p *sshParser) run() {
 | |
| 	for state := p.parseStart; state != nil; {
 | |
| 		state = state()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (p *sshParser) peek() *token {
 | |
| 	if len(p.tokensBuffer) != 0 {
 | |
| 		return &(p.tokensBuffer[0])
 | |
| 	}
 | |
| 
 | |
| 	tok, ok := <-p.flow
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	p.tokensBuffer = append(p.tokensBuffer, tok)
 | |
| 	return &tok
 | |
| }
 | |
| 
 | |
| func (p *sshParser) getToken() *token {
 | |
| 	if len(p.tokensBuffer) != 0 {
 | |
| 		tok := p.tokensBuffer[0]
 | |
| 		p.tokensBuffer = p.tokensBuffer[1:]
 | |
| 		return &tok
 | |
| 	}
 | |
| 	tok, ok := <-p.flow
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return &tok
 | |
| }
 | |
| 
 | |
| func (p *sshParser) parseStart() sshParserStateFn {
 | |
| 	tok := p.peek()
 | |
| 
 | |
| 	// end of stream, parsing is finished
 | |
| 	if tok == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	switch tok.typ {
 | |
| 	case tokenComment, tokenEmptyLine:
 | |
| 		return p.parseComment
 | |
| 	case tokenKey:
 | |
| 		return p.parseKV
 | |
| 	case tokenEOF:
 | |
| 		return nil
 | |
| 	default:
 | |
| 		p.raiseErrorf(tok, fmt.Sprintf("unexpected token %q\n", tok))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *sshParser) parseKV() sshParserStateFn {
 | |
| 	key := p.getToken()
 | |
| 	hasEquals := false
 | |
| 	val := p.getToken()
 | |
| 	if val.typ == tokenEquals {
 | |
| 		hasEquals = true
 | |
| 		val = p.getToken()
 | |
| 	}
 | |
| 	comment := ""
 | |
| 	tok := p.peek()
 | |
| 	if tok == nil {
 | |
| 		tok = &token{typ: tokenEOF}
 | |
| 	}
 | |
| 	if tok.typ == tokenComment && tok.Position.Line == val.Position.Line {
 | |
| 		tok = p.getToken()
 | |
| 		comment = tok.val
 | |
| 	}
 | |
| 	if strings.ToLower(key.val) == "match" {
 | |
| 		// https://github.com/kevinburke/ssh_config/issues/6
 | |
| 		p.raiseErrorf(val, "ssh_config: Match directive parsing is unsupported")
 | |
| 		return nil
 | |
| 	}
 | |
| 	if strings.ToLower(key.val) == "host" {
 | |
| 		strPatterns := strings.Split(val.val, " ")
 | |
| 		patterns := make([]*Pattern, 0)
 | |
| 		for i := range strPatterns {
 | |
| 			if strPatterns[i] == "" {
 | |
| 				continue
 | |
| 			}
 | |
| 			pat, err := NewPattern(strPatterns[i])
 | |
| 			if err != nil {
 | |
| 				p.raiseErrorf(val, "Invalid host pattern: %v", err)
 | |
| 				return nil
 | |
| 			}
 | |
| 			patterns = append(patterns, pat)
 | |
| 		}
 | |
| 		p.config.Hosts = append(p.config.Hosts, &Host{
 | |
| 			Patterns:   patterns,
 | |
| 			Nodes:      make([]Node, 0),
 | |
| 			EOLComment: comment,
 | |
| 			hasEquals:  hasEquals,
 | |
| 		})
 | |
| 		return p.parseStart
 | |
| 	}
 | |
| 	lastHost := p.config.Hosts[len(p.config.Hosts)-1]
 | |
| 	if strings.ToLower(key.val) == "include" {
 | |
| 		inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1)
 | |
| 		if err == ErrDepthExceeded {
 | |
| 			p.raiseError(val, err)
 | |
| 			return nil
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			p.raiseErrorf(val, "Error parsing Include directive: %v", err)
 | |
| 			return nil
 | |
| 		}
 | |
| 		lastHost.Nodes = append(lastHost.Nodes, inc)
 | |
| 		return p.parseStart
 | |
| 	}
 | |
| 	kv := &KV{
 | |
| 		Key:          key.val,
 | |
| 		Value:        val.val,
 | |
| 		Comment:      comment,
 | |
| 		hasEquals:    hasEquals,
 | |
| 		leadingSpace: key.Position.Col - 1,
 | |
| 		position:     key.Position,
 | |
| 	}
 | |
| 	lastHost.Nodes = append(lastHost.Nodes, kv)
 | |
| 	return p.parseStart
 | |
| }
 | |
| 
 | |
| func (p *sshParser) parseComment() sshParserStateFn {
 | |
| 	comment := p.getToken()
 | |
| 	lastHost := p.config.Hosts[len(p.config.Hosts)-1]
 | |
| 	lastHost.Nodes = append(lastHost.Nodes, &Empty{
 | |
| 		Comment: comment.val,
 | |
| 		// account for the "#" as well
 | |
| 		leadingSpace: comment.Position.Col - 2,
 | |
| 		position:     comment.Position,
 | |
| 	})
 | |
| 	return p.parseStart
 | |
| }
 | |
| 
 | |
| func parseSSH(flow chan token, system bool, depth uint8) *Config {
 | |
| 	// Ensure we consume tokens to completion even if parser exits early
 | |
| 	defer func() {
 | |
| 		for range flow {
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	result := newConfig()
 | |
| 	result.position = Position{1, 1}
 | |
| 	parser := &sshParser{
 | |
| 		flow:          flow,
 | |
| 		config:        result,
 | |
| 		tokensBuffer:  make([]token, 0),
 | |
| 		currentTable:  make([]string, 0),
 | |
| 		seenTableKeys: make([]string, 0),
 | |
| 		system:        system,
 | |
| 		depth:         depth,
 | |
| 	}
 | |
| 	parser.run()
 | |
| 	return result
 | |
| }
 |