From b27a28cbf56bb6994f0577c5ebacfbdce17f1770 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Tue, 10 Dec 2024 18:12:35 +1300 Subject: [PATCH 01/15] Fix: Prevent splitting multi-byte characters in message snippets (#404) --- internal/tools/snippets.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/internal/tools/snippets.go b/internal/tools/snippets.go index 8e93b83a47..4746101311 100644 --- a/internal/tools/snippets.go +++ b/internal/tools/snippets.go @@ -26,7 +26,7 @@ func CreateSnippet(text, html string) string { return data } - return data[0:limit] + "..." + return truncate(data, limit) + "..." } if text != "" { @@ -37,8 +37,33 @@ func CreateSnippet(text, html string) string { return text } - return text[0:limit] + "..." + return truncate(text, limit) + "..." } return "" } + +// Truncate a string allowing for multi-byte encoding. +// Shamelessly borrowed from Tailscale. +// See https://github.com/tailscale/tailscale/blob/main/util/truncate/truncate.go +func truncate(s string, n int) string { + if n >= len(s) { + return s + } + + // Back up until we find the beginning of a UTF-8 encoding. + for n > 0 && s[n-1]&0xc0 == 0x80 { // 0x10... is a continuation byte + n-- + } + + // If we're at the beginning of a multi-byte encoding, back up one more to + // skip it. It's possible the value was already complete, but it's simpler + // if we only have to check in one direction. + // + // Otherwise, we have a single-byte code (0x00... or 0x01...). + if n > 0 && s[n-1]&0xc0 == 0xc0 { // 0x11... starts a multibyte encoding + n-- + } + + return s[:n] +} From 16fbb728a4418309005662e129d49c302134350d Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Tue, 10 Dec 2024 22:00:00 +1300 Subject: [PATCH 02/15] Chore: Display "From" details in message sidebar (desktop) (#403) --- server/ui-src/views/MessageView.vue | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/server/ui-src/views/MessageView.vue b/server/ui-src/views/MessageView.vue index 5c747d6b7d..d8e8063f96 100644 --- a/server/ui-src/views/MessageView.vue +++ b/server/ui-src/views/MessageView.vue @@ -601,10 +601,18 @@ export default { :id="message.ID" class="row gx-1 message d-flex small list-group-item list-group-item-action" :class="message.Read ? 'read' : '', isActive(message.ID) ? 'active' : ''"> -
- {{ message.Subject != "" ? message.Subject : "[ no subject ]" }} -
+
+ + {{ message.From.Name ? message.From.Name : message.From.Address }} + +
+
+
+ + {{ getRelativeCreated(message) }} +
+
To: {{ getPrimaryEmailTo(message) }} @@ -612,9 +620,10 @@ export default {
-
- - {{ getRelativeCreated(message) }} +
+
+ {{ message.Subject != "" ? message.Subject : "[ no subject ]" }} +
Date: Tue, 10 Dec 2024 22:00:36 +1300 Subject: [PATCH 03/15] Chore: Display "To" details in mobile messages list --- server/ui-src/components/ListMessages.vue | 27 +++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/server/ui-src/components/ListMessages.vue b/server/ui-src/components/ListMessages.vue index 330c04e292..a81f85bfbe 100644 --- a/server/ui-src/components/ListMessages.vue +++ b/server/ui-src/components/ListMessages.vue @@ -132,21 +132,20 @@ export default { {{ getRelativeCreated(message) }}
-
- - {{ message.From.Name ? message.From.Name : message.From.Address }} - +
+
+ + {{ message.From.Name ? message.From.Name : message.From.Address }} + +
-
- - {{ message.From.Name ? message.From.Name : message.From.Address }} - -
-
- To: {{ getPrimaryEmailTo(message) }} - - [+{{ message.To.length - 1 }}] - +
+
+ To: {{ getPrimaryEmailTo(message) }} + + [+{{ message.To.length - 1 }}] + +
From 0fbb9463d4714357c6141e5754a09836664b8054 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Thu, 12 Dec 2024 08:26:44 +1300 Subject: [PATCH 04/15] Minor style change --- server/ui-src/assets/styles.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/ui-src/assets/styles.scss b/server/ui-src/assets/styles.scss index cbe1b35e74..d04ff3c70e 100644 --- a/server/ui-src/assets/styles.scss +++ b/server/ui-src/assets/styles.scss @@ -314,10 +314,6 @@ body.blur { > div { opacity: 0.7; } - - b { - font-weight: normal; - } } } From e78bc79f5eab6a8aff27eb932bbc972c9fe37972 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 13 Dec 2024 06:25:19 +1300 Subject: [PATCH 05/15] Testing: Add smtpd tests --- .github/workflows/tests.yml | 2 +- server/smtpd/smtpd_test.go | 1579 +++++++++++++++++++++++++++++++++++ 2 files changed, 1580 insertions(+), 1 deletion(-) create mode 100644 server/smtpd/smtpd_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8bf4a7220..6ddc501fe7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - run: go test -p 1 ./internal/storage ./server ./server/pop3 ./internal/tools ./internal/html2text ./internal/linkcheck -v + - run: go test -p 1 ./internal/storage ./server ./server/smtpd ./server/pop3 ./internal/tools ./internal/html2text ./internal/linkcheck -v - run: go test -p 1 ./internal/storage ./internal/html2text -bench=. # build the assets diff --git a/server/smtpd/smtpd_test.go b/server/smtpd/smtpd_test.go new file mode 100644 index 0000000000..af590d655e --- /dev/null +++ b/server/smtpd/smtpd_test.go @@ -0,0 +1,1579 @@ +package smtpd + +import ( + "bufio" + "bytes" + "context" + "crypto/hmac" + "crypto/md5" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "io" + "net" + "os" + "reflect" + "regexp" + "strings" + "testing" + "time" +) + +var cert = makeCertificate() + +// Create a client to run commands with. Parse the banner for 220 response. +func newConn(t *testing.T, server *Server) net.Conn { + clientConn, serverConn := net.Pipe() + session := server.newSession(serverConn) + go session.serve() + + banner, err := bufio.NewReader(clientConn).ReadString('\n') + if err != nil { + t.Fatalf("Failed to read banner from test server: %v", err) + } + if banner[0:3] != "220" { + t.Fatalf("Read incorrect banner from test server: %v", banner) + } + return clientConn +} + +// Send a command and verify the 3 digit code from the response. +func cmdCode(t *testing.T, conn net.Conn, cmd string, code string) string { + fmt.Fprintf(conn, "%s\r\n", cmd) + resp, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + t.Fatalf("Failed to read response from test server: %v", err) + } + if resp[0:3] != code { + t.Errorf("Command \"%s\" response code is %s, want %s", cmd, resp[0:3], code) + } + return strings.TrimSpace(resp) +} + +// Simple tests: connect, send command, then send QUIT. +// RFC 2821 section 4.1.4 specifies that these commands do not require a prior EHLO, +// only that clients should send one, so test without EHLO. +func TestSimpleCommands(t *testing.T) { + tests := []struct { + cmd string + code string + }{ + {"NOOP", "250"}, + {"RSET", "250"}, + {"HELP", "502"}, + {"VRFY", "502"}, + {"EXPN", "502"}, + {"TEST", "500"}, // Unsupported command + {"", "500"}, // Blank command + } + + for _, tt := range tests { + conn := newConn(t, &Server{}) + cmdCode(t, conn, tt.cmd, tt.code) + cmdCode(t, conn, "QUIT", "221") + conn.Close() + } +} + +func TestCmdHELO(t *testing.T) { + conn := newConn(t, &Server{}) + + // Send HELO, expect greeting. + cmdCode(t, conn, "HELO host.example.com", "250") + + // Verify that HELO resets the current transaction state like RSET. + // RFC 2821 section 4.1.4 says EHLO should cause a reset, so verify that HELO does it too. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "HELO host.example.com", "250") + cmdCode(t, conn, "DATA", "503") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdEHLO(t *testing.T) { + conn := newConn(t, &Server{}) + + // Send EHLO, expect greeting. + cmdCode(t, conn, "EHLO host.example.com", "250") + + // Verify that EHLO resets the current transaction state like RSET. + // See RFC 2821 section 4.1.4 for more detail. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "EHLO host.example.com", "250") + cmdCode(t, conn, "DATA", "503") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdRSET(t *testing.T) { + conn := newConn(t, &Server{}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // Verify that RSET clears the current transaction state. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "RSET", "250") + cmdCode(t, conn, "DATA", "503") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdMAIL(t *testing.T) { + conn := newConn(t, &Server{}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // MAIL with no FROM arg should return 501 syntax error + cmdCode(t, conn, "MAIL", "501") + // MAIL with empty FROM arg should return 501 syntax error + cmdCode(t, conn, "MAIL FROM:", "501") + cmdCode(t, conn, "MAIL FROM: ", "501") + cmdCode(t, conn, "MAIL FROM: ", "501") + // MAIL with DSN-style FROM arg should return 250 Ok + cmdCode(t, conn, "MAIL FROM:<>", "250") + // MAIL with valid FROM arg should return 250 Ok + cmdCode(t, conn, "MAIL FROM:", "250") + + // MAIL with seemingly valid but noncompliant FROM arg (single space after the colon) should be tolerated and should return 250 Ok + cmdCode(t, conn, "MAIL FROM: ", "250") + // MAIL with seemingly valid but noncompliant FROM arg (double space after the colon) should return 501 syntax error + cmdCode(t, conn, "MAIL FROM: ", "501") + + // MAIL with valid SIZE parameter should return 250 Ok + cmdCode(t, conn, "MAIL FROM: SIZE=1000", "250") + + // MAIL with bad size parameter should return 501 syntax error + cmdCode(t, conn, "MAIL FROM: SIZE", "501") + cmdCode(t, conn, "MAIL FROM: SIZE=", "501") + cmdCode(t, conn, "MAIL FROM: SIZE= ", "501") + cmdCode(t, conn, "MAIL FROM: SIZE=foo", "501") + + // TODO: MAIL with valid AUTH parameter should return 250 Ok + + // TODO: MAIL with invalid AUTH parameter must return 501 syntax error + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdMAILMaxSize(t *testing.T) { + maxSize := 10 + time.Now().Minute() + conn := newConn(t, &Server{MaxSize: maxSize}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // MAIL with no size parameter should return 250 Ok + cmdCode(t, conn, "MAIL FROM:", "250") + + // MAIL with bad size parameter should return 501 syntax error + cmdCode(t, conn, "MAIL FROM: SIZE", "501") + cmdCode(t, conn, "MAIL FROM: SIZE=", "501") + cmdCode(t, conn, "MAIL FROM: SIZE= ", "501") + cmdCode(t, conn, "MAIL FROM: SIZE=foo", "501") + + // MAIL with size parameter zero should return 250 Ok + cmdCode(t, conn, "MAIL FROM: SIZE=0", "250") + + // MAIL below the maximum size should return 250 Ok + cmdCode(t, conn, fmt.Sprintf("MAIL FROM: SIZE=%d", maxSize-1), "250") + + // MAIL matching the maximum size should return 250 Ok + cmdCode(t, conn, fmt.Sprintf("MAIL FROM: SIZE=%d", maxSize), "250") + + // MAIL above the maximum size should return a maximum size exceeded error. + cmdCode(t, conn, fmt.Sprintf("MAIL FROM: SIZE=%d", maxSize+1), "552") + + // Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2). + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdRCPT(t *testing.T) { + conn := newConn(t, &Server{}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // RCPT without prior MAIL should return 503 bad sequence + cmdCode(t, conn, "RCPT", "503") + + cmdCode(t, conn, "MAIL FROM:", "250") + + // RCPT with no TO arg should return 501 syntax error + cmdCode(t, conn, "RCPT", "501") + + // RCPT with empty TO arg should return 501 syntax error + cmdCode(t, conn, "RCPT TO:", "501") + cmdCode(t, conn, "RCPT TO: ", "501") + cmdCode(t, conn, "RCPT TO: ", "501") + + // RCPT with valid TO arg should return 250 Ok + cmdCode(t, conn, "RCPT TO:", "250") + + // Up to 100 valid recipients should return 250 Ok + for i := 2; i < 101; i++ { + cmdCode(t, conn, fmt.Sprintf("RCPT TO:", i), "250") + } + + // 101st valid recipient with valid TO arg should return 452 too many recipients + cmdCode(t, conn, "RCPT TO:", "452") + + // RCPT with valid TO arg and prior DSN-style FROM arg should return 250 Ok + cmdCode(t, conn, "RSET", "250") + cmdCode(t, conn, "MAIL FROM:<>", "250") + cmdCode(t, conn, "RCPT TO:", "250") + + // RCPT with seemingly valid but noncompliant TO arg (single space after the colon) should be tolerated and should return 250 Ok + cmdCode(t, conn, "RSET", "250") + cmdCode(t, conn, "MAIL FROM:<>", "250") + cmdCode(t, conn, "RCPT TO: ", "250") + + // RCPT with seemingly valid but noncompliant TO arg (double space after the colon) should return 501 syntax error + cmdCode(t, conn, "RSET", "250") + cmdCode(t, conn, "MAIL FROM:<>", "250") + cmdCode(t, conn, "RCPT TO: ", "501") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdDATA(t *testing.T) { + conn := newConn(t, &Server{}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // DATA without prior MAIL & RCPT should return 503 bad sequence + cmdCode(t, conn, "DATA", "503") + cmdCode(t, conn, "RSET", "250") + + // DATA without prior RCPT should return 503 bad sequence + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "DATA", "503") + cmdCode(t, conn, "RSET", "250") + + // Test a full mail transaction. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message.\r\n.", "250") + + // Test a full mail transaction with a bad last recipient. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "RCPT TO:", "501") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message.\r\n.", "250") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdDATAWithMaxSize(t *testing.T) { + // "Test message.\r\n." is 15 bytes after trailing period is removed. + conn := newConn(t, &Server{MaxSize: 15}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // Messages below the maximum size should return 250 Ok + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message\r\n.", "250") + + // Messages matching the maximum size should return 250 Ok + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message.\r\n.", "250") + + // Messages above the maximum size should return a maximum size exceeded error. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message that is too long.\r\n.", "552") + + // Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2). + cmdCode(t, conn, "RSET", "250") + + // Messages above the maximum size should return a maximum size exceeded error. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message.\r\nSecond line that is too long.\r\n.", "552") + + // Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2). + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +type mockHandler struct { + handlerCalled int +} + +func (m *mockHandler) handler(err error) func(a net.Addr, f string, t []string, d []byte) error { + return func(a net.Addr, f string, t []string, d []byte) error { + m.handlerCalled++ + return err + } +} + +func TestCmdDATAWithHandler(t *testing.T) { + m := mockHandler{} + conn := newConn(t, &Server{Handler: m.handler(nil)}) + + cmdCode(t, conn, "EHLO host.example.com", "250") + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message.\r\n.", "250") + cmdCode(t, conn, "QUIT", "221") + conn.Close() + + if m.handlerCalled != 1 { + t.Errorf("MailHandler called %d times, want one call", m.handlerCalled) + } +} + +func TestCmdDATAWithHandlerError(t *testing.T) { + m := mockHandler{} + conn := newConn(t, &Server{Handler: m.handler(errors.New("Handler error"))}) + + cmdCode(t, conn, "EHLO host.example.com", "250") + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message.\r\n.", "451") + cmdCode(t, conn, "QUIT", "221") + conn.Close() + + if m.handlerCalled != 1 { + t.Errorf("MailHandler called %d times, want one call", m.handlerCalled) + } +} + +func TestCmdSTARTTLS(t *testing.T) { + conn := newConn(t, &Server{}) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // By default, TLS is not configured, so STARTTLS should return 502 not implemented. + cmdCode(t, conn, "STARTTLS", "502") + + // Parameters are not allowed (RFC 3207 section 4). + cmdCode(t, conn, "STARTTLS FOO", "501") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdSTARTTLSFailure(t *testing.T) { + // Deliberately misconfigure TLS to force a handshake failure. + server := &Server{TLSConfig: &tls.Config{}} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // When TLS is configured, STARTTLS should return 220 Ready to start TLS. + cmdCode(t, conn, "STARTTLS", "220") + + // A failed TLS handshake should return 403 TLS handshake failed + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + reader := bufio.NewReader(conn) + resp, readErr := reader.ReadString('\n') + if readErr != nil { + t.Fatalf("Failed to read response after failed TLS handshake: %v", err) + } + if resp[0:3] != "403" { + t.Errorf("Failed TLS handshake response code is %s, want 403", resp[0:3]) + } + } else { + t.Error("TLS handshake succeeded with empty tls.Config, want failure") + } + + cmdCode(t, conn, "QUIT", "221") + tlsConn.Close() +} + +// Utility function to make a valid TLS certificate for use by the server. +func makeCertificate() tls.Certificate { + const certPEM = ` +-----BEGIN CERTIFICATE----- +MIID9DCCAtygAwIBAgIJAIX/1sxuqZKrMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzA1MDYxNDIy +MjVaFw0yNzA1MDQxNDIyMjVaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l +LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALO4 +XVY5Kw9eNblqBenC03Wz6qemLFw8zLDNrehvjYuJPn5WVwvzLNP+3S02iqQD+Y1k +vszqDIZLQdjWLiEZdtxfemyIr+RePIMclnceGYFx3Zgg5qeyvOWlJLM41ZU8YZb/ +zGj3RtXzuOZ5vePSLGS1nudjrKSBs7shRY8bYjkOqFujsSVnEK7s3Kb2Sf/rO+7N +RZ1df3hhyKtyq4Pb5eC1mtQqcRjRSZdTxva8kO4vRQbvGgjLUakvBVrrnwbww5a4 +2wKbQPKIClEbSLyKQ62zR8gW1rPwBdokd8u9+rLbcmr7l0OuAsSn5Xi9x6VxXTNE +bgCa1KVoE4bpoGG+KQsCAwEAAaOBvjCBuzAdBgNVHQ4EFgQUILso/fozIhaoyi05 +XNSWzP/ck+4wgYsGA1UdIwSBgzCBgIAUILso/fozIhaoyi05XNSWzP/ck+6hXaRb +MFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ +bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAIX/ +1sxuqZKrMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIbzsvTZb8LA +JqyaTttsMMA1szf4WBX88lVWbIk91k0nlTa0BiU/UocKrU6c9PySwJ6FOFJpgpdH +z/kmJ+S+d4pvgqBzWbKMoMrNlMt6vL+H8Mbf/l/CN91eNM+gJZu2HgBIFGW1y4Wy +gOzjEm9bw15Hgqqs0P4CSy7jcelWA285DJ7IG1qdPGhAKxT4/UuDin8L/u2oeYWH +3DwTDO4kAUnKetcmNQFSX3Ge50uQypl8viYgFJ2axOfZ3imjQZrs7M1Og6Wnj/SD +F414wVQibsZyZp8cqwR/OinvxloPkPVnf163jPRtftuqezEY8Nyj83O5u5sC1Azs +X/Gm54QNk6w= +-----END CERTIFICATE-----` + const keyPEM = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAs7hdVjkrD141uWoF6cLTdbPqp6YsXDzMsM2t6G+Ni4k+flZX +C/Ms0/7dLTaKpAP5jWS+zOoMhktB2NYuIRl23F96bIiv5F48gxyWdx4ZgXHdmCDm +p7K85aUkszjVlTxhlv/MaPdG1fO45nm949IsZLWe52OspIGzuyFFjxtiOQ6oW6Ox +JWcQruzcpvZJ/+s77s1FnV1/eGHIq3Krg9vl4LWa1CpxGNFJl1PG9ryQ7i9FBu8a +CMtRqS8FWuufBvDDlrjbAptA8ogKURtIvIpDrbNHyBbWs/AF2iR3y736sttyavuX +Q64CxKfleL3HpXFdM0RuAJrUpWgThumgYb4pCwIDAQABAoIBAHzvYntJPKTvUhu2 +F6w8kvHVBABNpbLtVUJniUj3G4fv/bCn5tVY1EX/e9QtgU2psbbYXUdoQRKuiHTr +15+M6zMhcKK4lsYDuL9QhU0DcKmq9WgHHzFfMK/YEN5CWT/ofNMSuhASLn0Xc+dM +pHQWrGPKWk/y25Z0z/P7mjZ0y+BrJOKlxV53A2AWpj4JtjX2YO6s/eiraFX+RNlv +GyWzeQ7Gynm2TD9VXhS+m40VVBmmbbeZYDlziDoWWNe9r26A+C8K65gZtjKdarMd +0LN89jJvI1pUxcIuvZJnumWUenZ7JhfBGpkfAwLB+MogUo9ekAHv1IZv/m3uWq9f +Zml2dZECgYEA2OCI8kkLRa3+IodqQNFrb/uZ16YouQ71B7nBgAxls9nuhyELKO7d +fzf1snPx6cbaCQKTyxrlYvck4gz8P09R7nVYwJuTmP0+QIgeCCc3Y9A2dyExaC6I +uKkFzJEqIVZNLvdjBRWQs5AiD1w58oto+wOvbagAQM483WiJ/qFaHCMCgYEA1CPo +zwI6pCn39RSYffK25HXM1q3i8ypkYdNsG6IVqS2FqHqj8XJSnDvLeIm7W1Rtw+uM +QdZ5O6PH31XgolG6LrFkW9vtfH+QnXQA2AnZQEfn034YZubhcexLqAkS9r0FUUZp +a1WI2jSxBBeB+to6MdNABuQOL3NHjPUidUKnOfkCgYA+HvKbE7ka2F+23DrfHh08 +EkFat8lqWJJvCBIY73QiNAZSxnA/5UukqQ7DctqUL9U8R3S19JpH4qq55SZLrBi3 +yP0HDokUhVVTfqm7hCAlgvpW3TcdtFaNLjzu/5WlvuaU0V+XkTnFdT+MTsp6YtxL +Kh8RtdF8vpZIhS0htm3tKQKBgQDQXoUp79KRtPdsrtIpw+GI/Xw50Yp9tkHrJLOn +YMlN5vzFw9CMM/KYqtLsjryMtJ0sN40IjhV+UxzbbYq7ZPMvMeaVo6vdAZ+WSH8b +tHDEBtzai5yEVntSXvrhDiimWnuCnVqmptlJG0BT+JMfRoKqtgjJu++DBARfm9hA +vTtsYQKBgE1ttTzd3HJoIhBBSvSMbyDWTED6jecKvsVypb7QeDxZCbIwCkoK9zn1 +twPDHLBcUNhHJx6JWTR6BxI5DZoIA1tcKHtdO5smjLWNSKhXTsKWee2aNkZJkNIW +TDHSaTMOxVUEzpx84xClf561BTiTgzQy2MULpg3AK0Cv9l0+Yrvz +-----END RSA PRIVATE KEY-----` + + cert, _ := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + return cert +} + +func TestCmdSTARTTLSSuccess(t *testing.T) { + // Configure a valid TLS certificate so the handshake will succeed. + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // When TLS is configured, STARTTLS should return 220 Ready to start TLS. + cmdCode(t, conn, "STARTTLS", "220") + + // A successful TLS handshake shouldn't return anything, it should wait for EHLO. + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + + // The subsequent EHLO should be successful. + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // When TLS is already in use, STARTTLS should return 503 bad sequence. + cmdCode(t, tlsConn, "STARTTLS", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestCmdSTARTTLSRequired(t *testing.T) { + tests := []struct { + cmd string + codeBefore string + codeAfter string + }{ + {"EHLO host.example.com", "250", "250"}, + {"NOOP", "250", "250"}, + {"MAIL FROM:", "530", "250"}, + {"RCPT TO:", "530", "250"}, + {"RSET", "530", "250"}, // Reset before DATA to avoid having to actually send a message. + {"DATA", "530", "503"}, + {"HELP", "502", "502"}, + {"VRFY", "502", "502"}, + {"EXPN", "502", "502"}, + {"TEST", "500", "500"}, // Unsupported command + {"", "500", "500"}, // Blank command + {"AUTH", "530", "502"}, // AuthHandler not configured + } + + // If TLS is not configured, the TLSRequired setting is ignored, so it must be configured for this test. + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, TLSRequired: true} + conn := newConn(t, server) + + // If TLS is required, but not in use, reject every command except NOOP, EHLO, STARTTLS, or QUIT as per RFC 3207 section 4. + for _, tt := range tests { + cmdCode(t, conn, tt.cmd, tt.codeBefore) + } + + // Switch to using TLS. + cmdCode(t, conn, "STARTTLS", "220") + + // A successful TLS handshake shouldn't return anything, it should wait for EHLO. + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + + // The subsequent EHLO should be successful. + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // If TLS is required, and is in use, every command should work normally. + for _, tt := range tests { + cmdCode(t, tlsConn, tt.cmd, tt.codeAfter) + } + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestMakeHeaders(t *testing.T) { + now := time.Now().Format("Mon, _2 Jan 2006 15:04:05 -0700 (MST)") + valid := "Received: from clientName (clientHost [clientIP])\r\n" + + " by serverName (smtpd) with SMTP\r\n" + + " for ; " + + fmt.Sprintf("%s\r\n", now) + + srv := &Server{AppName: "smtpd", Hostname: "serverName"} + s := &session{srv: srv, remoteIP: "clientIP", remoteHost: "clientHost", remoteName: "clientName"} + headers := s.makeHeaders([]string{"recipient@example.com"}) + if string(headers) != valid { + t.Errorf("makeHeaders() returned\n%v, want\n%v", string(headers), valid) + } +} + +// Test parsing of commands into verbs and arguments. +func TestParseLine(t *testing.T) { + tests := []struct { + line string + verb string + args string + }{ + {"EHLO host.example.com", "EHLO", "host.example.com"}, + {"MAIL FROM:", "MAIL", "FROM:"}, + {"RCPT TO:", "RCPT", "TO:"}, + {"QUIT", "QUIT", ""}, + } + s := &session{} + for _, tt := range tests { + verb, args := s.parseLine(tt.line) + if verb != tt.verb || args != tt.args { + t.Errorf("ParseLine(%v) returned %v, %v, want %v, %v", tt.line, verb, args, tt.verb, tt.args) + } + } +} + +// Test reading of complete lines from the socket. +func TestReadLine(t *testing.T) { + var buf bytes.Buffer + s := &session{} + s.srv = &Server{} + s.br = bufio.NewReader(&buf) + + // Ensure readLine() returns an EOF error on an empty buffer. + _, err := s.readLine() + if err != io.EOF { + t.Errorf("readLine() on empty buffer returned err: %v, want EOF", err) + } + + // Ensure trailing is stripped. + line := "FOO BAR BAZ\r\n" + cmd := "FOO BAR BAZ" + buf.Write([]byte(line)) + output, err := s.readLine() + if err != nil { + t.Errorf("readLine(%v) returned err: %v", line, err) + } else if output != cmd { + t.Errorf("readLine(%v) returned %v, want %v", line, output, cmd) + } +} + +// Test reading of message data, including dot stuffing (see RFC 5321 section 4.5.2). +func TestReadData(t *testing.T) { + tests := []struct { + lines string + data string + }{ + // Single line message. + {"Test message.\r\n.\r\n", "Test message.\r\n"}, + + // Single line message with leading period removed. + {".Test message.\r\n.\r\n", "Test message.\r\n"}, + + // Multiple line message. + {"Line 1.\r\nLine 2.\r\nLine 3.\r\n.\r\n", "Line 1.\r\nLine 2.\r\nLine 3.\r\n"}, + + // Multiple line message with leading period removed. + {"Line 1.\r\n.Line 2.\r\nLine 3.\r\n.\r\n", "Line 1.\r\nLine 2.\r\nLine 3.\r\n"}, + + // Multiple line message with one leading period removed. + {"Line 1.\r\n..Line 2.\r\nLine 3.\r\n.\r\n", "Line 1.\r\n.Line 2.\r\nLine 3.\r\n"}, + } + var buf bytes.Buffer + s := &session{} + s.srv = &Server{} + s.br = bufio.NewReader(&buf) + + // Ensure readData() returns an EOF error on an empty buffer. + _, err := s.readData() + if err != io.EOF { + t.Errorf("readData() on empty buffer returned err: %v, want EOF", err) + } + + for _, tt := range tests { + buf.Write([]byte(tt.lines)) + data, err := s.readData() + if err != nil { + t.Errorf("readData(%v) returned err: %v", tt.lines, err) + } else if string(data) != tt.data { + t.Errorf("readData(%v) returned %v, want %v", tt.lines, string(data), tt.data) + } + } +} + +// Test reading of message data with maximum size set (see RFC 1870 section 6.3). +func TestReadDataWithMaxSize(t *testing.T) { + tests := []struct { + lines string + maxSize int + err error + }{ + // Maximum size of zero (the default) should not return an error. + {"Test message.\r\n.\r\n", 0, nil}, + + // Messages below the maximum size should not return an error. + {"Test message.\r\n.\r\n", 16, nil}, + + // Messages matching the maximum size should not return an error. + {"Test message.\r\n.\r\n", 15, nil}, + + // Messages above the maximum size should return a maximum size exceeded error. + {"Test message.\r\n.\r\n", 14, maxSizeExceeded(14)}, + } + var buf bytes.Buffer + s := &session{} + s.br = bufio.NewReader(&buf) + + for _, tt := range tests { + s.srv = &Server{MaxSize: tt.maxSize} + buf.Write([]byte(tt.lines)) + _, err := s.readData() + if err != tt.err { + t.Errorf("readData(%v) returned err: %v", tt.lines, tt.err) + } + } +} + +// Utility function for parsing extensions listed as service extensions in response to an EHLO command. +func parseExtensions(t *testing.T, greeting string) map[string]string { + extensions := make(map[string]string) + lines := strings.Split(greeting, "\n") + + if len(lines) > 1 { + iLast := len(lines) - 1 + for i, line := range lines { + prefix := line[0:4] + + // All but the last extension code prefix should be "250-". + if i != iLast && prefix != "250-" { + t.Errorf("Extension code prefix is %s, want '250-'", prefix) + } + + // The last extension code prefix should be "250 ". + if i == iLast && prefix != "250 " { + t.Errorf("Extension code prefix is %s, want '250 '", prefix) + } + + // Skip greeting line. + if i == 0 { + continue + } + + // Add line as extension. + line = strings.TrimSpace(line[4:]) // Strip code prefix and trailing \r\n + if idx := strings.Index(line, " "); idx != -1 { + extensions[line[:idx]] = line[idx+1:] + } else { + extensions[line] = "" + } + } + } + + return extensions +} + +// Test handler function for validating authentication credentials. +// The secret parameter is passed as nil for LOGIN and PLAIN authentication mechanisms. +func testAuthHandler(_ net.Addr, _ string, username []byte, _ []byte, _ []byte) (bool, error) { + return string(username) == "valid", nil +} + +// Test the extensions listed in response to an EHLO command. +func TestMakeEHLOResponse(t *testing.T) { + s := &session{} + s.srv = &Server{} + + // Greeting should be returned without trailing newlines. + greeting := s.makeEHLOResponse() + if len(greeting) != len(strings.TrimSpace(greeting)) { + t.Errorf("EHLO greeting string has leading or trailing whitespace") + } + + // By default, TLS is not configured, so STARTTLS should not appear. + extensions := parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["STARTTLS"]; ok { + t.Errorf("STARTTLS appears in the extension list when TLS is not configured") + } + + // If TLS is configured, but not already in use, STARTTLS should appear. + s.srv.TLSConfig = &tls.Config{} + extensions = parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["STARTTLS"]; !ok { + t.Errorf("STARTTLS does not appear in the extension list when TLS is configured") + } + + // If TLS is already used on the connection, STARTTLS should not appear. + s.tls = true + extensions = parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["STARTTLS"]; ok { + t.Errorf("STARTTLS appears in the extension list when TLS is already in use") + } + + // Verify default SIZE extension is zero. + s.srv = &Server{} + extensions = parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["SIZE"]; !ok { + t.Errorf("SIZE does not appear in the extension list") + } else if extensions["SIZE"] != "0" { + t.Errorf("SIZE appears in the extension list with incorrect parameter %s, want %s", extensions["SIZE"], "0") + } + + // Verify configured maximum message size is listed correctly. + // Any integer will suffice, as long as it's not hardcoded. + maxSize := 10 + time.Now().Minute() + maxSizeStr := fmt.Sprintf("%d", maxSize) + s.srv = &Server{MaxSize: maxSize} + extensions = parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["SIZE"]; !ok { + t.Errorf("SIZE does not appear in the extension list") + } else if extensions["SIZE"] != maxSizeStr { + t.Errorf("SIZE appears in the extension list with incorrect parameter %s, want %s", extensions["SIZE"], maxSizeStr) + } + + // With no authentication handler configured, AUTH should not be advertised. + s.srv = &Server{} + extensions = parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["AUTH"]; ok { + t.Errorf("AUTH appears in the extension list when no AuthHandler is specified") + } + + // With an authentication handler configured, AUTH should be advertised. + s.srv = &Server{AuthHandler: testAuthHandler} + extensions = parseExtensions(t, s.makeEHLOResponse()) + if _, ok := extensions["AUTH"]; !ok { + t.Errorf("AUTH does not appear in the extension list when an AuthHandler is specified") + } + + reLogin := regexp.MustCompile("\\bLOGIN\\b") + rePlain := regexp.MustCompile("\\bPLAIN\\b") + + // RFC 4954 specifies that, without TLS in use, plaintext authentication mechanisms must not be advertised. + s.tls = false + extensions = parseExtensions(t, s.makeEHLOResponse()) + if reLogin.MatchString(extensions["AUTH"]) { + t.Errorf("AUTH mechanism LOGIN appears in the extension list when an AuthHandler is specified and TLS is not in use") + } + if rePlain.MatchString(extensions["AUTH"]) { + t.Errorf("AUTH mechanism PLAIN appears in the extension list when an AuthHandler is specified and TLS is not in use") + } + + // RFC 4954 specifies that, with TLS in use, plaintext authentication mechanisms can be advertised. + s.tls = true + extensions = parseExtensions(t, s.makeEHLOResponse()) + if !reLogin.MatchString(extensions["AUTH"]) { + t.Errorf("AUTH mechanism LOGIN does not appear in the extension list when an AuthHandler is specified and TLS is in use") + } + if !rePlain.MatchString(extensions["AUTH"]) { + t.Errorf("AUTH mechanism PLAIN does not appear in the extension list when an AuthHandler is specified and TLS is in use") + } +} + +func createTmpFile(content string) (file *os.File, err error) { + file, err = os.CreateTemp("", "") + if err != nil { + return + } + _, err = file.Write([]byte(content)) + if err != nil { + return + } + err = file.Close() + return +} + +func createTLSFiles() ( + certFile *os.File, + keyFile *os.File, + passphrase string, + err error, +) { + const certPEM = `-----BEGIN CERTIFICATE----- +MIIDRzCCAi+gAwIBAgIJAKtg4oViVwv4MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAgFw0xODA0MjAxMzMxNTBaGA8yMDg2MDUwODEzMzE1MFow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA8h7vl0gUquis5jRtcnETyD+8WITZO0s53aIzp0Y+9HXiHW6FGJjbOZjM +IvozNVni+83QWKumRTgeSzIIW2j4V8iFMSNrvWmhmCKloesXS1aY6H979e01Ve8J +WAJFRe6vZJd6gC6Z/P+ELU3ie4Vtr1GYfkV7nZ6VFp5/V/5nxGFag5TUlpP5hcoS +9r2kvXofosVwe3x3udT8SEbv5eBD4bKeVyJs/RLbxSuiU1358Y1cDdVuHjcvfm3c +ajhheQ4vX9WXsk7LGGhnf1SrrPN/y+IDTXfvoHn+nJh4vMAB4yzQdE1V1N1AB8RA +0yBVJ6dwxRrSg4BFrNWhj3gfsvrA7wIDAQABo4GZMIGWMB0GA1UdDgQWBBQ4/ncp +befFuKH1hoYkPqLwuRrPRjAfBgNVHSMEGDAWgBQ4/ncpbefFuKH1hoYkPqLwuRrP +RjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDALBgNVHQ8EBAMCBaAwEwYD +VR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3 +DQEBCwUAA4IBAQBJBetEXiEIzKAEpXGX87j6aUON51Fdf6BiLMCghuGKyhnaOG32 +4KJhtvVoS3ZUKPylh9c2VdItYlhWp76zd7YKk+3xUOixWeTMQHIvCvRGTyFibOPT +mApwp2pEnJCe4vjUrBaRhiyI+xnB70cWVF2qeernlLUeJA1mfYyQLz+v06ebDWOL +c/hPVQFB94lEdiyjGO7RZfIe8KwcK48g7iv0LQU4+c9MoWM2ZsVM1AL2tHzokSeA +u64gDTW4K0Tzx1ab7KmOFXYUjbz/xWuReMt33EwDXAErKCjbVt2T55Qx8UoKzSh1 +tY0KDHdnYOzgsm2HIj2xcJqbeylYQvckNnoC +-----END CERTIFICATE-----` + + const keyPEM = `-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,C16BF8745B2CDB53AC2B1D7609893AA0 + +O13z7Yq7butaJmMfg9wRis9YnIDPsp4coYI6Ud+JGcP7iXoy95QMhovKWx25o1ol +tvUTsrsG27fHGf9qG02KizApIVtO9c1e0swCWzFrKRQX0JDiZDmilb9xosBNNst1 +BOzOTRZEwFGSOCKZRBfSXyqC93TvLJ3DO9IUnKIeGt7upipvg29b/Dur/fyCy2WV +bLHXwUTDBm7j49yfoEyGkDjoB2QO9wgcgbacbnQJQ25fTFUwZpZJEJv6o1tRhoYM +ZMOhC9x1URmdHKN1+z2y5BrB6oNpParfeAMEvs/9FE6jJwYUR28Ql6Mhphfvr9W2 +5Gxd3J65Ao9Vi2I5j5X6aBuNjyhXN3ScLjPG4lVZm9RU/uTPEt81pig/d5nSAjvF +Nfc08NuG3cnMyJSE/xScJ4D+GtX8U969wO4oKPCR4E/NFyXPR730ppupDFG6hzPD +PDmiszDtU438JAZ8AuFa1LkbyFnEW6KVD4h7VRr8YDjirCqnkgjNSI6dFY0NQ8H7 +SyexB0lrceX6HZc+oNdAtkX3tYdzY3ExzUM5lSF1dkldnRbApLbqc4uuNIVXhXFM +dJnoPdKAzM6i+2EeVUxWNdafKDxnjVSHIHzHfIFJLQ4GS5rnz9keRFdyDjQL07tT +Lu9pPOmsadDXp7oSa81RgoCUfNZeR4jKpCk2BOft0L6ZSqwYFLcQHLIfJaGfn902 +TUOTxHt0KzEUYeYSrXC2a6cyvXAd1YI7lOgy60qG89VHyCc2v5Bs4c4FNUDC/+Dj +4ZwogaAbSNkLaE0q3sYQRPdxSqLftyX0KitAgE7oGtdzBfe1cdBoozw3U67NEMMT +6qvk5j7RepPRSrapHtK5pMMdg5XpKFWcOXZ26VHVrDCj4JKdjVb4iyiQi94VveV0 +w9+KcOtyrM7/jbQlCWnXpsIkP8VA/RIgh7CBn/h4oF1sO8ywP25OGQ7VWAVq1R9D +8bl8GzIdR9PZpFyOxuIac4rPa8tkDeoXKs4cxoao7H/OZO9o9aTB7CJMTL9yv0Kb +ntWuYxQchE6syoGsOgdGyZhaw4JeFkasDUP5beyNY+278NkzgGTOIMMTXIX46woP +ehzHKGHXVGf7ZiSFF+zAHMXZRSwNVMkOYwlIoRg1IbvIRbAXqAR6xXQTCVzNG0SU +cskojycBca1Cz3hDVIKYZd9beDhprVdr2a4K2nft2g2xRNjKPopsaqXx+VPibFUx +X7542eQ3eAlhkWUuXvt0q5a9WJdjJp9ODA0/d0akF6JQlEHIAyLfoUKB1HYwgUGG +6uRm651FDAab9U4cVC5PY1hfv/QwzpkNDkzgJAZ5SMOfZhq7IdBcqGd3lzPmq2FP +Vy1LVZIl3eM+9uJx5TLsBHH6NhMwtNhFCNa/5ksodQYlTvR8IrrgWlYg4EL69vjS +yt6HhhEN3lFCWvrQXQMp93UklbTlpVt6qcDXiC7HYbs3+EINargRd5Z+xL5i5vkN +f9k7s0xqhloWNPZcyOXMrox8L81WOY+sP4mVlGcfDRLdEJ8X2ofJpOAcwYCnjsKd +uEGsi+l2fTj/F+eZLE6sYoMprgJrbfeqtRWFguUgTn7s5hfU0tZ46al5d0vz8fWK +-----END RSA PRIVATE KEY-----` + + passphrase = "test" + + certFile, err = createTmpFile(certPEM) + if err != nil { + return + } + keyFile, err = createTmpFile(keyPEM) + return +} + +func TestAuthMechs(t *testing.T) { + s := session{} + s.srv = &Server{} + + // Validate that non-TLS (default) configuration does not allow plaintext authentication mechanisms. + correct := map[string]bool{"LOGIN": false, "PLAIN": false, "CRAM-MD5": true} + mechs := s.authMechs() + if !reflect.DeepEqual(mechs, correct) { + t.Errorf("authMechs() returned %v, want %v", mechs, correct) + } + + // Validate that TLS configuration allows plaintext authentication mechanisms. + correct = map[string]bool{"LOGIN": true, "PLAIN": true, "CRAM-MD5": true} + s.tls = true + mechs = s.authMechs() + if !reflect.DeepEqual(mechs, correct) { + t.Errorf("authMechs() returned %v, want %v", mechs, correct) + } + + // Validate that overridden values take precedence over RFC compliance when not using TLS. + correct = map[string]bool{"LOGIN": true, "PLAIN": true, "CRAM-MD5": false} + s.tls = false + s.srv.AuthMechs = map[string]bool{"LOGIN": true, "PLAIN": true, "CRAM-MD5": false} + mechs = s.authMechs() + if !reflect.DeepEqual(mechs, correct) { + t.Errorf("authMechs() returned %v, want %v", mechs, correct) + } + + // Validate that overridden values take precedence over RFC compliance when using TLS. + correct = map[string]bool{"LOGIN": false, "PLAIN": false, "CRAM-MD5": true} + s.tls = true + s.srv.AuthMechs = map[string]bool{"LOGIN": false, "PLAIN": false, "CRAM-MD5": true} + mechs = s.authMechs() + if !reflect.DeepEqual(mechs, correct) { + t.Errorf("authMechs() returned %v, want %v", mechs, correct) + } + + // Validate ability to explicitly disallow all mechanisms. + correct = map[string]bool{"LOGIN": false, "PLAIN": false, "CRAM-MD5": false} + s.srv.AuthMechs = map[string]bool{"LOGIN": false, "PLAIN": false, "CRAM-MD5": false} + mechs = s.authMechs() + if !reflect.DeepEqual(mechs, correct) { + t.Errorf("authMechs() returned %v, want %v", mechs, correct) + } + + // Validate ability to explicitly allow all mechanisms. + correct = map[string]bool{"LOGIN": true, "PLAIN": true, "CRAM-MD5": true} + s.srv.AuthMechs = map[string]bool{"LOGIN": true, "PLAIN": true, "CRAM-MD5": true} + mechs = s.authMechs() + if !reflect.DeepEqual(mechs, correct) { + t.Errorf("authMechs() returned %v, want %v", mechs, correct) + } +} + +func TestCmdAUTH(t *testing.T) { + server := &Server{} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // By default no authentication handler is configured, so AUTH should return 502 not implemented. + cmdCode(t, conn, "AUTH", "502") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdAUTHOptional(t *testing.T) { + server := &Server{AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH without mechanism parameter must return 501 syntax error. + cmdCode(t, conn, "AUTH", "501") + + // AUTH with a supported mechanism should return 334. + cmdCode(t, conn, "AUTH CRAM-MD5", "334") + + // AUTH must support cancellation with '*' and return 501 syntax error. + cmdCode(t, conn, "*", "501") + + // AUTH with an unsupported mechanism should return 504 unrecognized type. + cmdCode(t, conn, "AUTH FOO", "504") + + // The LOGIN and PLAIN mechanisms require a TLS connection, and are disabled by default. + cmdCode(t, conn, "AUTH LOGIN", "504") + cmdCode(t, conn, "AUTH PLAIN", "504") + + // AUTH attempt during a mail transaction must return 503 bad sequence. + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "AUTH CRAM-MD5", "503") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "AUTH CRAM-MD5", "503") + + // AUTH after a mail transaction must return 334. + // TODO: Work out what should happen if AUTH is received after DATA. + cmdCode(t, conn, "DATA", "354") + cmdCode(t, conn, "Test message\r\n.", "250") + cmdCode(t, conn, "AUTH CRAM-MD5", "334") + + // Cancel the authentication attempt, otherwise the QUIT below will return 502. + // TODO: Work out what should happen if QUIT is received after AUTH. + cmdCode(t, conn, "*", "501") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdAUTHRequired(t *testing.T) { + server := &Server{AuthHandler: testAuthHandler, AuthRequired: true} + conn := newConn(t, server) + + tests := []struct { + cmd string + codeBefore string + codeAfter string + }{ + {"EHLO host.example.com", "250", "250"}, + {"NOOP", "250", "250"}, + {"MAIL FROM:", "530", "250"}, + {"RCPT TO:", "530", "250"}, + {"RSET", "250", "250"}, // Reset before DATA to avoid having to actually send a message. + {"DATA", "530", "503"}, + {"HELP", "502", "502"}, + {"VRFY", "502", "502"}, + {"EXPN", "502", "502"}, + {"TEST", "500", "500"}, // Unsupported command + {"", "500", "500"}, // Blank command + {"STARTTLS", "502", "502"}, // TLS not configured + } + + // If authentication is configured and required, but not already in use, reject every command except + // AUTH, EHLO, HELO, NOOP, RSET, or QUIT as per RFC 4954. + for _, tt := range tests { + cmdCode(t, conn, tt.cmd, tt.codeBefore) + } + + // AUTH without mechanism parameter must return 501 syntax error. + cmdCode(t, conn, "AUTH", "501") + + // AUTH with a supported mechanism should return 334. + cmdCode(t, conn, "AUTH CRAM-MD5", "334") + + // AUTH must support cancellation with '*' and return 501 syntax error. + cmdCode(t, conn, "*", "501") + + // AUTH with an unsupported mechanism should return 504 unrecognized type. + cmdCode(t, conn, "AUTH FOO", "504") + + // The LOGIN and PLAIN mechanisms require a TLS connection, and are disabled by default. + cmdCode(t, conn, "AUTH LOGIN", "504") + cmdCode(t, conn, "AUTH PLAIN", "504") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdAUTHLOGIN(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH LOGIN without TLS in use must return 504 unrecognised type. + cmdCode(t, conn, "AUTH LOGIN", "504") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH LOGIN with TLS in use can proceed. + + // LOGIN authentication process: + // Client sends "AUTH LOGIN" + // Server sends "334 VXNlcm5hbWU6" (Base64-encoded "Username:"). + // Client sends Base64-encoded username. + // Server sends "334 UGFzc3dvcmQ6" (Base64-encoded "Password:"). + // Client sends Base64-encoded password. + invalidBase64 := "==" // Invalid Base64 string. + validUsername := base64.StdEncoding.EncodeToString([]byte("valid")) + invalidUsername := base64.StdEncoding.EncodeToString([]byte("invalid")) + password := base64.StdEncoding.EncodeToString([]byte("password")) + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH LOGIN", "334") + cmdCode(t, tlsConn, invalidBase64, "501") + + cmdCode(t, tlsConn, "AUTH LOGIN", "334") + cmdCode(t, tlsConn, validUsername, "334") + cmdCode(t, tlsConn, invalidBase64, "501") + + // Invalid credentials must return 535 authentication credentials invalid. + cmdCode(t, tlsConn, "AUTH LOGIN", "334") + cmdCode(t, tlsConn, invalidUsername, "334") + cmdCode(t, tlsConn, password, "535") + + // Valid credentials must return 235 authentication succeeded. + cmdCode(t, tlsConn, "AUTH LOGIN", "334") + cmdCode(t, tlsConn, validUsername, "334") + cmdCode(t, tlsConn, password, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestCmdAUTHLOGINFast(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH LOGIN without TLS in use must return 504 unrecognised type. + cmdCode(t, conn, "AUTH LOGIN", "504") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH LOGIN with TLS in use can proceed. + + // Fast LOGIN authentication process: + // Client sends "AUTH LOGIN " plus Base64-encoded username. + // Server sends "334 UGFzc3dvcmQ6" (Base64-encoded "Password:"). + // Client sends Base64-encoded password. + invalidBase64 := "==" // Invalid Base64 string. + validUsername := base64.StdEncoding.EncodeToString([]byte("valid")) + invalidUsername := base64.StdEncoding.EncodeToString([]byte("invalid")) + password := base64.StdEncoding.EncodeToString([]byte("password")) + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH LOGIN "+invalidBase64, "501") + + cmdCode(t, tlsConn, "AUTH LOGIN "+validUsername, "334") + cmdCode(t, tlsConn, invalidBase64, "501") + + // Invalid credentials must return 535 authentication credentials invalid. + cmdCode(t, tlsConn, "AUTH LOGIN "+invalidUsername, "334") + cmdCode(t, tlsConn, password, "535") + + // Valid credentials must return 235 authentication succeeded. + cmdCode(t, tlsConn, "AUTH LOGIN", "334") + cmdCode(t, tlsConn, validUsername, "334") + cmdCode(t, tlsConn, password, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestCmdAUTHPLAIN(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH PLAIN without TLS in use must return 504 unrecognised type. + cmdCode(t, conn, "AUTH PLAIN", "504") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH PLAIN with TLS in use can proceed. + // RFC 2595 specifies: + // The client sends the authorization identity (identity to + // login as), followed by a US-ASCII NUL character, followed by the + // authentication identity (identity whose password will be used), + // followed by a US-ASCII NUL character, followed by the clear-text + // password. The client may leave the authorization identity empty to + // indicate that it is the same as the authentication identity. + + // PLAIN authentication process: + // Client sends "AUTH PLAIN" + // Server sends "334 " (RFC 4954 requires the space). + // Client sends Base64-encoded string: identity\0username\0password + invalidBase64 := "==" // Invalid Base64 string. + missingNUL := base64.StdEncoding.EncodeToString([]byte("valid\x00password")) + valid := base64.StdEncoding.EncodeToString([]byte("identity\x00valid\x00password")) + invalid := base64.StdEncoding.EncodeToString([]byte("identity\x00invalid\x00password")) + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, invalidBase64, "501") + + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, missingNUL, "501") + + // Invalid credentials must return 535 authentication credentials invalid. + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, invalid, "535") + + // Valid credentials must return 235 authentication succeeded. + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, valid, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestCmdAUTHPLAINEmpty(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH PLAIN without TLS in use must return 504 unrecognised type. + cmdCode(t, conn, "AUTH PLAIN", "504") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH PLAIN with TLS in use can proceed. + // RFC 2595 specifies: + // The client sends the authorization identity (identity to + // login as), followed by a US-ASCII NUL character, followed by the + // authentication identity (identity whose password will be used), + // followed by a US-ASCII NUL character, followed by the clear-text + // password. The client may leave the authorization identity empty to + // indicate that it is the same as the authentication identity. + + // PLAIN authentication process with empty authorisation identity: + // Client sends "AUTH PLAIN" + // Server sends "334 " (RFC 4954 requires the space). + // Client sends Base64-encoded string: \0username\0password + invalidBase64 := "==" // Invalid Base64 string. + missingNUL := base64.StdEncoding.EncodeToString([]byte("valid\x00password")) + valid := base64.StdEncoding.EncodeToString([]byte("\x00valid\x00password")) + invalid := base64.StdEncoding.EncodeToString([]byte("\x00invalid\x00password")) + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, invalidBase64, "501") + + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, missingNUL, "501") + + // Invalid credentials must return 535 authentication credentials invalid. + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, invalid, "535") + + // Valid credentials must return 235 authentication succeeded. + cmdCode(t, tlsConn, "AUTH PLAIN", "334") + cmdCode(t, tlsConn, valid, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestCmdAUTHPLAINFast(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH PLAIN without TLS in use must return 504 unrecognised type. + cmdCode(t, conn, "AUTH PLAIN", "504") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH PLAIN with TLS in use can proceed. + // RFC 2595 specifies: + // The client sends the authorization identity (identity to + // login as), followed by a US-ASCII NUL character, followed by the + // authentication identity (identity whose password will be used), + // followed by a US-ASCII NUL character, followed by the clear-text + // password. The client may leave the authorization identity empty to + // indicate that it is the same as the authentication identity. + + // Fast PLAIN authentication process: + // Client sends "AUTH PLAIN " plus Base64-encoded string: identity\0username\0password + invalidBase64 := "==" // Invalid Base64 string. + missingNUL := base64.StdEncoding.EncodeToString([]byte("valid\x00password")) + valid := base64.StdEncoding.EncodeToString([]byte("identity\x00valid\x00password")) + invalid := base64.StdEncoding.EncodeToString([]byte("identity\x00invalid\x00password")) + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH PLAIN "+invalidBase64, "501") + cmdCode(t, tlsConn, "AUTH PLAIN "+missingNUL, "501") + + // Invalid credentials must return 535 authentication credentials invalid. + cmdCode(t, tlsConn, "AUTH PLAIN "+invalid, "535") + + // Valid credentials must return 235 authentication succeeded. + cmdCode(t, tlsConn, "AUTH PLAIN "+valid, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +func TestCmdAUTHPLAINFastAndEmpty(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH PLAIN without TLS in use must return 504 unrecognised type. + cmdCode(t, conn, "AUTH PLAIN", "504") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH PLAIN with TLS in use can proceed. + // RFC 2595 specifies: + // The client sends the authorization identity (identity to + // login as), followed by a US-ASCII NUL character, followed by the + // authentication identity (identity whose password will be used), + // followed by a US-ASCII NUL character, followed by the clear-text + // password. The client may leave the authorization identity empty to + // indicate that it is the same as the authentication identity. + + // Fast PLAIN authentication process with empty authorisation identity: + // Client sends "AUTH PLAIN " plus Base64-encoded string: \0username\0password + invalidBase64 := "==" // Invalid Base64 string. + missingNUL := base64.StdEncoding.EncodeToString([]byte("valid\x00password")) + valid := base64.StdEncoding.EncodeToString([]byte("\x00valid\x00password")) + invalid := base64.StdEncoding.EncodeToString([]byte("\x00invalid\x00password")) + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH PLAIN "+invalidBase64, "501") + cmdCode(t, tlsConn, "AUTH PLAIN "+missingNUL, "501") + + // Invalid credentials must return 535 authentication credentials invalid. + cmdCode(t, tlsConn, "AUTH PLAIN "+invalid, "535") + + // Valid credentials must return 235 authentication succeeded. + cmdCode(t, tlsConn, "AUTH PLAIN "+valid, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +// makeCRAMMD5Response is a helper function to create the CRAM-MD5 hash. +func makeCRAMMD5Response(challenge string, username string, secret string) (string, error) { + decoded, err := base64.StdEncoding.DecodeString(challenge) + if err != nil { + return "", err + } + hash := hmac.New(md5.New, []byte(secret)) + hash.Write(decoded) + buffer := make([]byte, 0, hash.Size()) + response := fmt.Sprintf("%s %x", username, hash.Sum(buffer)) + return base64.StdEncoding.EncodeToString([]byte(response)), nil +} + +func TestCmdAUTHCRAMMD5(t *testing.T) { + server := &Server{AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // AUTH CRAM-MD5 without TLS in use can proceed. + // RFC 2195 specifies: + // The challenge format is that of a Message-ID email header value. + // Challenge format: '<' + random digits + '.' + timestamp in digits + '@' + fully-qualified server hostname + '>' + // Challenge example: <1896.697170952@postoffice.reston.mci.net> + // The response format consists of the username, a space and a digest. + // Digest calculation: MD5((secret XOR opad), MD5((secret XOR ipad), challenge)) + // Response example: tim b913a602c7eda7a495b4e6e7334d3890 + + // CRAM-MD5 authentication process: + // Client sends "AUTH CRAM-MD5". + // Server sends "334 " plus Base64-encoded challenge. + // Client sends Base64-encoded response. + invalidBase64 := "==" // Invalid Base64 string. + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, conn, "AUTH CRAM-MD5", "334") + cmdCode(t, conn, invalidBase64, "501") + + // Test valid credentials with missing space (causing a parse error). + line := cmdCode(t, conn, "AUTH CRAM-MD5", "334") + valid, _ := makeCRAMMD5Response(line[4:], "valid", "password") + buffer, _ := base64.StdEncoding.DecodeString(valid) + buffer = bytes.Replace(buffer, []byte(" "), []byte(""), 1) + missingSpace := base64.StdEncoding.EncodeToString(buffer) + cmdCode(t, conn, string(missingSpace), "501") + + // Invalid credentials must return 535 authentication credentials invalid. + line = cmdCode(t, conn, "AUTH CRAM-MD5", "334") + invalid, err := makeCRAMMD5Response(line[4:], "invalid", "password") + if err != nil { + cmdCode(t, conn, "*", "501") + } + cmdCode(t, conn, invalid, "535") + + // Valid credentials must return 235 authentication succeeded. + line = cmdCode(t, conn, "AUTH CRAM-MD5", "334") + valid, err = makeCRAMMD5Response(line[4:], "valid", "password") + if err != nil { + cmdCode(t, conn, "*", "501") + } + cmdCode(t, conn, valid, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, conn, "AUTH LOGIN", "503") + cmdCode(t, conn, "AUTH PLAIN", "503") + cmdCode(t, conn, "AUTH CRAM-MD5", "503") + + cmdCode(t, conn, "QUIT", "221") + conn.Close() +} + +func TestCmdAUTHCRAMMD5WithTLS(t *testing.T) { + server := &Server{TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, AuthHandler: testAuthHandler} + conn := newConn(t, server) + cmdCode(t, conn, "EHLO host.example.com", "250") + + // Upgrade to TLS. + cmdCode(t, conn, "STARTTLS", "220") + tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + err := tlsConn.Handshake() + if err != nil { + t.Errorf("Failed to perform TLS handshake") + } + cmdCode(t, tlsConn, "EHLO host.example.com", "250") + + // AUTH CRAM-MD5 with TLS in use can proceed. + // RFC 2195 specifies: + // The challenge format is that of a Message-ID email header value. + // Challenge format: '<' + random digits + '.' + timestamp in digits + '@' + fully-qualified server hostname + '>' + // Challenge example: <1896.697170952@postoffice.reston.mci.net> + // The response format consists of the username, a space and a digest. + // Digest calculation: MD5((secret XOR opad), MD5((secret XOR ipad), challenge)) + // Response example: tim b913a602c7eda7a495b4e6e7334d3890 + + // CRAM-MD5 authentication process: + // Client sends "AUTH CRAM-MD5". + // Server sends "334 " plus Base64-encoded challenge. + // Client sends Base64-encoded response. + invalidBase64 := "==" // Invalid Base64 string. + + // Corrupt credentials must return 501 syntax error. + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "334") + cmdCode(t, tlsConn, invalidBase64, "501") + + // Test valid credentials with missing space (causing a parse error). + line := cmdCode(t, tlsConn, "AUTH CRAM-MD5", "334") + valid, _ := makeCRAMMD5Response(line[4:], "valid", "password") + buffer, _ := base64.StdEncoding.DecodeString(valid) + buffer = bytes.Replace(buffer, []byte(" "), []byte(""), 1) + missingSpace := base64.StdEncoding.EncodeToString(buffer) + cmdCode(t, tlsConn, string(missingSpace), "501") + + // Invalid credentials must return 535 authentication credentials invalid. + line = cmdCode(t, tlsConn, "AUTH CRAM-MD5", "334") + invalid, err := makeCRAMMD5Response(line[4:], "invalid", "password") + if err != nil { + cmdCode(t, tlsConn, "*", "501") + } + cmdCode(t, tlsConn, invalid, "535") + + // Valid credentials must return 235 authentication succeeded. + line = cmdCode(t, tlsConn, "AUTH CRAM-MD5", "334") + valid, err = makeCRAMMD5Response(line[4:], "valid", "password") + if err != nil { + cmdCode(t, tlsConn, "*", "501") + } + cmdCode(t, tlsConn, valid, "235") + + // AUTH after prior successful AUTH must return 503 bad sequence. + cmdCode(t, tlsConn, "AUTH LOGIN", "503") + cmdCode(t, tlsConn, "AUTH PLAIN", "503") + cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") + + cmdCode(t, tlsConn, "QUIT", "221") + tlsConn.Close() +} + +// Benchmark the mail handling without the network stack introducing latency. +func BenchmarkReceive(b *testing.B) { + server := &Server{} // Default server configuration. + clientConn, serverConn := net.Pipe() + session := server.newSession(serverConn) + go session.serve() + + reader := bufio.NewReader(clientConn) + _, _ = reader.ReadString('\n') // Read greeting message first. + + b.ResetTimer() + + // Benchmark a full mail transaction. + for i := 0; i < b.N; i++ { + fmt.Fprintf(clientConn, "%s\r\n", "HELO host.example.com") + _, _ = reader.ReadString('\n') + fmt.Fprintf(clientConn, "%s\r\n", "MAIL FROM:") + _, _ = reader.ReadString('\n') + fmt.Fprintf(clientConn, "%s\r\n", "RCPT TO:") + _, _ = reader.ReadString('\n') + fmt.Fprintf(clientConn, "%s\r\n", "DATA") + _, _ = reader.ReadString('\n') + fmt.Fprintf(clientConn, "%s\r\n", "Test message.\r\n.") + _, _ = reader.ReadString('\n') + fmt.Fprintf(clientConn, "%s\r\n", "QUIT") + _, _ = reader.ReadString('\n') + } +} + +func TestCmdShutdown(t *testing.T) { + + srv := &Server{} + + conn := newConn(t, srv) + + // Send HELO, expect greeting. + cmdCode(t, conn, "HELO host.example.com", "250") + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + cmdCode(t, conn, "HELO host.example.com", "250") + cmdCode(t, conn, "DATA", "503") + + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + t.Errorf("Error shutting down server: %v\n", err) + } + }() + + // give the shutdown time to act + time.Sleep(200 * time.Millisecond) + + // shutdown will wait until the end of the session + cmdCode(t, conn, "HELO host.example.com", "250") + cmdCode(t, conn, "MAIL FROM:", "250") + cmdCode(t, conn, "RCPT TO:", "250") + + // this will trigger the close + cmdCode(t, conn, "QUIT", "221") + + // connection should now be closed + fmt.Fprintf(conn, "%s\r\n", "HELO host.example.com") + _, err := bufio.NewReader(conn).ReadString('\n') + if err != io.EOF { + t.Errorf("Expected connection to be closed\n") + } + + conn.Close() +} From b5734691e8a3296159884fc8eaaa26e9ee84dd0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 07:10:47 +1300 Subject: [PATCH 06/15] Bump golang.org/x/crypto from 0.30.0 to 0.31.0 (#408) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 333e431b9c..7a08ec1c2f 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/vanng822/css v1.0.1 // indirect - golang.org/x/crypto v0.30.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect golang.org/x/image v0.23.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index 61feb94eb1..aa762b7a25 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= From 4ad6a4553c41994fe8a0d7c3bc86016074552a20 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 13 Dec 2024 15:40:11 +1300 Subject: [PATCH 07/15] Fix: Ignore unsupported optional SMTP 'MAIL FROM' parameters (#407) --- server/smtpd/lib.go | 11 +++++++---- server/smtpd/smtpd_test.go | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/server/smtpd/lib.go b/server/smtpd/lib.go index 0574c5055b..49f1382bd1 100644 --- a/server/smtpd/lib.go +++ b/server/smtpd/lib.go @@ -29,7 +29,9 @@ var ( Debug = false rcptToRE = regexp.MustCompile(`[Tt][Oo]:\s?<(.+)>`) mailFromRE = regexp.MustCompile(`[Ff][Rr][Oo][Mm]:\s?<(.*)>(\s(.*))?`) // Delivery Status Notifications are sent with "MAIL FROM:<>" - mailSizeRE = regexp.MustCompile(`[Ss][Ii][Zz][Ee]=(\d+)`) + + // extract mail size from 'MAIL FROM' parameter + mailFromSizeRE = regexp.MustCompile(`(?U)(^| |,)[Ss][Ii][Zz][Ee]=(.*)($|,| )`) ) // Handler function called upon successful receipt of an email. @@ -411,12 +413,13 @@ loop: } else { // Validate the SIZE parameter if one was sent. if len(match[2]) > 0 { // A parameter is present - sizeMatch := mailSizeRE.FindStringSubmatch(match[3]) + sizeMatch := mailFromSizeRE.FindStringSubmatch(match[3]) if sizeMatch == nil { - s.writef("501 5.5.4 Syntax error in parameters or arguments (invalid SIZE parameter)") + // ignore other parameter + s.writef("250 2.1.0 Ok") } else { // Enforce the maximum message size if one is set. - size, err := strconv.Atoi(sizeMatch[1]) + size, err := strconv.Atoi(sizeMatch[2]) if err != nil { // Bad SIZE parameter s.writef("501 5.5.4 Syntax error in parameters or arguments (invalid SIZE parameter)") } else if s.srv.MaxSize > 0 && size > s.srv.MaxSize { // SIZE above maximum size, if set diff --git a/server/smtpd/smtpd_test.go b/server/smtpd/smtpd_test.go index af590d655e..6f8cf61b11 100644 --- a/server/smtpd/smtpd_test.go +++ b/server/smtpd/smtpd_test.go @@ -148,11 +148,15 @@ func TestCmdMAIL(t *testing.T) { cmdCode(t, conn, "MAIL FROM: SIZE=1000", "250") // MAIL with bad size parameter should return 501 syntax error - cmdCode(t, conn, "MAIL FROM: SIZE", "501") cmdCode(t, conn, "MAIL FROM: SIZE=", "501") cmdCode(t, conn, "MAIL FROM: SIZE= ", "501") cmdCode(t, conn, "MAIL FROM: SIZE=foo", "501") + // MAIL with options should be ignored except for SIZE + cmdCode(t, conn, "MAIL FROM: BODY=8BITMIME", "250") // ignored + cmdCode(t, conn, "MAIL FROM: BODY=8BITMIME,SIZE=1000", "250") // size detected + cmdCode(t, conn, "MAIL FROM: BODY=8BITMIME,SIZE=foo", "501") // ignored + // TODO: MAIL with valid AUTH parameter should return 250 Ok // TODO: MAIL with invalid AUTH parameter must return 501 syntax error @@ -170,7 +174,6 @@ func TestCmdMAILMaxSize(t *testing.T) { cmdCode(t, conn, "MAIL FROM:", "250") // MAIL with bad size parameter should return 501 syntax error - cmdCode(t, conn, "MAIL FROM: SIZE", "501") cmdCode(t, conn, "MAIL FROM: SIZE=", "501") cmdCode(t, conn, "MAIL FROM: SIZE= ", "501") cmdCode(t, conn, "MAIL FROM: SIZE=foo", "501") From 0af5d184f56aac23f9e4916ced318e28df8221ab Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 13 Dec 2024 20:05:30 +1300 Subject: [PATCH 08/15] Add SMTP From address when ignored parameters are received --- server/smtpd/lib.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/smtpd/lib.go b/server/smtpd/lib.go index 49f1382bd1..20691eb822 100644 --- a/server/smtpd/lib.go +++ b/server/smtpd/lib.go @@ -416,6 +416,8 @@ loop: sizeMatch := mailFromSizeRE.FindStringSubmatch(match[3]) if sizeMatch == nil { // ignore other parameter + from = match[1] + gotFrom = true s.writef("250 2.1.0 Ok") } else { // Enforce the maximum message size if one is set. From 2ea92d1b7e020c2286571ae2a23887369a441172 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Sat, 14 Dec 2024 03:03:20 +0100 Subject: [PATCH 09/15] Chore: Stricter SMTP 'MAIL FROM' & 'RCPT TO' handling (#409) * Update lib.go: Changing `mailFromRE` and `rcptToRE` regex I don't know how to rebase so I started from scratch ;-) 2 changes compared to what you said at https://github.com/axllent/mailpit/pull/406#issuecomment-2540350780 * I did the same for `rcptToRE` * I replaced the `*` quantifier with `+`, for consistency * Update lib.go * Allow valid empty MAIL FROM value --------- Co-authored-by: Ralph Slooten --- server/smtpd/lib.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/smtpd/lib.go b/server/smtpd/lib.go index 20691eb822..a240f65556 100644 --- a/server/smtpd/lib.go +++ b/server/smtpd/lib.go @@ -27,8 +27,8 @@ import ( var ( // Debug `true` enables verbose logging. Debug = false - rcptToRE = regexp.MustCompile(`[Tt][Oo]:\s?<(.+)>`) - mailFromRE = regexp.MustCompile(`[Ff][Rr][Oo][Mm]:\s?<(.*)>(\s(.*))?`) // Delivery Status Notifications are sent with "MAIL FROM:<>" + rcptToRE = regexp.MustCompile(`[Tt][Oo]: ?<([^<>\v]+)>( |$)(.*)?`) + mailFromRE = regexp.MustCompile(`[Ff][Rr][Oo][Mm]: ?<(|[^<>\v]+)>( |$)(.*)?`) // Delivery Status Notifications are sent with "MAIL FROM:<>" // extract mail size from 'MAIL FROM' parameter mailFromSizeRE = regexp.MustCompile(`(?U)(^| |,)[Ss][Ii][Zz][Ee]=(.*)($|,| )`) From b2f4acb7edb6e06d5686fdde3f31b3dbaaf79a02 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 14 Dec 2024 15:12:45 +1300 Subject: [PATCH 10/15] Rename smtpd files --- server/smtpd/{server.go => main.go} | 0 server/smtpd/{lib.go => smtpd.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename server/smtpd/{server.go => main.go} (100%) rename server/smtpd/{lib.go => smtpd.go} (100%) diff --git a/server/smtpd/server.go b/server/smtpd/main.go similarity index 100% rename from server/smtpd/server.go rename to server/smtpd/main.go diff --git a/server/smtpd/lib.go b/server/smtpd/smtpd.go similarity index 100% rename from server/smtpd/lib.go rename to server/smtpd/smtpd.go From 23fee8e4e1c0e5e30beae5c2972b8c86ccc9a719 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 14 Dec 2024 17:51:02 +1300 Subject: [PATCH 11/15] Chore: Move smtpd & pop3 modules to internal --- .github/workflows/tests.yml | 2 +- cmd/root.go | 2 +- {server => internal}/pop3/functions.go | 0 {server => internal}/pop3/pop3_test.go | 0 {server => internal}/pop3/server.go | 0 {server => internal}/smtpd/main.go | 0 {server => internal}/smtpd/relay.go | 0 {server => internal}/smtpd/smtpd.go | 0 {server => internal}/smtpd/smtpd_test.go | 0 server/apiv1/release.go | 2 +- server/apiv1/send.go | 2 +- server/server.go | 2 +- 12 files changed, 5 insertions(+), 5 deletions(-) rename {server => internal}/pop3/functions.go (100%) rename {server => internal}/pop3/pop3_test.go (100%) rename {server => internal}/pop3/server.go (100%) rename {server => internal}/smtpd/main.go (100%) rename {server => internal}/smtpd/relay.go (100%) rename {server => internal}/smtpd/smtpd.go (100%) rename {server => internal}/smtpd/smtpd_test.go (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ddc501fe7..d6b1816e06 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - run: go test -p 1 ./internal/storage ./server ./server/smtpd ./server/pop3 ./internal/tools ./internal/html2text ./internal/linkcheck -v + - run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/linkcheck -v - run: go test -p 1 ./internal/storage ./internal/html2text -bench=. # build the assets diff --git a/cmd/root.go b/cmd/root.go index e7a277cae8..4bc81ded36 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,10 +9,10 @@ import ( "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/internal/auth" "github.com/axllent/mailpit/internal/logger" + "github.com/axllent/mailpit/internal/smtpd" "github.com/axllent/mailpit/internal/storage" "github.com/axllent/mailpit/internal/tools" "github.com/axllent/mailpit/server" - "github.com/axllent/mailpit/server/smtpd" "github.com/axllent/mailpit/server/webhook" "github.com/spf13/cobra" ) diff --git a/server/pop3/functions.go b/internal/pop3/functions.go similarity index 100% rename from server/pop3/functions.go rename to internal/pop3/functions.go diff --git a/server/pop3/pop3_test.go b/internal/pop3/pop3_test.go similarity index 100% rename from server/pop3/pop3_test.go rename to internal/pop3/pop3_test.go diff --git a/server/pop3/server.go b/internal/pop3/server.go similarity index 100% rename from server/pop3/server.go rename to internal/pop3/server.go diff --git a/server/smtpd/main.go b/internal/smtpd/main.go similarity index 100% rename from server/smtpd/main.go rename to internal/smtpd/main.go diff --git a/server/smtpd/relay.go b/internal/smtpd/relay.go similarity index 100% rename from server/smtpd/relay.go rename to internal/smtpd/relay.go diff --git a/server/smtpd/smtpd.go b/internal/smtpd/smtpd.go similarity index 100% rename from server/smtpd/smtpd.go rename to internal/smtpd/smtpd.go diff --git a/server/smtpd/smtpd_test.go b/internal/smtpd/smtpd_test.go similarity index 100% rename from server/smtpd/smtpd_test.go rename to internal/smtpd/smtpd_test.go diff --git a/server/apiv1/release.go b/server/apiv1/release.go index c125ede62a..3c1c9ce818 100644 --- a/server/apiv1/release.go +++ b/server/apiv1/release.go @@ -10,9 +10,9 @@ import ( "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/internal/logger" + "github.com/axllent/mailpit/internal/smtpd" "github.com/axllent/mailpit/internal/storage" "github.com/axllent/mailpit/internal/tools" - "github.com/axllent/mailpit/server/smtpd" "github.com/gorilla/mux" "github.com/lithammer/shortuuid/v4" ) diff --git a/server/apiv1/send.go b/server/apiv1/send.go index 7f006ccfde..270856f1f4 100644 --- a/server/apiv1/send.go +++ b/server/apiv1/send.go @@ -12,8 +12,8 @@ import ( "strings" "github.com/axllent/mailpit/config" + "github.com/axllent/mailpit/internal/smtpd" "github.com/axllent/mailpit/internal/tools" - "github.com/axllent/mailpit/server/smtpd" "github.com/jhillyerd/enmime" ) diff --git a/server/server.go b/server/server.go index c942b29612..c98e91cc6e 100644 --- a/server/server.go +++ b/server/server.go @@ -19,12 +19,12 @@ import ( "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/internal/auth" "github.com/axllent/mailpit/internal/logger" + "github.com/axllent/mailpit/internal/pop3" "github.com/axllent/mailpit/internal/stats" "github.com/axllent/mailpit/internal/storage" "github.com/axllent/mailpit/internal/tools" "github.com/axllent/mailpit/server/apiv1" "github.com/axllent/mailpit/server/handlers" - "github.com/axllent/mailpit/server/pop3" "github.com/axllent/mailpit/server/websockets" "github.com/gorilla/mux" "github.com/lithammer/shortuuid/v4" From 572bda80a2453238302727d0d69bba24809cb07f Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 14 Dec 2024 18:03:49 +1300 Subject: [PATCH 12/15] Chore: Bump Go version for automated testing --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d6b1816e06..0bd42720c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: test: strategy: matrix: - go-version: [1.21.x] + go-version: ['1.23'] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: From ebf8f3568b4fdc716143a84a587875f5672fb301 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 14 Dec 2024 22:50:53 +1300 Subject: [PATCH 13/15] Chore: Update Go dependencies --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7a08ec1c2f..cce988364e 100644 --- a/go.mod +++ b/go.mod @@ -54,11 +54,11 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/vanng822/css v1.0.1 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect + golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect golang.org/x/image v0.23.0 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect + modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 // indirect modernc.org/libc v1.61.4 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect diff --git a/go.sum b/go.sum index aa762b7a25..1eaefd8ba7 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -231,8 +231,8 @@ modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= -modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9 h1:ovz6yUKX71igz2yvk4NpiCL5fvdjZAI+DhuDEGx1xyU= +modernc.org/gc/v3 v3.0.0-20241213165251-3bc300f6d0c9/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/libc v1.61.4 h1:wVyqEx6tlltte9lPTjq0kDAdtdM9c4JH8rU6M1ZVawA= modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= From e01a0f8f4b6d89fd4e04ee836b3c3c08c1279211 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 14 Dec 2024 22:53:35 +1300 Subject: [PATCH 14/15] Chore: Update node dependencies --- package-lock.json | 630 ++++++++++++++++++++++++---------------------- 1 file changed, 333 insertions(+), 297 deletions(-) diff --git a/package-lock.json b/package-lock.json index 084a15bdd8..3ff02e0365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,9 +104,9 @@ } }, "node_modules/@bufbuild/protobuf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz", - "integrity": "sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz", + "integrity": "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==", "dev": true, "license": "(Apache-2.0 AND BSD-3-Clause)", "peer": true @@ -868,13 +868,13 @@ "license": "Apache-2.0" }, "node_modules/@swagger-api/apidom-ast": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.3.tgz", - "integrity": "sha512-JOXGfadL3ucJH+MY9BDT7dJOwFy0jX3XaAY/CWR92EnliEYfaEzZvH08FGnyqyYHcfT8T0DLKna5CWUHaskZuw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.4.tgz", + "integrity": "sha512-PjdAYS3A2tlI1eC3FA+q4MExfCT78hnJxZyLpuVfgXHmDOrJB4cRraFdf4+5aFWbubG6sQH0Q0dRbeG75SVMHQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -882,14 +882,14 @@ } }, "node_modules/@swagger-api/apidom-core": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.3.tgz", - "integrity": "sha512-oRcv3PgwSAvfxvai0afGt/rC2Kk9Zs2ArLPZ6FnVCv/GSnMsuvIQJc5UH29P9eGFcLJIZpQtEHnU6W+u8u0zAA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.4.tgz", + "integrity": "sha512-gi6cs2ZBqj180Jj/Tsih8pO+r/8y1ZZOWnrik/aJ+qMIVVbjDILKXpPwtGN6Ws+f2ffkvUldgmfiNhcuqkWDTg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ast": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "minim": "~0.23.8", "ramda": "~0.30.0", @@ -899,39 +899,39 @@ } }, "node_modules/@swagger-api/apidom-error": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.3.tgz", - "integrity": "sha512-cW1tzehphuxA0uM+1m4/0G1d/WjDQyF+RL9D9t1mfhuVxr8AorgYUgY+bjg0pkLfiSTwjrDiuTbYM+jZwrHx8w==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.4.tgz", + "integrity": "sha512-qWAfueTTQX0UovunGECdZ/rbKebevTZ0Kg4Djcblkd05N7rXb56byXSZx0X1sbYUBtde3qiToMiI8RAQs+JQDw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7" } }, "node_modules/@swagger-api/apidom-json-pointer": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.3.tgz", - "integrity": "sha512-r6Gvbj2XDcK1wIULoclHcGYPAVXeUkj5ECRslB/Zle/fOU0Jb8s4mmFARyQE/DT+fQggXn8nUJBda3NWPK4GcA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.4.tgz", + "integrity": "sha512-S283kWxRdfI2YXhmIRmcgaaCOCF981IcFzupdWFzEmBNdKw5bJlnkTPAUA7acdOlwGQlIoAuwtIrtyh+GDFnlQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-ns-api-design-systems": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.3.tgz", - "integrity": "sha512-x+NiLR0xZ0VB8AMJr7ii+6A27AP2CGjLyPQr6JutnifXG+vpkjbgXCPyz2qlmrvuLIkBJIE2lBuyX3+qQXmgCw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.4.tgz", + "integrity": "sha512-JRj9OsS8l6DrraenBn++vIFBi87hqqpQOcyFfdiFeX1Pkefk6eXFqNKBh/0yvq5SeUsAw/doYxBeZTmpZh2KgA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -939,15 +939,15 @@ } }, "node_modules/@swagger-api/apidom-ns-asyncapi-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.3.tgz", - "integrity": "sha512-9E4/kTf/OzV3vgRjZOB+6TRqQX2ljirD+UBQ8QPSJKBUTtq8+F7U9a8Z9AGYrKCQUMgbge5JMYCqHmOmrJKVUA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.4.tgz", + "integrity": "sha512-1+MlcjheZIiQ6k81yM7FOM8MPEx0SBtWX4nQH8uS7nTUscSZxwCgTqbAcBpvICzn49m1jtOoKMeGS2GKFHVOyA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -955,14 +955,14 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.3.tgz", - "integrity": "sha512-Sc/ywYCHFIMwhZX0Yo+OTmHUvszv3JE3xsvpd18nu7rH+jNyA10oUdTMgnRsTNMnL7siVO+32OKQkdLOSKsEHA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.4.tgz", + "integrity": "sha512-xfwuoikZ45scgML07GHQcxsw83f9RdRnZEf45nWRQHFPlt09yuHT3qtBPJnaTZlAk64yJdoCwLmFsDhKFTuq4w==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ast": "^1.0.0-beta.4", + "@swagger-api/apidom-core": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -970,16 +970,16 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.3.tgz", - "integrity": "sha512-UuGfaJfWzsTCTEyxyKtM86SNdS4EsWB/+j8JWw88h7nFK59YNDmnuXk9PpFyuccpIAHnDq7UJypD3lRvNkJdhQ==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.4.tgz", + "integrity": "sha512-ycr7Ce+DkMYrr2/JKb3ePUiv6Xkcs7gXRNEyCsMn1YdNUu3dFqqZYdK7YxH7Xprubl5xJYymb0XtPgioLOEesA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -987,16 +987,16 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.3.tgz", - "integrity": "sha512-7Snaf8/qZ3Q9xnjEXo2cJ8L4pvDbHA+k/j7rqbY4o3h5EeMy93ClVUwoeJ2y/JWax/V1DWTyYMhq+9dXlcIUYQ==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.4.tgz", + "integrity": "sha512-8o0bUcoEbsioqAeAPzldlb6dZhE0v+XI8vsEZ4yMUhuIC7L+iluFLcZtH6DoHbz1o7pfZq0PFJrJEA7Bk4MLiQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1004,16 +1004,16 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.3.tgz", - "integrity": "sha512-eBNUkQdIDE2fWUXdIeRpN9OMxwfxU2WJFMRHst204Doanh8iJVp3Mz/+z9agHJ6Pkqth2XTXA0EDd1QiI37t+g==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.4.tgz", + "integrity": "sha512-LEMvkxbgARa9dZFpCy1pG5mYpC9LNhOpftIZ4cSm9BmBvim1M8AzT/TRniUuJyaHtUt+b+3OC/ZAjX6uP18dXQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1021,15 +1021,15 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-0": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.3.tgz", - "integrity": "sha512-wKMdk5nplkT2PA1sRFZ2WOLmb7xi9++T6UnCeivmV+sy5NtUPpwkJLUWWIlZdZLyiGKmhZQ1gVvhsbyWRoAVPw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.4.tgz", + "integrity": "sha512-JWsalir+Kh2AObx/0s6rAKH+LWIrKstwYf9cABG/vT7iTN5XCG/3KCxgbwO5BAIdY6m3oN6JV4bXxrTwaNuBww==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1037,16 +1037,16 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-1": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.3.tgz", - "integrity": "sha512-XltfOZNTjrBvrWx1hPU6pHn7lHKKY9jXmiQzojX/jhMjZ6Kp6TLGjMMU3SmEUPU6sTaXKUeO5UUTxe2v6VmqMA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.4.tgz", + "integrity": "sha512-fhxtXbX+BcMkbd6xTFZFtPHENbn334aJDOuFNSrY4eRz7xzDrUaB7J/PvceqskGntBvVzZVNn6BnkxEmzXoG2g==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-json-pointer": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3", + "@swagger-api/apidom-ast": "^1.0.0-beta.4", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-json-pointer": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1054,15 +1054,15 @@ } }, "node_modules/@swagger-api/apidom-ns-workflows-1": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-1.0.0-beta.3.tgz", - "integrity": "sha512-+7i8CZAC+TypSYuxTtwXH2qIyQC1ATn8r+1pW4NWCs4F2Yr4K2gGG4ZmOE6ckNa+Q53yyx+Spt7xhLfZDJZp/w==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-1.0.0-beta.4.tgz", + "integrity": "sha512-lXJC4amoOF1FybQb5cHQsKqx4fF388bhiFBPAraM238wkEyTMnh+QQHs898fH9l2F8dGzlGoWXPWrWzQWtXvug==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -1070,243 +1070,307 @@ } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-beta.3.tgz", - "integrity": "sha512-IpnxjLDVdRaY+ewNW8zbiMzYu5eKifpioFPGDlHc2MoTW6zqo5UKViZKL4MbsncySWBj7+URvTIFYjip3TvkKg==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-beta.4.tgz", + "integrity": "sha512-Dip6qEkJtZSz+ZDY1EuLnmrjczC4piuP5g12weolmESPHF0kBrqcmkyfJuJ/KsSFfhlhLuhHrPq3pcHii5J47Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-beta.3.tgz", - "integrity": "sha512-Pvj+4OMIzKMx77Ulbp/CdWGAQhor88q5BJlY3cuSNd2Oth+mfe6r7NUXWVSpG6H9+9Y6YJdnGOzQ1PHWJPOlqA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-beta.4.tgz", + "integrity": "sha512-cH4Ay1tNV9Ld3nvvrdNW+QeGJhjqF1sOVug2FAEIglUtY7Xk3WOCRBjZ1xOS87WPhhM+1NfzrJz7nggOlknwyw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-beta.3.tgz", - "integrity": "sha512-Z8xIy3pirwAapLgZ18BqRVua5rh0NsvQNpx+5Bi5yJD+SD6Syk5OqsgFkqN7T/LmyqpivQiYRgItUBaHXuDnxg==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-beta.4.tgz", + "integrity": "sha512-XRXP88yGKNv1KxZ/XqOgxT1DvXFXcJ4Pf3OwDlbY62hc1+MFKKbAUk8uPhmFQBIWrQUsa9yMZNTzwQlQQwKrew==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-beta.3.tgz", - "integrity": "sha512-Xl9MU1+24ZTDuGzy/mKVPlnMSvgA/lS+AoqwMzxLMuiIsTmnQX3gEdiM+pXmK7rg1KV/k0aLwDLKt3e00CPiXQ==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-beta.4.tgz", + "integrity": "sha512-eFAjdj3cOoycF1P7ZCt1dr69CxEyRB/36tH9MzYWMp2QrdNYb1LlgF01Zvxbrf/D9eyw41b69eBoAHW0o2WR6Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-json": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-beta.3.tgz", - "integrity": "sha512-28zQdF8oeaUmNxZNU0De4JUY9jvxiaN+QCJ1GZN9aQ6NQ/eOAuGg+HRuL8+RrSe4STacdi1FCX46jHcMGQeqfg==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-beta.4.tgz", + "integrity": "sha512-kvHA1hP4phPpM0Jb15fj23JuSrcsbCDv1+dxLuRkXuFLoTGXZqOQ30Di3AAvie7qWBlB03f8dcIavIel79V15A==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ast": "^1.0.0-beta.4", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", - "tree-sitter": "=0.21.1", + "tree-sitter": "=0.22.1", "tree-sitter-json": "=0.24.8", - "web-tree-sitter": "=0.24.3" + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/node-addon-api": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", + "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/tree-sitter": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.1.tgz", + "integrity": "sha512-gRO+jk2ljxZlIn20QRskIvpLCMtzuLl5T0BY6L9uvPYD17uUrxlxWkvYCiVqED2q2q7CVtY52Uex4WcYo2FEXw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-beta.3.tgz", - "integrity": "sha512-ufiQMl89sTGf09qlh/QvFLEUs9FH9ZZV4mjz1xIB127rnNbWg/sSGr0WIcJGKoLrioI9orb+7aqIhmSDw/plmw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-beta.4.tgz", + "integrity": "sha512-MzMXkVNRG4PZrOH3QcRTsB9ITHN7IiXxsnMMjZ1Bglt6mnCXoQCfSb8mn1EZhjBA5aPbZ+a7QNm804tqADELhA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-beta.3.tgz", - "integrity": "sha512-yINlDTIZCywuKRsBeJJDmQLV4+r9FaWDezb4omw6xFQnQZQV1tHgIb549OsV6lT70TabLj+HoMYNLQ9/Bm59Yw==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-beta.4.tgz", + "integrity": "sha512-oKB9cfxuHiWvqubrXTZeEfygNLvf3WfgSwPh8QrU2eNRAPdDDAN0t+1rdQkbzVCC3E4+JPzU9EaTpMcm0YxJuw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-beta.3.tgz", - "integrity": "sha512-kBZsyNHtp6w41g9N5c+PF4FqoE8vosxgYJEfhQeQs4qXK7T7d8sfjXwcnWRjqlOM4X8dt5R359h58AfwyEF20w==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-beta.4.tgz", + "integrity": "sha512-G0HzOvfc3lAzEfgaqAiq1ljc4bNUbecLJqUQQbav4lVs+2N8SCe5Wav9YAJeDz8BG6GA4ZxUbJo4oJHCSP3HJg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-beta.3.tgz", - "integrity": "sha512-K/FRLCuB0UD9Nq/CNqfjkSVfQfzcpA7lJCg6QueZKd0dQJ54dyHFU9AroshutXHTmEjBleoL7V1K3PNh10HiYQ==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-beta.4.tgz", + "integrity": "sha512-tXiLoqEWaY6WwDWOF3HgEZqHy5PNAa770mS/Y43xChd/RjQ5TcyvxvhBHrQwTJsbzf9ECAXQWIO9h/Uq6fg7JQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-beta.3.tgz", - "integrity": "sha512-EUdpzJnqZqCu2keEyOxlCED/u0oaA05c6dO48XzbdyENONY/etoN5wrEoqxqxOz+1cC+FZWj/cnmsXdFfbJlEg==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-beta.4.tgz", + "integrity": "sha512-F/nL0Hyp1HbAZS/igUsCafhHMszNUUVyO3ak3K3DmU9Do3VQ6pSHyA7IOGxaW2toNhuu5uPpq/Bd0sz/FDAf6Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-beta.3.tgz", - "integrity": "sha512-2Q9vmrgTQ4cA5WALGyTLp8tF984R9C7QmDOjGf/ngrTIQLyyrQZ0ZDaXL7RHTmT6K9Lg6axMpKquBNiO+Aff6g==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-beta.4.tgz", + "integrity": "sha512-WUIiKGb8zhkMeR1u1S4NF9v6PRLz8ueNM0+i4A0rfokO27zdaZso0QAtGNaXEOTcLnOQ7eDILdtghGZo/iUdMQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-workflows-json-1": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-beta.3.tgz", - "integrity": "sha512-OsKz09YcfQfTbiNZueTLHBrn7umnMjtuN0ZzuNiBs5txaLS196grpzyTiG+4UJ1zIWvjvZmLZEbQqbKZ9qTw8A==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-beta.4.tgz", + "integrity": "sha512-5GcJsOe44GR070zzIk9JuEmaRYQpMH+PUhHyGtZh6J3HKFlXc/cDwyzH8jtqnO4+Wxb+aYKl3F9Vj51n8eKNdA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-workflows-yaml-1": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-yaml-1/-/apidom-parser-adapter-workflows-yaml-1-1.0.0-beta.3.tgz", - "integrity": "sha512-IifK3T6UtqBkIoHOQe6QRGpFU9LFqmJ5T1JzbWnVX+gazoVE+N9ZkFWQfb9pKCaCfAwPVp+vai6bQ2eUsGh4CA==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-yaml-1/-/apidom-parser-adapter-workflows-yaml-1-1.0.0-beta.4.tgz", + "integrity": "sha512-DofhMj3LUHe6hQa+Hk+HYsNQUbCQR7lCE7MtvUOaPfPYNYV1FMTlL+OMFDTVb7MXi1W6ejIFpNuubIHsJIakkg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.3", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.4", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-beta.3.tgz", - "integrity": "sha512-sSGxnMTNNTqhJBeUOge4Q/5l/7170maoxyrK6J57kRxqkchSAqam73VIBpKa8c/sJ7zhdZI7CZ9aTJe/q7vc7w==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-beta.4.tgz", + "integrity": "sha512-EwEnKpu6GOtxOJ+71h8l65P27CnNNMKGS7p+qAxg5fBLyziOURqCFAT6fcSpGetHRKSYUmscoGjq+prOmgAtIg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@tree-sitter-grammars/tree-sitter-yaml": "=0.6.1", + "@swagger-api/apidom-ast": "^1.0.0-beta.4", + "@swagger-api/apidom-core": "^1.0.0-beta.4", + "@swagger-api/apidom-error": "^1.0.0-beta.4", + "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.0", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", - "tree-sitter": "=0.21.1", - "web-tree-sitter": "=0.24.3" + "tree-sitter": "=0.22.1", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/@tree-sitter-grammars/tree-sitter-yaml": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.7.0.tgz", + "integrity": "sha512-GOMIK3IaDvECD0eZEhAsLl03RMtM1E8StxuGMn6PpMKFg7jyQ+jSzxJZ4Jmc/tYitah9/AECt8o4tlRQ5yEZQg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.22.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/node-addon-api": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", + "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/tree-sitter": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.1.tgz", + "integrity": "sha512-gRO+jk2ljxZlIn20QRskIvpLCMtzuLl5T0BY6L9uvPYD17uUrxlxWkvYCiVqED2q2q7CVtY52Uex4WcYo2FEXw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" } }, "node_modules/@swagger-api/apidom-reference": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.3.tgz", - "integrity": "sha512-MkSW/uKA+iCUeQ5HqICGxXPZI1y5vbXnOZLT+22+ZvaO3+5j7tD2aS9mAF+140VaaE5AkpZE28XC9TaYyjEwDg==", + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.4.tgz", + "integrity": "sha512-RxUXxmQPZtfxUMcgycnPGHnJPF3UyddRcaSasf+oBxNNS4xM4/L5781p/45/l2mlGfRhWzp2HBPqdbDD1ebaKg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.4", "@types/ramda": "~0.30.0", "axios": "^1.7.4", "minimatch": "^7.4.3", @@ -1338,36 +1402,6 @@ "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3 <1.0.0-rc.0" } }, - "node_modules/@tree-sitter-grammars/tree-sitter-yaml": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.6.1.tgz", - "integrity": "sha512-FqgUNdtMuPpk5D/9YQvCxTK4tzlUEVq/yNewdcxJbMv0KVt/yDfuuUn5ZvxphftKyOco+1e/6/oNHCKVQ5A83Q==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-addon-api": "^8.0.0", - "node-gyp-build": "^4.8.0" - }, - "peerDependencies": { - "tree-sitter": "^0.21.1" - }, - "peerDependenciesMeta": { - "tree_sitter": { - "optional": true - } - } - }, - "node_modules/@tree-sitter-grammars/tree-sitter-yaml/node_modules/node-addon-api": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", - "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", - "license": "MIT", - "optional": true, - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, "node_modules/@types/bootstrap": { "version": "5.2.10", "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz", @@ -1780,9 +1814,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.2.tgz", - "integrity": "sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz", + "integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -2014,9 +2048,9 @@ "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "dev": true, "license": "MIT", "dependencies": { @@ -2115,9 +2149,9 @@ "license": "MIT" }, "node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.15", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.15.tgz", + "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -2490,13 +2524,13 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2535,9 +2569,9 @@ "license": "ISC" }, "node_modules/sass": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.82.0.tgz", - "integrity": "sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.0.tgz", + "integrity": "sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==", "dev": true, "license": "MIT", "dependencies": { @@ -2556,9 +2590,9 @@ } }, "node_modules/sass-embedded": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.82.0.tgz", - "integrity": "sha512-v13sRVVZtWAQLpAGTz5D8hy+oyNKRHao5tKVc/P6AMqSP+jDM8X6GkEpL0jfbu3MaN2/hAQsd4Qx14GG1u0prQ==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.83.0.tgz", + "integrity": "sha512-/8cYZeL39evUqe0o//193na51Q1VWZ61qhxioQvLJwOtWIrX+PgNhCyD8RSuTtmzc4+6+waFZf899bfp/MCUwA==", "dev": true, "license": "MIT", "peer": true, @@ -2579,32 +2613,32 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "sass-embedded-android-arm": "1.82.0", - "sass-embedded-android-arm64": "1.82.0", - "sass-embedded-android-ia32": "1.82.0", - "sass-embedded-android-riscv64": "1.82.0", - "sass-embedded-android-x64": "1.82.0", - "sass-embedded-darwin-arm64": "1.82.0", - "sass-embedded-darwin-x64": "1.82.0", - "sass-embedded-linux-arm": "1.82.0", - "sass-embedded-linux-arm64": "1.82.0", - "sass-embedded-linux-ia32": "1.82.0", - "sass-embedded-linux-musl-arm": "1.82.0", - "sass-embedded-linux-musl-arm64": "1.82.0", - "sass-embedded-linux-musl-ia32": "1.82.0", - "sass-embedded-linux-musl-riscv64": "1.82.0", - "sass-embedded-linux-musl-x64": "1.82.0", - "sass-embedded-linux-riscv64": "1.82.0", - "sass-embedded-linux-x64": "1.82.0", - "sass-embedded-win32-arm64": "1.82.0", - "sass-embedded-win32-ia32": "1.82.0", - "sass-embedded-win32-x64": "1.82.0" + "sass-embedded-android-arm": "1.83.0", + "sass-embedded-android-arm64": "1.83.0", + "sass-embedded-android-ia32": "1.83.0", + "sass-embedded-android-riscv64": "1.83.0", + "sass-embedded-android-x64": "1.83.0", + "sass-embedded-darwin-arm64": "1.83.0", + "sass-embedded-darwin-x64": "1.83.0", + "sass-embedded-linux-arm": "1.83.0", + "sass-embedded-linux-arm64": "1.83.0", + "sass-embedded-linux-ia32": "1.83.0", + "sass-embedded-linux-musl-arm": "1.83.0", + "sass-embedded-linux-musl-arm64": "1.83.0", + "sass-embedded-linux-musl-ia32": "1.83.0", + "sass-embedded-linux-musl-riscv64": "1.83.0", + "sass-embedded-linux-musl-x64": "1.83.0", + "sass-embedded-linux-riscv64": "1.83.0", + "sass-embedded-linux-x64": "1.83.0", + "sass-embedded-win32-arm64": "1.83.0", + "sass-embedded-win32-ia32": "1.83.0", + "sass-embedded-win32-x64": "1.83.0" } }, "node_modules/sass-embedded-android-arm": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.82.0.tgz", - "integrity": "sha512-ttGMvWnA/5TYdZTjr5fWHDbb9nZgKipHKCc9zZQRF5HjUydOYWKNqmAJHQtbFWaq35kd5qn6yiE73IJN6eJ6wA==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.83.0.tgz", + "integrity": "sha512-uwFSXzJlfbd4Px189xE5l+cxN8+TQpXdQgJec7TIrb4HEY7imabtpYufpVdqUVwT1/uiis5V4+qIEC4Vl5XObQ==", "cpu": [ "arm" ], @@ -2620,9 +2654,9 @@ } }, "node_modules/sass-embedded-android-arm64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.82.0.tgz", - "integrity": "sha512-bldHMs02QQWXsgHUZRgolNnZdMjN6XHvmUYoRkzmFq7lsvtLU6SJg2S1Wa9IZJs9jRWdTmOgA6YibSf3pROyFQ==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.83.0.tgz", + "integrity": "sha512-GBiCvM4a2rkWBLdYDxI6XYnprfk5U5c81g69RC2X6kqPuzxzx8qTArQ9M6keFK4+iDQ5N9QTwFCr0KbZTn+ZNQ==", "cpu": [ "arm64" ], @@ -2638,9 +2672,9 @@ } }, "node_modules/sass-embedded-android-ia32": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.82.0.tgz", - "integrity": "sha512-FUJOnxw8IYKuYuxxiOkk6QXle8/yQFtKjnuSAJuZ5ZpLVMcSZzLc3SWOtuEXYx5iSAfJCO075o2ZoG/pPrJ9aw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.83.0.tgz", + "integrity": "sha512-5ATPdGo2SICqAhiJl/Z8KQ23zH4sGgobGgux0TnrNtt83uHZ+r+To/ubVJ7xTkZxed+KJZnIpolGD8dQyQqoTg==", "cpu": [ "ia32" ], @@ -2656,9 +2690,9 @@ } }, "node_modules/sass-embedded-android-riscv64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.82.0.tgz", - "integrity": "sha512-rd+vc+sxJxNnbhaubiIJmnb1b3FvC9wxCIq8spstopbO7o1uufvBBDeRoFSJaN+7oNhamzjlYGdu6aQoQNs3+A==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.83.0.tgz", + "integrity": "sha512-aveknUOB8GZewOzVn2Uwk+DKcncTR50Q6vtzslNMGbYnxtgQNHzy8A1qVEviNUruex+pHofppeMK4iMPFAbiEQ==", "cpu": [ "riscv64" ], @@ -2674,9 +2708,9 @@ } }, "node_modules/sass-embedded-android-x64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.82.0.tgz", - "integrity": "sha512-EVlybGTgJ8wNLyWj8RUatPXSnmIcvCsx3EfsRfBfhGihLbn4NNpavYO9QsvZzI2XWbJqHLBCd+CvkTcDw/TaSQ==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.83.0.tgz", + "integrity": "sha512-WqIay/72ncyf9Ph4vS742J3a73wZihWmzFUwpn1OD6lme1Aj4eWzWIve5IVnlTEJgcZcDHu6ECID9IZgehJKoA==", "cpu": [ "x64" ], @@ -2692,9 +2726,9 @@ } }, "node_modules/sass-embedded-darwin-arm64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.82.0.tgz", - "integrity": "sha512-LvdJPojjKlNGYOB0nSUR/ZtMDuAF4puspHlwK42aA/qK292bfSkMUKZPPapB2aSRwccc/ieBq5fI7n/WHrOCVw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.83.0.tgz", + "integrity": "sha512-XQl9QqgxFFIPm/CzHhmppse5o9ocxrbaAdC2/DAnlAqvYWBBtgFqPjGoYlej13h9SzfvNoogx+y9r+Ap+e+hYg==", "cpu": [ "arm64" ], @@ -2710,9 +2744,9 @@ } }, "node_modules/sass-embedded-darwin-x64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.82.0.tgz", - "integrity": "sha512-6LfnD6YmG1aBfd3ReqMOJDb6Pg2Z/hmlJB7nU+Lb3E+hCNjAZAgeUHQxU/Pm1eIqJJTU/h4ib5QP0Pt9O8yVnw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.83.0.tgz", + "integrity": "sha512-ERQ7Tvp1kFOW3ux4VDFIxb7tkYXHYc+zJpcrbs0hzcIO5ilIRU2tIOK1OrNwrFO6Qxyf7AUuBwYKLAtIU/Nz7g==", "cpu": [ "x64" ], @@ -2728,9 +2762,9 @@ } }, "node_modules/sass-embedded-linux-arm": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.82.0.tgz", - "integrity": "sha512-ozjdC5rWzyi5Vo300I4tVZzneXOTQUiaxOr7DjtN26HuFaGAGCGmvThh2BRV4RvySg++5H9rdFu+VgyUQ5iukw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.83.0.tgz", + "integrity": "sha512-baG9RYBJxUFmqwDNC9h9ZFElgJoyO3jgHGjzEZ1wHhIS9anpG+zZQvO8bHx3dBpKEImX+DBeLX+CxsFR9n81gQ==", "cpu": [ "arm" ], @@ -2746,9 +2780,9 @@ } }, "node_modules/sass-embedded-linux-arm64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.82.0.tgz", - "integrity": "sha512-590/y0HJr/JiyxaqgR7Xf9P20BIhJ+zhB/afAnVuZe/4lEfCpTyM5xMe2+sKLsqtrVyzs9Zm/M4S4ASUOPCggA==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.83.0.tgz", + "integrity": "sha512-syEAVTJt4qhaMLxrSwOWa46zdqHJdnqJkLUK+t9aCr8xqBZLPxSUeIGji76uOehQZ1C+KGFj6n9xstHN6wzOJw==", "cpu": [ "arm64" ], @@ -2764,9 +2798,9 @@ } }, "node_modules/sass-embedded-linux-ia32": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.82.0.tgz", - "integrity": "sha512-hpc4acZ3UTjjJ3Q/GUXqQOCSml6AFKaku0HMawra9bKyRmOpxn8V5hqgXeOWVjK2oQzCmCnJvwKoQUP+S/SIYQ==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.83.0.tgz", + "integrity": "sha512-RRBxQxMpoxu5+XcSSc6QR/o9asEwUzR8AbCS83RaXcdTIHTa/CccQsiAoDDoPlRsMTLqnzs0LKL4CfOsf7zBbA==", "cpu": [ "ia32" ], @@ -2782,9 +2816,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.82.0.tgz", - "integrity": "sha512-R5PQmY/I+GSoMtfLo8GgHkvF/q6x6y8VNM7yu/Ac1mJj86n48VFi29W1HfY2496+Q6cpAq7toobDj7YfldIdVA==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.83.0.tgz", + "integrity": "sha512-Yc7u2TelCfBab+PRob9/MNJFh3EooMiz4urvhejXkihTiKSHGCv5YqDdtWzvyb9tY2Jb7YtYREVuHwfdVn3dTQ==", "cpu": [ "arm" ], @@ -2800,9 +2834,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.82.0.tgz", - "integrity": "sha512-bc2MUSMv/jabnNGEyKP2jQAYZoEzTT/c633W6QoeSEWETGCuTNjaHvWWE6qSI6/UfRg1EpuV1LQA2jPMzZfv/w==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.83.0.tgz", + "integrity": "sha512-Y7juhPHClUO2H5O+u+StRy6SEAcwZ+hTEk5WJdEmo1Bb1gDtfHvJaWB/iFZJ2tW0W1e865AZeUrC4OcOFjyAQA==", "cpu": [ "arm64" ], @@ -2818,9 +2852,9 @@ } }, "node_modules/sass-embedded-linux-musl-ia32": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.82.0.tgz", - "integrity": "sha512-ZQKCFKm5TBcJ19UG6uUQmIKfVCJIWMb7e1a93lGeujSb9gyKF5Fb6MN3tuExoT7iFK8zU0Z9iyHqh93F58lcCw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.83.0.tgz", + "integrity": "sha512-arQeYwGmwXV8byx5G1PtSzZWW1jbkfR5qrIHMEbTFSAvAxpqjgSvCvrHMOFd73FcMxVaYh4BX9LQNbKinkbEdg==", "cpu": [ "ia32" ], @@ -2836,9 +2870,9 @@ } }, "node_modules/sass-embedded-linux-musl-riscv64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.82.0.tgz", - "integrity": "sha512-5meSU8BHFeaT09RWfkuUrikRlC+WZcYb9To7MpfV1d9nlD7CZ2xydPExK+mj3DqRuQvTbvhMPcr7f+pHlgHINQ==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.83.0.tgz", + "integrity": "sha512-E6uzlIWz59rut+Z3XR6mLG915zNzv07ISvj3GUNZENdHM7dF8GQ//ANoIpl5PljMQKp89GnYdvo6kj2gnaBf/g==", "cpu": [ "riscv64" ], @@ -2854,9 +2888,9 @@ } }, "node_modules/sass-embedded-linux-musl-x64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.82.0.tgz", - "integrity": "sha512-ASLAMfjWv7YEPBvEOVlb3zzHq8l4Y9Eh4x3m7B1dNauGVbO11Yng5cPCX/XbwGVf30BtE75pwqvV7oXxBtN15w==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.83.0.tgz", + "integrity": "sha512-eAMK6tyGqvqr21r9g8BnR3fQc1rYFj85RGduSQ3xkITZ6jOAnOhuU94N5fwRS852Hpws0lXhET+7JHXgg3U18w==", "cpu": [ "x64" ], @@ -2872,9 +2906,9 @@ } }, "node_modules/sass-embedded-linux-riscv64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.82.0.tgz", - "integrity": "sha512-qWvRDXCXH3GzD8OcP0ntd8gBTK3kZyUeyXmxQDZyEtMAM4STC2Tn7+5+2JYYHlppzqWnZPFBNESvpKeOtHaBBw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.83.0.tgz", + "integrity": "sha512-Ojpi78pTv02sy2fUYirRGXHLY3fPnV/bvwuC2i5LwPQw2LpCcFyFTtN0c5h4LJDk9P6wr+/ZB/JXU8tHIOlK+Q==", "cpu": [ "riscv64" ], @@ -2890,9 +2924,9 @@ } }, "node_modules/sass-embedded-linux-x64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.82.0.tgz", - "integrity": "sha512-AmRaHqShztwfep+M4NagdGaY7fTyWGSOM3k4Z/dd7q4nZclXbALLqNJtKx8xOM7A41LHYJ9zDpIBVRkrh0PzTA==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.83.0.tgz", + "integrity": "sha512-3iLjlXdoPfgZRtX4odhRvka1BQs5mAXqfCtDIQBgh/o0JnGPzJIWWl9bYLpHxK8qb+uyVBxXYgXpI0sCzArBOw==", "cpu": [ "x64" ], @@ -2908,9 +2942,9 @@ } }, "node_modules/sass-embedded-win32-arm64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.82.0.tgz", - "integrity": "sha512-zL9JDQZHXHSGAZe5DqSrR86wMHbm9QPziU4/3hoIG+99StuS74CuV42+hw/+FXXBkXMWbjKWsyF/HZt+I/wJuw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.83.0.tgz", + "integrity": "sha512-iOHw/8/t2dlTW3lOFwG5eUbiwhEyGWawivlKWJ8lkXH7fjMpVx2VO9zCFAm8RvY9xOHJ9sf1L7g5bx3EnNP9BQ==", "cpu": [ "arm64" ], @@ -2926,9 +2960,9 @@ } }, "node_modules/sass-embedded-win32-ia32": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.82.0.tgz", - "integrity": "sha512-xE+AzLquCkFPnnpo0NHjQdLRIhG1bVs42xIKx42aUbVLYKkBDvbBGpw6EtTscRMyvcjoOqGH5saRvSFComUQcw==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.83.0.tgz", + "integrity": "sha512-2PxNXJ8Pad4geVcTXY4rkyTr5AwbF8nfrCTDv0ulbTvPhzX2mMKEGcBZUXWn5BeHZTBc6whNMfS7d5fQXR9dDQ==", "cpu": [ "ia32" ], @@ -2944,9 +2978,9 @@ } }, "node_modules/sass-embedded-win32-x64": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.82.0.tgz", - "integrity": "sha512-cEgfOQG5womOzzk16ReTv2dxPq5BG16LgLUold/LH9IZH86u4E/MN7Fspf4RWeEJ2EcLdew9QYSC2YWs1l98dQ==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.83.0.tgz", + "integrity": "sha512-muBXkFngM6eLTNqOV0FQi7Dv9s+YRQ42Yem26mosdan/GmJQc81deto6uDTgrYn+bzFNmiXcOdfm+0MkTWK3OQ==", "cpu": [ "x64" ], @@ -3088,6 +3122,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "node-addon-api": "^8.0.0", "node-gyp-build": "^4.8.0" @@ -3129,6 +3164,7 @@ "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": "^18 || ^20 || >= 21" } @@ -3231,9 +3267,9 @@ } }, "node_modules/web-tree-sitter": { - "version": "0.24.3", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.3.tgz", - "integrity": "sha512-uR9YNewr1S2EzPKE+y39nAwaTyobBaZRG/IsfkB/OT4v0lXtNj5WjtHKgn2h7eOYUWIZh5rK9Px7tI6S9CRKdA==", + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.5.tgz", + "integrity": "sha512-+J/2VSHN8J47gQUAvF8KDadrfz6uFYVjxoxbKWDoXVsH2u7yLdarCnIURnrMA6uSRkgX3SdmqM5BOoQjPdSh5w==", "license": "MIT", "optional": true }, From 9d205cfdcc0c18b2f3ad2a8351844119df35fdb7 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 14 Dec 2024 23:01:05 +1300 Subject: [PATCH 15/15] Release v1.21.7 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 180febe505..191ae867d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ Notable changes to Mailpit will be documented in this file. +## [v1.21.7] + +### Chore +- Update node dependencies +- Update Go dependencies +- Bump Go version for automated testing +- Move smtpd & pop3 modules to internal +- Stricter SMTP 'MAIL FROM' & 'RCPT TO' handling ([#409](https://github.com/axllent/mailpit/issues/409)) +- Display "To" details in mobile messages list +- Display "From" details in message sidebar (desktop) ([#403](https://github.com/axllent/mailpit/issues/403)) + +### Fix +- Ignore unsupported optional SMTP 'MAIL FROM' parameters ([#407](https://github.com/axllent/mailpit/issues/407)) +- Prevent splitting multi-byte characters in message snippets ([#404](https://github.com/axllent/mailpit/issues/404)) + +### Testing +- Add smtpd tests + + ## [v1.21.6] ### Feature