package mailparser import ( "bufio" "bytes" "fmt" ) var ( mboxSeparator = []byte("From ") mboxHeaderTag = []byte("From: ") mboxQuoted = []byte(">From ") ) // SplitMbox splits a raw mbox file into individual RFC 2822 message bytes. // Each message starts with a "From " separator line which is skipped. // // Scan errors (e.g. a single line larger than the 10 MB scanner limit) are // ignored here for backwards compatibility — use SplitMboxErr to detect a // truncated result instead of silently importing partial data. func SplitMbox(data []byte) [][]byte { messages, _ := SplitMboxErr(data) return messages } // SplitMboxErr behaves like SplitMbox but also reports scanner failures. // On error the already-collected messages are still returned so the caller can // decide between aborting and importing a partial file. func SplitMboxErr(data []byte) ([][]byte, error) { var messages [][]byte var current bytes.Buffer scanner := bufio.NewScanner(bytes.NewReader(data)) scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024) inMessage := false for scanner.Scan() { // Bytes() avoids a string allocation per line — mbox files routinely // have millions of lines. line := scanner.Bytes() // mbox separator: line starts with "From " but not "From:" header if bytes.HasPrefix(line, mboxSeparator) && !bytes.HasPrefix(line, mboxHeaderTag) { if inMessage && current.Len() > 0 { // Copy: current.Bytes() aliases the buffer, which is reused // after Reset() and would corrupt already-collected messages. msg := bytes.TrimSpace(current.Bytes()) messages = append(messages, append([]byte(nil), msg...)) current.Reset() } inMessage = true continue } if inMessage { // unescape ">From " lines (mbox quoting) if bytes.HasPrefix(line, mboxQuoted) { line = line[1:] } current.Write(line) current.WriteByte('\n') } } if inMessage && current.Len() > 0 { msg := bytes.TrimSpace(current.Bytes()) messages = append(messages, append([]byte(nil), msg...)) } if err := scanner.Err(); err != nil { return messages, fmt.Errorf("mailparser: mbox scan aborted after %d messages: %w", len(messages), err) } return messages, nil }