aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dashboard/app/email_test.go58
-rw-r--r--dashboard/app/entities.go1
-rw-r--r--dashboard/app/jobs.go59
-rw-r--r--dashboard/app/jobs_test.go9
-rw-r--r--dashboard/app/notifications_test.go30
-rw-r--r--dashboard/app/reporting.go103
-rw-r--r--dashboard/app/reporting_email.go5
-rw-r--r--dashboard/app/util_test.go8
-rw-r--r--dashboard/dashapi/dashapi.go1
-rw-r--r--pkg/email/parser.go11
10 files changed, 142 insertions, 143 deletions
diff --git a/dashboard/app/email_test.go b/dashboard/app/email_test.go
index 4cb254854..07a242e22 100644
--- a/dashboard/app/email_test.go
+++ b/dashboard/app/email_test.go
@@ -22,7 +22,7 @@ func TestEmailReport(t *testing.T) {
c.client2.UploadBuild(build)
crash := testCrash(build, 1)
- crash.Maintainers = []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`}
+ crash.Maintainers = []string{`"Foo Bar" <foo@bar.com>`, `bar@foo.com`, `idont@want.EMAILS`}
c.client2.ReportCrash(crash)
// Report the crash over email and check all fields.
@@ -52,7 +52,7 @@ console output: %[2]v
kernel config: %[3]v
dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
compiler: compiler1
-CC: [bar@foo.com foo@bar.com]
+CC: [bar@foo.com foo@bar.com idont@want.EMAILS]
Unfortunately, I don't have any reproducer for this crash yet.
@@ -109,6 +109,14 @@ For more options, visit https://groups.google.com/d/optout.
// We used to extract "#syz fix: exact-commit-title" from it.
c.incomingEmail(sender0, body0)
+ c.incomingEmail(sender0, "I don't want emails", EmailOptFrom(`"idont" <idont@WANT.emails>`))
+ c.expectNoEmail()
+
+ // This person sends an email and is listed as a maintainer, but opt-out of emails.
+ // We should not send anything else to them for this bug. Also don't warn about no mailing list in CC.
+ c.incomingEmail(sender0, "#syz uncc", EmailOptFrom(`"IDONT" <Idont@want.emails>`), EmailOptCC(nil))
+ c.expectNoEmail()
+
// Now report syz reproducer and check updated email.
build2 := testBuild(10)
build2.Arch = "386"
@@ -310,8 +318,7 @@ unknown command "bad-command"
// Now mark the bug as fixed.
c.incomingEmail(sender1, "#syz fix: some: commit title")
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Check that the commit is now passed to builders.
builderPollResp, _ := c.client2.BuilderPoll(build.Manager)
@@ -361,9 +368,6 @@ Content-Type: text/plain
#syz upstream
`, sender)
c.expectOK(c.POST("/_ah/mail/", incoming1))
-
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
}
// Basic dup scenario: mark one bug as dup of another.
@@ -383,24 +387,25 @@ func TestEmailDup(t *testing.T) {
c.client2.ReportCrash(crash2)
c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 2)
- msg1 := <-c.emailSink
- msg2 := <-c.emailSink
+ msg1 := c.pollEmailBug()
+ msg2 := c.pollEmailBug()
// Dup crash2 to crash1.
c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title")
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Second crash happens again
crash2.ReproC = []byte("int main() {}")
c.client2.ReportCrash(crash2)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Now close the original bug, and check that new bugs for dup are now created.
c.incomingEmail(msg1.Sender, "#syz invalid")
+ // uncc command must not trugger error reply even for closed bug.
+ c.incomingEmail(msg1.Sender, "#syz uncc", EmailOptCC(nil))
+ c.expectNoEmail()
+
// New crash must produce new bug in the first reporting.
c.client2.ReportCrash(crash2)
{
@@ -425,25 +430,21 @@ func TestEmailUndup(t *testing.T) {
c.client2.ReportCrash(crash2)
c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 2)
- msg1 := <-c.emailSink
- msg2 := <-c.emailSink
+ msg1 := c.pollEmailBug()
+ msg2 := c.pollEmailBug()
// Dup crash2 to crash1.
c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title")
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Undup crash2.
c.incomingEmail(msg2.Sender, "#syz undup")
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Now close the original bug, and check that new crashes for the dup does not create bugs.
c.incomingEmail(msg1.Sender, "#syz invalid")
c.client2.ReportCrash(crash2)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
}
func TestEmailCrossReportingDup(t *testing.T) {
@@ -491,13 +492,9 @@ func TestEmailCrossReportingDup(t *testing.T) {
c.incomingEmail(bugSender, "#syz dup: "+crash2.Title)
if test.result {
- if len(c.emailSink) != 0 {
- msg := <-c.emailSink
- t.Fatalf("unexpected reply: %s\n%s\n", msg.Subject, msg.Body)
- }
+ c.expectNoEmail()
} else {
- c.expectEQ(len(c.emailSink), 1)
- msg := <-c.emailSink
+ msg := c.pollEmailBug()
if !strings.Contains(msg.Body, "> #syz dup:") ||
!strings.Contains(msg.Body, "Can't dup bug to a bug in different reporting") {
c.t.Fatalf("bad reply body:\n%v", msg.Body)
@@ -512,8 +509,7 @@ func TestEmailErrors(t *testing.T) {
// No reply for email without bug hash and no commands.
c.incomingEmail("syzbot@testapp.appspotmail.com", "Investment Proposal")
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// If email contains a command we need to reply.
c.incomingEmail("syzbot@testapp.appspotmail.com", "#syz invalid")
diff --git a/dashboard/app/entities.go b/dashboard/app/entities.go
index 32cf37926..a3ab5c79b 100644
--- a/dashboard/app/entities.go
+++ b/dashboard/app/entities.go
@@ -89,6 +89,7 @@ type Bug struct {
CommitInfo []Commit // additional info for commits (for historical reasons parallel array to Commits)
HappenedOn []string `datastore:",noindex"` // list of managers
PatchedOn []string `datastore:",noindex"` // list of managers
+ UNCC []string // don't CC these emails on this bug
}
type Commit struct {
diff --git a/dashboard/app/jobs.go b/dashboard/app/jobs.go
index 1bb67a991..7bff92b85 100644
--- a/dashboard/app/jobs.go
+++ b/dashboard/app/jobs.go
@@ -572,10 +572,6 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c
if err != nil {
return nil, err
}
- kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig)
- if err != nil {
- return nil, err
- }
bugKey := jobKey.Parent()
crashKey := datastore.NewKey(c, "Crash", "", job.CrashID, bugKey)
crash := new(Crash)
@@ -590,10 +586,6 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c
if bugReporting == nil {
return nil, fmt.Errorf("job bug has no reporting %q", job.Reporting)
}
- creditEmail, err := email.AddAddrContext(ownEmail(c), bugReporting.ID)
- if err != nil {
- return nil, err
- }
var typ dashapi.ReportType
switch job.Type {
case JobTestPatch:
@@ -606,39 +598,21 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c
return nil, fmt.Errorf("unknown job type %v", job.Type)
}
rep := &dashapi.BugReport{
- Type: typ,
- Namespace: job.Namespace,
- Config: reportingConfig,
- ID: bugReporting.ID,
- JobID: extJobID(jobKey),
- ExtID: job.ExtID,
- Title: bug.displayTitle(),
- Link: fmt.Sprintf("%v/bug?extid=%v", appURL(c), bugReporting.ID),
- CreditEmail: creditEmail,
- CC: job.CC,
- Log: crashLog,
- LogLink: externalLink(c, textCrashLog, job.CrashLog),
- Report: report,
- ReportLink: externalLink(c, textCrashReport, job.CrashReport),
- OS: build.OS,
- Arch: build.Arch,
- VMArch: build.VMArch,
- UserSpaceArch: kernelArch(build.Arch),
- CompilerID: build.CompilerID,
- KernelRepo: build.KernelRepo,
- KernelRepoAlias: kernelRepoInfo(build).Alias,
- KernelBranch: build.KernelBranch,
- KernelCommit: build.KernelCommit,
- KernelCommitTitle: build.KernelCommitTitle,
- KernelCommitDate: build.KernelCommitDate,
- KernelConfig: kernelConfig,
- KernelConfigLink: externalLink(c, textKernelConfig, build.KernelConfig),
- ReproCLink: externalLink(c, textReproC, crash.ReproC),
- ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz),
- CrashTitle: job.CrashTitle,
- Error: jobError,
- ErrorLink: externalLink(c, textError, job.Error),
- PatchLink: externalLink(c, textPatch, job.Patch),
+ Type: typ,
+ Config: reportingConfig,
+ JobID: extJobID(jobKey),
+ ExtID: job.ExtID,
+ CC: job.CC,
+ Log: crashLog,
+ LogLink: externalLink(c, textCrashLog, job.CrashLog),
+ Report: report,
+ ReportLink: externalLink(c, textCrashReport, job.CrashReport),
+ ReproCLink: externalLink(c, textReproC, crash.ReproC),
+ ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz),
+ CrashTitle: job.CrashTitle,
+ Error: jobError,
+ ErrorLink: externalLink(c, textError, job.Error),
+ PatchLink: externalLink(c, textPatch, job.Patch),
}
if job.Type == JobBisectCause || job.Type == JobBisectFix {
kernelRepo := kernelRepoInfo(build)
@@ -659,6 +633,9 @@ func createBugReportForJob(c context.Context, job *Job, jobKey *datastore.Key, c
rep.Error = rep.Error[len(rep.Error)-maxInlineError:]
rep.ErrorTruncated = true
}
+ if err := fillBugReport(c, rep, bug, bugReporting, build); err != nil {
+ return nil, err
+ }
return rep, nil
}
diff --git a/dashboard/app/jobs_test.go b/dashboard/app/jobs_test.go
index a5783b63b..73e324733 100644
--- a/dashboard/app/jobs_test.go
+++ b/dashboard/app/jobs_test.go
@@ -74,8 +74,7 @@ func TestJob(t *testing.T) {
c.incomingEmail(sender, "#syz test: git://git.git/git.git kernel-branch\n"+patch,
EmailOptFrom("\"foo\" <blAcklisteD@dOmain.COM>"))
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
pollResp := c.client2.pollJobs(build.Manager)
c.expectEQ(pollResp.ID, "")
@@ -83,15 +82,13 @@ func TestJob(t *testing.T) {
c.incomingEmail(sender, "#syz test: git://git.git/git.git kernel-branch\n"+patch,
EmailOptMessageID(1), EmailOptFrom("test@requester.com"),
EmailOptCC([]string{"somebody@else.com"}))
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// A dup of the same request with the same Message-ID.
c.incomingEmail(sender, "#syz test: git://git.git/git.git kernel-branch\n"+patch,
EmailOptMessageID(1), EmailOptFrom("test@requester.com"),
EmailOptCC([]string{"somebody@else.com"}))
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
pollResp = c.client2.pollJobs("foobar")
c.expectEQ(pollResp.ID, "")
diff --git a/dashboard/app/notifications_test.go b/dashboard/app/notifications_test.go
index d59465e57..12e9aae73 100644
--- a/dashboard/app/notifications_test.go
+++ b/dashboard/app/notifications_test.go
@@ -27,8 +27,7 @@ func TestEmailNotifUpstreamEmbargo(t *testing.T) {
// Upstreaming happens after 14 days, so no emails yet.
c.advanceTime(13 * 24 * time.Hour)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Now we should get notification about upstreaming and upstream report:
c.advanceTime(2 * 24 * time.Hour)
@@ -54,8 +53,7 @@ func TestEmailNotifUpstreamSkip(t *testing.T) {
c.expectEQ(report.To, []string{"test@syzkaller.com"})
// No emails yet.
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Now upload repro and it should be auto-upstreamed.
crash.ReproOpts = []byte("repro opts")
@@ -82,16 +80,13 @@ func TestEmailNotifBadFix(t *testing.T) {
c.expectEQ(report.To, []string{"test@syzkaller.com"})
c.incomingEmail(report.Sender, "#syz fix: some: commit title")
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Notification about bad fixing commit should be send after 90 days.
c.advanceTime(50 * 24 * time.Hour)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
c.advanceTime(35 * 24 * time.Hour)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
c.advanceTime(10 * 24 * time.Hour)
notif := c.pollEmailBug()
if !strings.Contains(notif.Body, "This bug is marked as fixed by commit:\nsome: commit title\n") {
@@ -99,8 +94,7 @@ func TestEmailNotifBadFix(t *testing.T) {
}
// No notifications for another 14 days, then another one.
c.advanceTime(13 * 24 * time.Hour)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
c.advanceTime(2 * 24 * time.Hour)
notif = c.pollEmailBug()
if !strings.Contains(notif.Body, "This bug is marked as fixed by commit:\nsome: commit title\n") {
@@ -127,13 +121,11 @@ func TestEmailNotifObsoleted(t *testing.T) {
// Bug is open, new crashes don't create new bug.
c.client2.ReportCrash(crash)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Not yet.
c.advanceTime(179 * 24 * time.Hour)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Now!
c.advanceTime(2 * 24 * time.Hour)
@@ -199,8 +191,7 @@ func TestEmailNotifNotObsoleted(t *testing.T) {
report4 = c.pollEmailBug()
c.advanceTime(179 * 24 * time.Hour)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
c.client2.ReportCrash(crash2)
c.incomingEmail(report3.Sender, "I am looking at it")
@@ -209,8 +200,7 @@ func TestEmailNotifNotObsoleted(t *testing.T) {
// Only crash 4 is obsoleted.
notif := c.pollEmailBug()
c.expectEQ(notif.Sender, report4.Sender)
- c.expectOK(c.GET("/email_poll"))
- c.expectEQ(len(c.emailSink), 0)
+ c.expectNoEmail()
// Crash 3 also obsoleted after some time.
c.advanceTime(20 * 24 * time.Hour)
diff --git a/dashboard/app/reporting.go b/dashboard/app/reporting.go
index 1467f63f6..31306b53a 100644
--- a/dashboard/app/reporting.go
+++ b/dashboard/app/reporting.go
@@ -343,14 +343,6 @@ func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datast
if err != nil {
return nil, err
}
- kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig)
- if err != nil {
- return nil, err
- }
- creditEmail, err := email.AddAddrContext(ownEmail(c), bugReporting.ID)
- if err != nil {
- return nil, err
- }
typ := dashapi.ReportNew
if !bugReporting.Reported.IsZero() {
typ = dashapi.ReportRepro
@@ -358,41 +350,23 @@ func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datast
kernelRepo := kernelRepoInfo(build)
rep := &dashapi.BugReport{
- Type: typ,
- Namespace: bug.Namespace,
- Config: reportingConfig,
- ID: bugReporting.ID,
- ExtID: bugReporting.ExtID,
- First: bugReporting.Reported.IsZero(),
- Moderation: reporting.moderation,
- Title: bug.displayTitle(),
- Link: fmt.Sprintf("%v/bug?extid=%v", appURL(c), bugReporting.ID),
- CreditEmail: creditEmail,
- Log: crashLog,
- LogLink: externalLink(c, textCrashLog, crash.Log),
- Report: report,
- ReportLink: externalLink(c, textCrashReport, crash.Report),
- Maintainers: append(crash.Maintainers, kernelRepo.CC...),
- OS: build.OS,
- Arch: build.Arch,
- VMArch: build.VMArch,
- UserSpaceArch: kernelArch(build.Arch),
- CompilerID: build.CompilerID,
- KernelRepo: build.KernelRepo,
- KernelRepoAlias: kernelRepo.Alias,
- KernelBranch: build.KernelBranch,
- KernelCommit: build.KernelCommit,
- KernelCommitTitle: build.KernelCommitTitle,
- KernelCommitDate: build.KernelCommitDate,
- KernelConfig: kernelConfig,
- KernelConfigLink: externalLink(c, textKernelConfig, build.KernelConfig),
- ReproC: reproC,
- ReproCLink: externalLink(c, textReproC, crash.ReproC),
- ReproSyz: reproSyz,
- ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz),
- CrashID: crashKey.IntID(),
- NumCrashes: bug.NumCrashes,
- HappenedOn: managersToRepos(c, bug.Namespace, bug.HappenedOn),
+ Type: typ,
+ Config: reportingConfig,
+ ExtID: bugReporting.ExtID,
+ First: bugReporting.Reported.IsZero(),
+ Moderation: reporting.moderation,
+ Log: crashLog,
+ LogLink: externalLink(c, textCrashLog, crash.Log),
+ Report: report,
+ ReportLink: externalLink(c, textCrashReport, crash.Report),
+ Maintainers: append(crash.Maintainers, kernelRepo.CC...),
+ ReproC: reproC,
+ ReproCLink: externalLink(c, textReproC, crash.ReproC),
+ ReproSyz: reproSyz,
+ ReproSyzLink: externalLink(c, textReproSyz, crash.ReproSyz),
+ CrashID: crashKey.IntID(),
+ NumCrashes: bug.NumCrashes,
+ HappenedOn: managersToRepos(c, bug.Namespace, bug.HappenedOn),
}
if bugReporting.CC != "" {
rep.CC = strings.Split(bugReporting.CC, "|")
@@ -400,9 +374,48 @@ func createBugReport(c context.Context, bug *Bug, crash *Crash, crashKey *datast
if bug.BisectCause == BisectYes {
rep.BisectCause = bisectFromJob(c, rep, job)
}
+ if err := fillBugReport(c, rep, bug, bugReporting, build); err != nil {
+ return nil, err
+ }
return rep, nil
}
+// fillBugReport fills common report fields for bug and job reports.
+func fillBugReport(c context.Context, rep *dashapi.BugReport, bug *Bug, bugReporting *BugReporting,
+ build *Build) error {
+ kernelConfig, _, err := getText(c, textKernelConfig, build.KernelConfig)
+ if err != nil {
+ return err
+ }
+ creditEmail, err := email.AddAddrContext(ownEmail(c), bugReporting.ID)
+ if err != nil {
+ return err
+ }
+ rep.Namespace = bug.Namespace
+ rep.ID = bugReporting.ID
+ rep.Title = bug.displayTitle()
+ rep.Link = fmt.Sprintf("%v/bug?extid=%v", appURL(c), bugReporting.ID)
+ rep.CreditEmail = creditEmail
+ rep.OS = build.OS
+ rep.Arch = build.Arch
+ rep.VMArch = build.VMArch
+ rep.UserSpaceArch = kernelArch(build.Arch)
+ rep.CompilerID = build.CompilerID
+ rep.KernelRepo = build.KernelRepo
+ rep.KernelRepoAlias = kernelRepoInfo(build).Alias
+ rep.KernelBranch = build.KernelBranch
+ rep.KernelCommit = build.KernelCommit
+ rep.KernelCommitTitle = build.KernelCommitTitle
+ rep.KernelCommitDate = build.KernelCommitDate
+ rep.KernelConfig = kernelConfig
+ rep.KernelConfigLink = externalLink(c, textKernelConfig, build.KernelConfig)
+ for _, addr := range bug.UNCC {
+ rep.CC = email.RemoveFromEmailList(rep.CC, addr)
+ rep.Maintainers = email.RemoveFromEmailList(rep.Maintainers, addr)
+ }
+ return nil
+}
+
func loadBisectJob(c context.Context, bug *Bug) (*Job, *Crash, *datastore.Key, *datastore.Key, error) {
bugKey := bug.key(c)
var jobs []*Job
@@ -679,7 +692,7 @@ func incomingCommandTx(c context.Context, now time.Time, cmd *dashapi.BugUpdate,
if bugReporting.Link == "" {
bugReporting.Link = cmd.Link
}
- if len(cmd.CC) != 0 {
+ if len(cmd.CC) != 0 && cmd.Status != dashapi.BugStatusUnCC {
merged := email.MergeEmailLists(strings.Split(bugReporting.CC, "|"), cmd.CC)
bugReporting.CC = strings.Join(merged, "|")
}
@@ -755,6 +768,8 @@ func incomingCommandCmd(c context.Context, now time.Time, cmd *dashapi.BugUpdate
bug.DupOf = dupHash
case dashapi.BugStatusUpdate:
// Just update Link, Commits, etc below.
+ case dashapi.BugStatusUnCC:
+ bug.UNCC = email.MergeEmailLists(bug.UNCC, cmd.CC)
default:
return false, internalError, fmt.Errorf("unknown bug status %v", cmd.Status)
}
diff --git a/dashboard/app/reporting_email.go b/dashboard/app/reporting_email.go
index 7d06eeae3..d05b0cd59 100644
--- a/dashboard/app/reporting_email.go
+++ b/dashboard/app/reporting_email.go
@@ -326,6 +326,9 @@ func incomingMail(c context.Context, r *http.Request) error {
}
cmd.Status = dashapi.BugStatusDup
cmd.DupOf = msg.CommandArgs
+ case "uncc", "uncc:":
+ cmd.Status = dashapi.BugStatusUnCC
+ cmd.CC = []string{email.CanonicalEmail(msg.From)}
default:
return replyTo(c, msg, fmt.Sprintf("unknown command %q", msg.Command), nil)
}
@@ -336,7 +339,7 @@ func incomingMail(c context.Context, r *http.Request) error {
if !ok && reply != "" {
return replyTo(c, msg, reply, nil)
}
- if !mailingListInCC && msg.Command != "" {
+ if !mailingListInCC && msg.Command != "" && cmd.Status != dashapi.BugStatusUnCC {
warnMailingListInCC(c, msg, mailingList)
}
return nil
diff --git a/dashboard/app/util_test.go b/dashboard/app/util_test.go
index 74540d5b1..fbe11b017 100644
--- a/dashboard/app/util_test.go
+++ b/dashboard/app/util_test.go
@@ -252,6 +252,14 @@ func (c *Ctx) pollEmailBug() *aemail.Message {
return <-c.emailSink
}
+func (c *Ctx) expectNoEmail() {
+ c.expectOK(c.GET("/email_poll"))
+ if len(c.emailSink) != 0 {
+ msg := <-c.emailSink
+ c.t.Fatalf("\n%v: got expected email: %v\n%s", caller(0), msg.Subject, msg.Body)
+ }
+}
+
type apiClient struct {
*Ctx
*dashapi.Dashboard
diff --git a/dashboard/dashapi/dashapi.go b/dashboard/dashapi/dashapi.go
index 42200eb15..d53786476 100644
--- a/dashboard/dashapi/dashapi.go
+++ b/dashboard/dashapi/dashapi.go
@@ -469,6 +469,7 @@ const (
BugStatusInvalid
BugStatusDup
BugStatusUpdate // aux info update (i.e. ExtID/Link/CC)
+ BugStatusUnCC // don't CC sender on any future communication
)
const (
diff --git a/pkg/email/parser.go b/pkg/email/parser.go
index 1b2dbc747..ce652c5da 100644
--- a/pkg/email/parser.go
+++ b/pkg/email/parser.go
@@ -330,3 +330,14 @@ func MergeEmailLists(lists ...[]string) []string {
}
return result
}
+
+func RemoveFromEmailList(list []string, toRemove string) []string {
+ var result []string
+ toRemove = CanonicalEmail(toRemove)
+ for _, email := range list {
+ if CanonicalEmail(email) != toRemove {
+ result = append(result, email)
+ }
+ }
+ return result
+}