// Read reads and decodes quoted-printable data from the underlying reader. func (r *Reader) Read(p []byte) (int, error) { // Deviations from RFC 2045: // 1. in addition to "=\r\n", "=\n" is also treated as soft line break. // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent // with other broken QP encoders & decoders. var n int var err error for len(p) > 0 { if len(r.line) == 0 { if err = r.Fn(); err != nil { return n, err } r.line, r.rerr = r.br.ReadSlice('\n') r.gerr.addUnrecover(r.rerr) // Does the line end in CRLF instead of just LF? hasLF := bytes.HasSuffix(r.line, lf) hasCR := bytes.HasSuffix(r.line, crlf) wholeLine := r.line r.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace) if bytes.HasSuffix(r.line, softSuffix) { rightStripped := wholeLine[len(r.line):] r.line = r.line[:len(r.line)-1] if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) { r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped) r.gerr.add(r.rerr) } } else if hasLF { if hasCR { r.line = append(r.line, '\r', '\n') } else { r.line = append(r.line, '\n') } } continue } b := r.line[0] switch { case b == '=': b, err = readHexByte(r.line[1:]) if err != nil { b = '=' r.gerr.add(err) break // this modification allow bad email to be parsed too //return n, err } r.line = r.line[2:] // 2 of the 3; other 1 is done below case b == '\t' || b == '\r' || b == '\n': case b < ' ' || b > '~': //return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b) r.gerr.add(fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b)) } p[0] = b p = p[1:] r.line = r.line[1:] n++ } return n, r.Fn() }
func main() { s := []byte("123456789") f := func(r rune) bool { return r > '7' } fmt.Println(string(bytes.TrimRightFunc(s, f))) }
func EncodeKey(key []byte) string { // we do sloppy work and process safe bytes only at the beginning // and end; this avoids many false positives in large binary data var left, middle, right string { mid := bytes.TrimLeftFunc(key, isSafe) if len(key)-len(mid) > prettyTheshold { left = string(key[:len(key)-len(mid)]) + string(FragSeparator) key = mid } } { mid := bytes.TrimRightFunc(key, isSafe) if len(key)-len(mid) > prettyTheshold { right = string(FragSeparator) + string(key[len(mid):]) key = mid } } if len(key) > 0 { middle = "@" + hex.EncodeToString(key) } return strings.Trim(left+middle+right, string(FragSeparator)) }
func (g *gobot) Listen() (err error) { start, err := g.slackApi.startRTM() if err != nil { return } if !start.Okay { return fmt.Errorf("Real-Time Messaging failed to start, aborting: %s", start.Error) } if g.setupFunc != nil { g.setupFunc(g.slackApi) } conn := start.openWebSocket() healthChecks(conn) for { _, msg, err := conn.ReadMessage() if err != nil { return err } var msgType unmarshalled if err = json.Unmarshal(bytes.TrimRightFunc(msg, func(r rune) bool { return r == '\x00' }), &msgType); err != nil { return err } go g.delegate(msgType.Type, msg) } }
// sanitizeText tries to make the given string easier to read when presented // as a single line. It squashes each run of white space into a single // space, trims leading and trailing white space and trailing full // stops. If newlineSemi is true, any newlines will be replaced with a // semicolon. func sanitizeText(s string, newlineSemi bool) []byte { out := make([]byte, 0, len(s)) prevWhite := false for _, r := range s { if newlineSemi && r == '\n' && len(out) > 0 { out = append(out, ';') prevWhite = true continue } if unicode.IsSpace(r) { if len(out) > 0 { prevWhite = true } continue } if prevWhite { out = append(out, ' ') prevWhite = false } out = append(out, string(r)...) } // Remove final space, any full stops and any final semicolon // we might have added. out = bytes.TrimRightFunc(out, func(r rune) bool { return r == '.' || r == ' ' || r == ';' }) return out }
func main() { whitespace := " \t\r\n" padded := []byte(" \t\r\n\r\n\r\n hello!!! \t\t\t\t") trimmed := bytes.Trim(padded, whitespace) log.Printf("Trim removed runes in %q from the ends of %q to produce %q", whitespace, padded, trimmed) rhyme := []byte("aabbccddee") trimFunced := bytes.TrimFunc(rhyme, trimOdd) log.Printf("TrimFunc removed 'odd' runes from %q to produce %q", rhyme, trimFunced) leftTrimmed := bytes.TrimLeft(padded, whitespace) log.Printf("TrimLeft removed runes in %q from the left side of %q to produce %q", whitespace, padded, leftTrimmed) leftTrimFunced := bytes.TrimLeftFunc(rhyme, trimOdd) log.Printf("TrimLeftFunc removed 'odd' runes from the left side of %q to produce %q", rhyme, leftTrimFunced) rightTrimmed := bytes.TrimRight(padded, whitespace) log.Printf("TrimRight removed runes in %q from the right side of %q to produce %q", whitespace, padded, rightTrimmed) rightTrimFunced := bytes.TrimRightFunc(rhyme, trimOdd) log.Printf("TrimRightFunc removed 'odd' runes from the right side of %q to produce %q", rhyme, rightTrimFunced) spaceTrimmed := bytes.TrimSpace(padded) log.Printf("TrimSpace trimmed all whitespace from the ends of %q to produce %q", padded, spaceTrimmed) }
// Read reads and decodes quoted-printable data from the underlying reader. func (r *Reader) Read(p []byte) (n int, err error) { // Deviations from RFC 2045: // 1. in addition to "=\r\n", "=\n" is also treated as soft line break. // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent // with other broken QP encoders & decoders. // 3. it accepts soft line-break (=) at end of message (issue 15486); i.e. // the final byte read from the underlying reader is allowed to be '=', // and it will be silently ignored. for len(p) > 0 { if len(r.line) == 0 { if r.rerr != nil { return n, r.rerr } r.line, r.rerr = r.br.ReadSlice('\n') // Does the line end in CRLF instead of just LF? hasLF := bytes.HasSuffix(r.line, lf) hasCR := bytes.HasSuffix(r.line, crlf) wholeLine := r.line r.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace) if bytes.HasSuffix(r.line, softSuffix) { rightStripped := wholeLine[len(r.line):] r.line = r.line[:len(r.line)-1] if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) && !(len(rightStripped) == 0 && len(r.line) > 0 && r.rerr == io.EOF) { r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped) } } else if hasLF { if hasCR { r.line = append(r.line, '\r', '\n') } else { r.line = append(r.line, '\n') } } continue } b := r.line[0] switch { case b == '=': b, err = readHexByte(r.line[1:]) if err != nil { return n, err } r.line = r.line[2:] // 2 of the 3; other 1 is done below case b == '\t' || b == '\r' || b == '\n': break case b < ' ' || b > '~': return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b) } p[0] = b p = p[1:] r.line = r.line[1:] n++ } return n, nil }
func ZeroUnPadding(origData []byte, blockSize int) []byte { return bytes.TrimRightFunc(origData, func(r rune) bool { if r == rune(0) && blockSize > 0 { blockSize-- return true } return false }) }
func (q *qpReader) Read(p []byte) (n int, err error) { for len(p) > 0 { if len(q.line) == 0 { if q.rerr != nil { return n, q.rerr } q.skipWhite = true q.line, q.rerr = q.br.ReadSlice('\n') // Does the line end in CRLF instead of just LF? hasLF := bytes.HasSuffix(q.line, lf) hasCR := bytes.HasSuffix(q.line, crlf) wholeLine := q.line q.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace) if bytes.HasSuffix(q.line, softSuffix) { rightStripped := wholeLine[len(q.line):] q.line = q.line[:len(q.line)-1] if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) { q.rerr = fmt.Errorf("multipart: invalid bytes after =: %q", rightStripped) } } else if hasLF { if hasCR { q.line = append(q.line, '\r', '\n') } else { q.line = append(q.line, '\n') } } continue } b := q.line[0] if q.skipWhite && isQPSkipWhiteByte(b) { q.line = q.line[1:] continue } q.skipWhite = false switch { case b == '=': b, err = q.readHexByte(q.line[1:]) if err != nil { return n, err } q.line = q.line[2:] // 2 of the 3; other 1 is done below case b == '\t' || b == '\r' || b == '\n': break case b < ' ' || b > '~': return n, fmt.Errorf("multipart: invalid unescaped byte 0x%02x in quoted-printable body", b) } p[0] = b p = p[1:] q.line = q.line[1:] n++ } return n, nil }
func (b *bufferHelper) NextXAsString(fieldName string, x int, remainingBytes *int) string { bString := b.NextX(fieldName, x, remainingBytes) if b.Error != nil { return "" } // trim nulls from end return string(bytes.TrimRightFunc(bString, func(r rune) bool { return r == 0x0 })) }
func minifyReadFile(file string) []byte { f, err := os.Open(file) if err != nil { log.Fatalln(err) } defer f.Close() var lastLineEnd byte var partMark int var r = bufio.NewReader(f) var buf = new(bytes.Buffer) for { line, part, err := r.ReadLine() if part { partMark++ } else if partMark > 0 { partMark = -1 } else { partMark = 0 } if len(line) > 0 { switch partMark { case 0: line = bytes.TrimSpace(line) case 1: line = bytes.TrimLeftFunc(line, unicode.IsSpace) default: if partMark < 0 { partMark = 0 line = bytes.TrimRightFunc(line, unicode.IsSpace) } } buf.Write(line) lastLineEnd = line[len(line)-1] } if err != nil && r.Buffered() == 0 { break } } // make sure line end with \n if lastLineEnd != '\n' { buf.WriteByte('\n') } return buf.Bytes() }
// Parse a debian changelog from r for any changes happening later than afterVersion func Parse(r io.Reader, afterVersion string) (Changelog, error) { scanner := bufio.NewScanner(r) changelog := make(Changelog, 0, 5) change := Change{} inside := false for scanner.Scan() { b := bytes.TrimRightFunc(scanner.Bytes(), unicode.IsSpace) if b2 := bytes.TrimSpace(b); len(b2) < len(b) && !inside { b = b2 } if len(b) == 0 { if inside { change.Changes = append(change.Changes, '\n') } continue } if !inside && change.parseVersionLine(b) { if len(afterVersion) > 0 && change.Version == afterVersion { break } inside = true continue } if inside && change.parseChangedByLine(b) { changelog = append(changelog, change) change = Change{} inside = false continue } change.Changes = append(change.Changes, b...) change.Changes = append(change.Changes, '\n') } if err := scanner.Err(); err != nil { return nil, err } return changelog, nil }
func decrypt(key, src []byte) (dst []byte, err error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(src) < aes.BlockSize { return nil, fmt.Errorf("invalid source, too short") } iv := src[:ivLength] cipherText := src[ivLength:] mode := cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(cipherText, cipherText) // handle padding lastByte := cipherText[len(cipherText)-1:] buf := bytes.NewBuffer(lastByte) uVal, err := binary.ReadUvarint(buf) if err != nil { return nil, fmt.Errorf("Invalid padding, %s", err) } paddingLength := int(uVal) if paddingLength > aes.BlockSize && paddingLength != 32 { return nil, fmt.Errorf("Decription failed") } if paddingLength == 32 { return bytes.TrimRightFunc(cipherText, unicode.IsSpace), nil } paddingIndex := len(cipherText) - paddingLength - 1 if bytes.Compare(cipherText[paddingIndex:], bytes.Repeat(lastByte, paddingLength)) == 0 { return nil, fmt.Errorf("Decription failed") } return cipherText[:len(cipherText)-paddingLength], nil }
func EncodeKey(key []byte) string { // we do sloppy work and process safe bytes only at the beginning // and end; this avoids many false positives in large binary data var left, right []byte var middle string if key[0] != '.' { mid := bytes.TrimLeftFunc(key, isSafe) if len(key)-len(mid) > prettyTheshold { left = key[:len(key)-len(mid)] key = mid } } { mid := bytes.TrimRightFunc(key, isSafe) if len(mid) == 0 && len(key) > 0 && key[0] == '.' { // don't let right safe zone reach all the way to leading dot mid = key[:1] } if len(key)-len(mid) > prettyTheshold { right = key[len(mid):] key = mid } } if len(key) > 0 { middle = "@" + hex.EncodeToString(key) } return strings.Trim( string(left)+string(FragSeparator)+middle+string(FragSeparator)+string(right), string(FragSeparator), ) }
// IF allowEOF is true it won't return io.EOF as an error, but see it as the end // of the value. func getValue(buf *buffer, end byte, allowEOF bool) ([]byte, error) { var started, isQouted, qoutedClosed bool var value []byte var er error for { c, err := buf.ReadByte() if err != nil { if allowEOF && err == io.EOF { er = err break } return []byte{}, err } if !started { if isSpace(c) { continue } if c == qouteByte { isQouted = true } else { value = append(value, c) } started = true continue } if qoutedClosed { if isSpace(c) { continue } else if c != end { return []byte{}, fmt.Errorf("unexpected %s after closed qoute", string(c)) } } if c == qouteByte { if isQouted && value[len(value)-1] != escapeByte { qoutedClosed = true continue } else if value[len(value)-1] == escapeByte { // replace the escape byte with the byte being escaped. value[len(value)-1] = c continue } } if c == end && (!isQouted || qoutedClosed) { break } value = append(value, c) } if !isQouted { value = bytes.TrimRightFunc(value, unicode.IsSpace) } // todo: trim left space. return value, er }
func ZeroUnPadding(origData []byte) []byte { return bytes.TrimRightFunc(origData, func(r rune) bool { return r == rune(0) }) }
func (sp *SyslogProcessor) processSyslogPkt() { var pkt []byte var addr string var n int var packet Packet for { select { case packet = <-sp.recieverPipe: case <-sp.closeCh: return } pkt = packet.pkt addr = packet.addr n = packet.n m := new(SyslogMessage) m.Source = addr m.Time = time.Now().UTC() // Parse priority (if exists) prio := 13 // default priority hasPrio := false if pkt[0] == '<' { n = 1 + bytes.IndexByte(pkt[1:], '>') if n > 1 && n < 5 { p, err := strconv.Atoi(string(pkt[1:n])) if err == nil && p >= 0 { hasPrio = true prio = p pkt = pkt[n+1:] } } } m.Severity = Severity(prio & 0x07) m.Facility = Facility(prio >> 3) // Parse header (if exists) if hasPrio && len(pkt) >= 16 && pkt[15] == ' ' { // Get timestamp layout := "Jan _2 15:04:05" ts, err := time.Parse(layout, string(pkt[:15])) if err == nil && !ts.IsZero() { // Get hostname n = 16 + bytes.IndexByte(pkt[16:], ' ') if n != 15 { m.Timestamp = ts m.Hostname = string(pkt[16:n]) pkt = pkt[n+1:] } } // TODO: check for version an new format of header as // described in RFC 5424. } // Parse msg part msg := string(bytes.TrimRightFunc(pkt, isNulCrLf)) n = strings.IndexFunc(msg, isNotAlnum) if n != -1 { m.Tag = msg[:n] m.Content = msg[n:] } else { m.Content = msg } msg = strings.TrimFunc(msg, unicode.IsSpace) n = strings.IndexFunc(msg, unicode.IsSpace) if n != -1 { m.Tag1 = msg[:n] m.Content1 = strings.TrimLeftFunc(msg[n+1:], unicode.IsSpace) } else { m.Content1 = msg } esoutput, err := sp.EncodeESFormat(m) if err != nil { log.Println(err) continue } sp.outputPipe <- esoutput } }
// Decode builds fs.Tree from given reader using bt.DecodeLine callback for parsing // node's name and its depth in the tree. Tree returns ErrTreeBuilder error when // a call to ct gives invalid values. // // If tb.DecodeLine is nil, Unix.DecodeLine is used. func (tb TreeBuilder) Decode(r io.Reader) (fs FS, err error) { var ( e error dir = Directory{} buf = bufio.NewReader(r) dec = tb.DecodeLine glob []Directory name []byte prevName []byte depth int prevDepth int ) fs.Tree = dir if dec == nil { dec = Unix.DecodeLine } line, err := buf.ReadBytes('\n') if len(line) == 0 || err == io.EOF { err = io.ErrUnexpectedEOF return } if err != nil { return } if len(line) != 1 || line[0] != '.' { p := filepath.FromSlash(string(bytes.TrimSpace(line))) if err = fs.MkdirAll(p, 0); err != nil { return } var perr *os.PathError if dir, perr = fs.lookup(p); perr != nil { err = perr return } } glob = append(glob, dir) for { line, err = buf.ReadBytes('\n') if len(bytes.TrimSpace(line)) == 0 { // Drain the buffer, needed for some use-cases (encoding, net/rpc) io.Copy(ioutil.Discard, buf) err, line = io.EOF, nil } else { depth, name, e = tb.DecodeLine(bytes.TrimRightFunc(line, unicode.IsSpace)) if len(name) == 0 || depth < 0 || e != nil { // Drain the buffer, needed for some use-cases (encoding, net/rpc) io.Copy(ioutil.Discard, buf) err, line = e, nil if err == nil || err == io.EOF { err = ErrTreeBuilder } } } // Skip first iteration. if len(prevName) != 0 { // Insert the node from previous iteration - node is a directory when // a diference of the tree depth > 0, a file otherwise. var ( name string value interface{} ) if bytes.HasSuffix(prevName, []byte{'/'}) { name, value = string(bytes.TrimRight(prevName, "/")), Directory{} } else { name, value = string(prevName), File{} } switch { case depth > prevDepth: d := Directory{} dir[name], glob, dir = d, append(glob, dir), d case depth == prevDepth: dir[name] = value case depth < prevDepth: n := max(len(glob)+depth-prevDepth, 0) dir[name], dir, glob = value, glob[n], glob[:n] } } // A node from each iteration is handled on the next one. That's why the // error handling is deferred. if len(line) == 0 { if err == io.EOF { err = nil } return } prevDepth, prevName = depth, name } }
// receiver loops reading data from the client's network connection and writing // it to the current driver for processing. If the read times out the connection // will be closed and the inactive user disconnected. func (c *Client) receiver() { // Our initial login driver. driver := driver.New(c) // buffer is the input buffer which may be drip fed data from a client. This // caters for input being read in multiple reads, multiple inputs being read // in a single read and byte-at-a-time reads - currently from Windows telnet // clients. It has a fixed length and capacity to avoid re-allocations. // bCursor points to the *next* byte in the buffer to be filled. buffer := make([]byte, BUFFER_SIZE, BUFFER_SIZE) bCursor := 0 // Short & simple function to simplify for loop // // NOTE: Slicing the buffer with buffer[0:bCursor] stops us accidently // reading an LF in the garbage portion of the buffer after the cursor. // See next TODO for notes on the garbage. nextLF := func() int { return bytes.IndexByte(buffer[0:bCursor], 0x0A) } var b int // bytes read from network var err error // any comms error var LF int // next linefeed position var cmd []byte // extracted command to be processed // Loop on connection until we bail out or timeout for !c.isBailing() && !driver.IsQuitting() { c.conn.SetReadDeadline(time.Now().Add(MAX_TIMEOUT)) b, err = c.conn.Read(buffer[bCursor:]) if b > 0 { // If buffer would overflow discard current buffer by // setting the buffer length back to zero if bCursor+b >= BUFFER_SIZE { bCursor = 0 continue } bCursor += b for LF = nextLF(); LF != -1; LF = nextLF() { // NOTE: This could be buffer[0:LF-1] to save trimming the CR before // the LF as Telnet is supposed to send CR+LF. However if we just get // sent an LF - might be malicious or a badly written / configured // client - then [0:LF-1] causes a 'slice bounds out of range' panic. // Trimming extra characters is simpler than adding checking // specifically for the corner case. cmd = bytes.TrimRightFunc(buffer[0:LF], unicode.IsSpace) driver.Process(string(cmd)) // Remove the part of the buffer we just processed by copying the bytes // after the bCursor to the front of the buffer. // // TODO: This has the side effect of being quick and simple but leaves // input garbage from the bCursor to the end of the buffer. Will this // be an issue? A security issue? We could setup a zero buffer and copy // enough to overwrite the garbage? See previous NOTE on garbage check. copy(buffer, buffer[LF+1:]) bCursor -= LF + 1 } } // Check for errors reading data (see io.Reader for details) if err != nil { if oe, ok := err.(*net.OpError); ok && oe.Timeout() { c.prompt = sender.PROMPT_NONE c.Send("") driver.Logout() c.Send("\n\n[RED]Idle connection terminated by server.") log.Printf("Closing idle connection for: %s", c) break } c.bailing(err) } } driver.Logout() }
// Trim space from the right. func (buf *parserBuf) trimSpaceRight() { buf.bytes = bytes.TrimRightFunc(buf.bytes, unicode.IsSpace) }
func (s *Server) receiver(c net.PacketConn) { //q := (chan<- Message)(s.q) buf := make([]byte, 1024) for { n, addr, err := c.ReadFrom(buf) if err != nil { if !s.shutdown { s.l.Fatalln("Read error:", err) } return } pkt := buf[:n] m := new(Message) m.Source = addr m.Time = time.Now() // Parse priority (if exists) prio := 13 // default priority hasPrio := false if pkt[0] == '<' { n = 1 + bytes.IndexByte(pkt[1:], '>') if n > 1 && n < 5 { p, err := strconv.Atoi(string(pkt[1:n])) if err == nil && p >= 0 { hasPrio = true prio = p pkt = pkt[n+1:] } } } m.Severity = Severity(prio & 0x07) m.Facility = Facility(prio >> 3) // Parse header (if exists) if hasPrio && len(pkt) >= 16 && pkt[15] == ' ' { // Get timestamp layout := "Jan _2 15:04:05" ts, err := time.Parse(layout, string(pkt[:15])) if err == nil && !ts.IsZero() { // Get hostname n = 16 + bytes.IndexByte(pkt[16:], ' ') if n != 15 { m.Timestamp = ts m.Hostname = string(pkt[16:n]) pkt = pkt[n+1:] } } // TODO: check for version an new format of header as // described in RFC 5424. } // Parse msg part msg := string(bytes.TrimRightFunc(pkt, isNulCrLf)) n = strings.IndexFunc(msg, isNotAlnum) if n != -1 { m.Tag = msg[:n] m.Content = msg[n:] } else { m.Content = msg } msg = strings.TrimFunc(msg, unicode.IsSpace) n = strings.IndexFunc(msg, unicode.IsSpace) if n != -1 { m.Tag1 = msg[:n] m.Content1 = strings.TrimLeftFunc(msg[n+1:], unicode.IsSpace) } else { m.Content1 = msg } s.passToHandlers(m) } }
// URL-encodes the `source` byte slice and strips trailing padding // characters. func encode64(source []byte) []byte { encoded := make([]byte, base64.URLEncoding.EncodedLen(len(source))) base64.URLEncoding.Encode(encoded, source) return bytes.TrimRightFunc(encoded, isPadding64) }
func trimRightSpaceBytes(b []byte) []byte { return bytes.TrimRightFunc(b, unicode.IsSpace) }
func (p *parser) NextValue() (*parseValue, error) { result := new(parseValue) inValue := false for { rawLine, err := p.nextLine() // make copy rawLine = append([]byte{}, rawLine...) if err == io.EOF && inValue { // will return EOF on next read return result, nil } else if err != nil { return result, err } // strip trailing whitespace line := bytes.TrimRightFunc(rawLine, unicode.IsSpace) if inValue { if len(line) == 0 || !startsWithSpace(line) { // leave p.bufLine intact for next call return result, nil } result.value = append(result.value, '\n') result.value = append(result.value, bytes.TrimLeftFunc(line, unicode.IsSpace)...) result.addRawLine(rawLine) // we crossed the line, so mark it zero p.bufLine = nil } else if len(line) > 0 { result.lineNum = p.lineNum switch line[0] { case ';': result.entryType = entryTypeComment result.addRawLine(rawLine) p.bufLine = nil return result, nil case '[': if line[len(line)-1] != ']' { return nil, newParseError(ErrInvalidSection, p.lineNum) } result.entryType = entryTypeSection result.section = strings.TrimFunc(string(line[1:len(line)-1]), unicode.IsSpace) result.addRawLine(rawLine) p.bufLine = nil return result, nil default: kv := bytes.SplitN(line, []byte("="), 2) if len(kv) != 2 { return nil, newParseError(ErrInvalidEntry, p.lineNum) } result.entryType = entryTypeKV result.key = string(bytes.TrimRightFunc(kv[0], unicode.IsSpace)) value := bytes.TrimLeftFunc(kv[1], unicode.IsSpace) result.value = append([]byte{}, value...) result.addRawLine(rawLine) // the next line may be a continuation of this value inValue = true p.bufLine = nil } } else { // empty line result.entryType = entryTypeBlank result.addRawLine([]byte("")) p.bufLine = nil return result, nil } } }