mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-20 17:12:25 +00:00 
			
		
		
		
	* Add support for extra sendmail arguments * Sendmail args to exec.command should be a list * Add go-shellquote package * Use go-shellquote lib for parsing Sendmail args * Only parse if sendmail is configured
		
			
				
	
	
		
			102 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			102 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package shellquote
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"strings"
 | |
| 	"unicode/utf8"
 | |
| )
 | |
| 
 | |
| // Join quotes each argument and joins them with a space.
 | |
| // If passed to /bin/sh, the resulting string will be split back into the
 | |
| // original arguments.
 | |
| func Join(args ...string) string {
 | |
| 	var buf bytes.Buffer
 | |
| 	for i, arg := range args {
 | |
| 		if i != 0 {
 | |
| 			buf.WriteByte(' ')
 | |
| 		}
 | |
| 		quote(arg, &buf)
 | |
| 	}
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	specialChars      = "\\'\"`${[|&;<>()*?!"
 | |
| 	extraSpecialChars = " \t\n"
 | |
| 	prefixChars       = "~"
 | |
| )
 | |
| 
 | |
| func quote(word string, buf *bytes.Buffer) {
 | |
| 	// We want to try to produce a "nice" output. As such, we will
 | |
| 	// backslash-escape most characters, but if we encounter a space, or if we
 | |
| 	// encounter an extra-special char (which doesn't work with
 | |
| 	// backslash-escaping) we switch over to quoting the whole word. We do this
 | |
| 	// with a space because it's typically easier for people to read multi-word
 | |
| 	// arguments when quoted with a space rather than with ugly backslashes
 | |
| 	// everywhere.
 | |
| 	origLen := buf.Len()
 | |
| 
 | |
| 	if len(word) == 0 {
 | |
| 		// oops, no content
 | |
| 		buf.WriteString("''")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	cur, prev := word, word
 | |
| 	atStart := true
 | |
| 	for len(cur) > 0 {
 | |
| 		c, l := utf8.DecodeRuneInString(cur)
 | |
| 		cur = cur[l:]
 | |
| 		if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) {
 | |
| 			// copy the non-special chars up to this point
 | |
| 			if len(cur) < len(prev) {
 | |
| 				buf.WriteString(prev[0 : len(prev)-len(cur)-l])
 | |
| 			}
 | |
| 			buf.WriteByte('\\')
 | |
| 			buf.WriteRune(c)
 | |
| 			prev = cur
 | |
| 		} else if strings.ContainsRune(extraSpecialChars, c) {
 | |
| 			// start over in quote mode
 | |
| 			buf.Truncate(origLen)
 | |
| 			goto quote
 | |
| 		}
 | |
| 		atStart = false
 | |
| 	}
 | |
| 	if len(prev) > 0 {
 | |
| 		buf.WriteString(prev)
 | |
| 	}
 | |
| 	return
 | |
| 
 | |
| quote:
 | |
| 	// quote mode
 | |
| 	// Use single-quotes, but if we find a single-quote in the word, we need
 | |
| 	// to terminate the string, emit an escaped quote, and start the string up
 | |
| 	// again
 | |
| 	inQuote := false
 | |
| 	for len(word) > 0 {
 | |
| 		i := strings.IndexRune(word, '\'')
 | |
| 		if i == -1 {
 | |
| 			break
 | |
| 		}
 | |
| 		if i > 0 {
 | |
| 			if !inQuote {
 | |
| 				buf.WriteByte('\'')
 | |
| 				inQuote = true
 | |
| 			}
 | |
| 			buf.WriteString(word[0:i])
 | |
| 		}
 | |
| 		word = word[i+1:]
 | |
| 		if inQuote {
 | |
| 			buf.WriteByte('\'')
 | |
| 			inQuote = false
 | |
| 		}
 | |
| 		buf.WriteString("\\'")
 | |
| 	}
 | |
| 	if len(word) > 0 {
 | |
| 		if !inQuote {
 | |
| 			buf.WriteByte('\'')
 | |
| 		}
 | |
| 		buf.WriteString(word)
 | |
| 		buf.WriteByte('\'')
 | |
| 	}
 | |
| }
 |