func (a *rethinkAPIServer) ListJobInfos(ctx context.Context, request *pps.ListJobRequest) (response *persist.JobInfos, err error) { defer func(start time.Time) { a.Log(request, response, err, time.Since(start)) }(time.Now()) query := a.getTerm(jobInfosTable) if request.Pipeline != nil && request.Input != nil { query = query.GetAllByIndex( pipelineNameAndInputIndex, gorethink.Expr([]string{request.Pipeline.Name, request.Input.Repo.Name, request.Input.Id}), ) } else if request.Pipeline != nil { query = query.GetAllByIndex( pipelineNameIndex, request.Pipeline.Name, ) } else if request.Input != nil { query = query.GetAllByIndex( inputIndex, gorethink.Expr([]string{request.Input.Repo.Name, request.Input.Id}), ) } jobInfoObjs, err := a.getAllMessages( jobInfosTable, func() proto.Message { return &persist.JobInfo{} }, ) if err != nil { return nil, err } jobInfos := make([]*persist.JobInfo, len(jobInfoObjs)) for i, jobInfoObj := range jobInfoObjs { jobInfos[i] = jobInfoObj.(*persist.JobInfo) } sortJobInfosByTimestampDesc(jobInfos) return &persist.JobInfos{ JobInfo: jobInfos, }, nil }
func (f *FilesTable) GetInEmail(owner string, email string, name string) ([]*models.File, error) { e, err := f.Emails.GetEmail(email) if err != nil { return nil, err } query, err := f.GetTable().Filter(func(row gorethink.Term) gorethink.Term { return gorethink.And( row.Field("owner").Eq(gorethink.Expr(owner)), gorethink.Expr(e.Files).Contains(row.Field("id")), row.Field("name").Eq(gorethink.Expr(name)), ) }).Run(f.GetSession()) if err != nil { return nil, err } var result []*models.File err = query.All(&result) if err != nil { return nil, err } return result, nil }
func (a *rethinkAPIServer) InspectJob(ctx context.Context, request *pps.InspectJobRequest) (response *persist.JobInfo, err error) { defer func(start time.Time) { a.Log(request, response, err, time.Since(start)) }(time.Now()) jobInfo := &persist.JobInfo{} var mustHaveFields []interface{} if request.BlockOutput { mustHaveFields = append(mustHaveFields, "OutputCommit") } if request.BlockState { mustHaveFields = append(mustHaveFields, "State") } if err := a.waitMessageByPrimaryKey( jobInfosTable, request.Job.Id, jobInfo, func(jobInfo gorethink.Term) gorethink.Term { blockOutput := jobInfo.HasFields("OutputCommit") blockState := jobInfo.Field("State").Ne(pps.JobState_JOB_STATE_RUNNING) if request.BlockOutput && request.BlockState { return blockOutput.And(blockState) } else if request.BlockOutput { return blockOutput } else if request.BlockState { return blockState } return gorethink.Expr(true) }, ); err != nil { return nil, err } return jobInfo, nil }
func (a *rethinkAPIServer) InspectJob(ctx context.Context, request *ppsclient.InspectJobRequest) (response *persist.JobInfo, err error) { defer func(start time.Time) { a.Log(request, response, err, time.Since(start)) }(time.Now()) if request.Job == nil { return nil, fmt.Errorf("request.Job cannot be nil") } jobInfo := &persist.JobInfo{} var mustHaveFields []interface{} if request.BlockState { mustHaveFields = append(mustHaveFields, "State") } if err := a.waitMessageByPrimaryKey( jobInfosTable, request.Job.ID, jobInfo, func(jobInfo gorethink.Term) gorethink.Term { if request.BlockState { return gorethink.Or( jobInfo.Field("State").Eq(ppsclient.JobState_JOB_SUCCESS), jobInfo.Field("State").Eq(ppsclient.JobState_JOB_FAILURE)) } return gorethink.Expr(true) }, ); err != nil { return nil, err } return jobInfo, nil }
func (a *rethinkAPIServer) ListJobInfos(ctx context.Context, request *ppsclient.ListJobRequest) (response *persist.JobInfos, retErr error) { defer func(start time.Time) { a.Log(request, response, retErr, time.Since(start)) }(time.Now()) query := a.getTerm(jobInfosTable) commitIndexVal, err := genCommitIndex(request.InputCommit) if err != nil { return nil, err } if request.Pipeline != nil && len(request.InputCommit) > 0 { query = query.GetAllByIndex( pipelineNameAndCommitIndex, gorethink.Expr([]interface{}{request.Pipeline.Name, commitIndexVal}), ) } else if request.Pipeline != nil { query = query.GetAllByIndex( pipelineNameIndex, request.Pipeline.Name, ) } else if len(request.InputCommit) > 0 { query = query.GetAllByIndex( commitIndex, gorethink.Expr(commitIndexVal), ) } cursor, err := query.Run(a.session) if err != nil { return nil, err } defer func() { if err := cursor.Close(); err != nil && retErr == nil { retErr = err } }() result := &persist.JobInfos{} for { jobInfo := &persist.JobInfo{} if !cursor.Next(jobInfo) { break } result.JobInfo = append(result.JobInfo, jobInfo) } if err := cursor.Err(); err != nil { return nil, err } return result, nil }
func BenchmarkDelete(b *testing.B) { for n := 0; n < b.N; n++ { _, err := gorethink.Db("test").Table("benchmark_keys_list").Get(table2search).Field("voted").DeleteAt( gorethink.Expr(gorethink.Db("test").Table("benchmark_keys_list").Get(table2search).Field("voted").IndexesOf(key2find).AtIndex(0)), ).Run(session) if err != nil { b.Log(err) b.Fail() } } }
func (f *FilesTable) GetEmailFiles(id string) ([]*models.File, error) { email, err := f.Emails.GetEmail(id) if err != nil { return nil, err } query, err := f.Emails.GetTable().Filter(func(row gorethink.Term) gorethink.Term { return gorethink.Expr(email.Files).Contains(row.Field("id")) }).GetAll().Run(f.Emails.GetSession()) if err != nil { return nil, err } var result []*models.File err = query.All(&result) if err != nil { return nil, err } return result, nil }
func Example() { session, err := r.Connect(r.ConnectOpts{ Address: url, AuthKey: authKey, }) if err != nil { log.Fatalln(err.Error()) } res, err := r.Expr("Hello World").Run(session) if err != nil { log.Fatalln(err.Error()) } var response string err = res.One(&response) if err != nil { log.Fatalln(err.Error()) } fmt.Println(response) }
func PrepareHandler(config *shared.Flags) func(peer smtpd.Peer, env smtpd.Envelope) error { cfg = config // Initialize a new logger log := logrus.New() if config.LogFormatterType == "text" { log.Formatter = &logrus.TextFormatter{ ForceColors: config.ForceColors, } } else if config.LogFormatterType == "json" { log.Formatter = &logrus.JSONFormatter{} } log.Level = logrus.DebugLevel // Initialize the database connection var err error session, err = gorethink.Connect(gorethink.ConnectOpts{ Address: config.RethinkAddress, AuthKey: config.RethinkKey, MaxIdle: 10, Timeout: time.Second * 10, }) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to connect to RethinkDB") } // Connect to NSQ producer, err := nsq.NewProducer(config.NSQDAddress, nsq.NewConfig()) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to connect to NSQd") } // Create a new spamd client spam := spamc.New(config.SpamdAddress, 10) // Last message sent by PrepareHandler log.WithFields(logrus.Fields{ "addr": config.BindAddress, }).Info("Listening for incoming traffic") return func(peer smtpd.Peer, e smtpd.Envelope) error { log.Debug("Started parsing") // Check recipients for Lavaboom users recipients := []interface{}{} for _, recipient := range e.Recipients { log.Printf("EMAIL TO %s", recipient) // Split the email address into username and domain parts := strings.Split(recipient, "@") if len(parts) != 2 { return describeError(fmt.Errorf("Invalid recipient email address")) } // Check if we support that domain if _, ok := domains[parts[1]]; ok { recipients = append(recipients, utils.RemoveDots( utils.NormalizeUsername(parts[0]), ), ) } } log.Debug("Parsed recipients") // If we didn't find a recipient, return an error if len(recipients) == 0 { return describeError(fmt.Errorf("Not supported email domain")) } // Fetch the mapping cursor, err := gorethink.Db(config.RethinkDatabase).Table("addresses").GetAll(recipients...).Run(session) if err != nil { return describeError(err) } defer cursor.Close() var addresses []*models.Address if err := cursor.All(&addresses); err != nil { return describeError(err) } // Transform the mapping into accounts accountIDs := []interface{}{} for _, address := range addresses { accountIDs = append(accountIDs, address.Owner) } // Fetch accounts cursor, err = gorethink.Db(config.RethinkDatabase).Table("accounts").GetAll(accountIDs...).Run(session) if err != nil { return describeError(err) } defer cursor.Close() var accounts []*models.Account if err := cursor.All(&accounts); err != nil { return describeError(err) } // Compare request and result lengths if len(accounts) != len(recipients) { return describeError(fmt.Errorf("One of the email addresses wasn't found")) } log.Debug("Recipients found") // Prepare a variable for the combined keyring of recipients toKeyring := []*openpgp.Entity{} // Fetch users' public keys for _, account := range accounts { account.Key, err = getAccountPublicKey(account) if err != nil { return describeError(err) } toKeyring = append(toKeyring, account.Key) } log.Debug("Fetched keys") // Check in the antispam isSpam := false spamReply, err := spam.Report(string(e.Data)) if err == nil { log.Print(spamReply.Code) log.Print(spamReply.Message) log.Print(spamReply.Vars) } if spamReply.Code == spamc.EX_OK { log.Print("Proper code") if spam, ok := spamReply.Vars["isSpam"]; ok && spam.(bool) { log.Print("It's spam.") isSpam = true } } // Parse the email email, err := ParseEmail(bytes.NewReader(e.Data)) if err != nil { return describeError(err) } // Determine email's kind contentType := email.Headers.Get("Content-Type") kind := "raw" if strings.HasPrefix(contentType, "multipart/encrypted") { // multipart/encrypted is dedicated for PGP/MIME and S/MIME kind = "pgpmime" } else if strings.HasPrefix(contentType, "multipart/mixed") && len(email.Children) >= 2 { // Has manifest? It is an email with a PGP manifest. If not, it's unencrypted. for _, child := range email.Children { if strings.HasPrefix(child.Headers.Get("Content-Type"), "application/x-pgp-manifest") { kind = "manifest" break } } } // Copy kind to a second variable for later parsing initialKind := kind // Debug the kind log.Debugf("Email is %s", kind) // Declare variables used later for data insertion var ( subject string manifest string body string fileIDs = map[string][]string{} files = []*models.File{} ) // Transform raw emails into encrypted with manifests if kind == "raw" { // Prepare variables for manifest generation parts := []*man.Part{} // Parsing vars var ( bodyType string bodyText string ) // Flatten the email var parseBody func(msg *Message) error parseBody = func(msg *Message) error { contentType := msg.Headers.Get("Content-Type") if strings.HasPrefix(contentType, "multipart/alternative") { preferredType := "" preferredIndex := -1 // Find the best body for index, child := range msg.Children { contentType := child.Headers.Get("Content-Type") if strings.HasPrefix(contentType, "application/pgp-encrypted") { preferredType = "pgp" preferredIndex = index break } if strings.HasPrefix(contentType, "text/html") { preferredType = "html" preferredIndex = index } if strings.HasPrefix(contentType, "text/plain") { if preferredType != "html" { preferredType = "plain" preferredIndex = index } } } if preferredIndex == -1 && len(msg.Children) > 0 { preferredIndex = 0 } else if preferredIndex == -1 { return nil // crappy email } // Parse its media type to remove non-required stuff match := msg.Children[preferredIndex] mediaType, _, err := mime.ParseMediaType(match.Headers.Get("Content-Type")) if err != nil { return describeError(err) } // Push contents into the parser's scope bodyType = mediaType bodyText = string(match.Body) /* change of plans - discard them. // Transform rest of the types into attachments nodeID := uniuri.New() for _, child := range msg.Children { child.Headers["disposition"] = "attachment; filename=\"alternative." + nodeID + "." + mime. +"\"" }*/ } else if strings.HasPrefix(contentType, "multipart/") { // Tread every other multipart as multipart/mixed, as we parse multipart/encrypted later for _, child := range msg.Children { if err := parseBody(child); err != nil { return describeError(err) } } } else { // Parse the content type mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { return describeError(err) } // Not multipart, parse the disposition disposition, dparams, err := mime.ParseMediaType(msg.Headers.Get("Content-Disposition")) if err == nil && disposition == "attachment" { // We're dealing with an attachment id := uniuri.NewLen(uniuri.UUIDLen) // Encrypt the body encryptedBody, err := shared.EncryptAndArmor(msg.Body, toKeyring) if err != nil { return describeError(err) } // Hash the body rawHash := sha256.Sum256(msg.Body) hash := hex.EncodeToString(rawHash[:]) // Push the attachment into parser's scope parts = append(parts, &man.Part{ Hash: hash, ID: id, ContentType: mediaType, Filename: dparams["filename"], Size: len(msg.Body), }) for _, account := range accounts { fid := uniuri.NewLen(uniuri.UUIDLen) files = append(files, &models.File{ Resource: models.Resource{ ID: fid, DateCreated: time.Now(), DateModified: time.Now(), Name: id + ".pgp", Owner: account.ID, }, Encrypted: models.Encrypted{ Encoding: "application/pgp-encrypted", Data: string(encryptedBody), }, }) if _, ok := fileIDs[account.ID]; !ok { fileIDs[account.ID] = []string{} } fileIDs[account.ID] = append(fileIDs[account.ID], fid) } } else { // Header is either corrupted or we're dealing with inline if bodyType == "" && mediaType == "text/plain" || mediaType == "text/html" { bodyType = mediaType bodyText = string(msg.Body) } else if bodyType == "" { bodyType = "text/html" if strings.Index(mediaType, "image/") == 0 { bodyText = `<img src="data:` + mediaType + `;base64,` + base64.StdEncoding.EncodeToString(msg.Body) + `"><br>` } else { bodyText = "<pre>" + string(msg.Body) + "</pre>" } } else if mediaType == "text/plain" { if bodyType == "text/plain" { bodyText += "\n\n" + string(msg.Body) } else { bodyText += "\n\n<pre>" + string(msg.Body) + "</pre>" } } else if mediaType == "text/html" { if bodyType == "text/plain" { bodyType = "text/html" bodyText = "<pre>" + bodyText + "</pre>\n\n" + string(msg.Body) } else { bodyText += "\n\n" + string(msg.Body) } } else { if bodyType != "text/html" { bodyType = "text/html" bodyText = "<pre>" + bodyText + "</pre>" } // Put images as HTML tags if strings.Index(mediaType, "image/") == 0 { bodyText = "\n\n<img src=\"data:" + mediaType + ";base64," + base64.StdEncoding.EncodeToString(msg.Body) + "\"><br>" } else { bodyText = "\n\n<pre>" + string(msg.Body) + "</pre>" } } } } return nil } // Parse the email parseBody(email) // Trim the body text bodyText = strings.TrimSpace(bodyText) // Hash the body bodyHash := sha256.Sum256([]byte(bodyText)) // Append body to the parts parts = append(parts, &man.Part{ Hash: hex.EncodeToString(bodyHash[:]), ID: "body", ContentType: bodyType, Size: len(bodyText), }) // Debug info log.Debug("Finished parsing the email") // Push files into RethinkDB for _, file := range files { if err := gorethink.Db(config.RethinkDatabase).Table("files").Insert(file).Exec(session); err != nil { return describeError(err) } } // Generate the from, to and cc addresses from, err := email.Headers.AddressList("from") if err != nil { from = []*mail.Address{} } to, err := email.Headers.AddressList("to") if err != nil { to = []*mail.Address{} } cc, err := email.Headers.AddressList("cc") if err != nil { cc = []*mail.Address{} } // Generate the manifest emailID := uniuri.NewLen(uniuri.UUIDLen) subject = "Encrypted message (" + emailID + ")" s2 := email.Headers.Get("subject") if len(s2) > 1 && s2[0] == '=' && s2[1] == '?' { s2, _, err = quotedprintable.DecodeHeader(s2) if err != nil { return describeError(err) } } var fm *mail.Address if len(from) > 0 { fm = from[0] } else { fm = &mail.Address{ Name: "no from header", Address: "invalid", } } rawManifest := &man.Manifest{ Version: semver.Version{ Major: 1, }, From: fm, To: to, CC: cc, Subject: s2, Parts: parts, } // Encrypt the manifest and the body encryptedBody, err := shared.EncryptAndArmor([]byte(bodyText), toKeyring) if err != nil { return describeError(err) } strManifest, err := man.Write(rawManifest) if err != nil { return describeError(err) } encryptedManifest, err := shared.EncryptAndArmor(strManifest, toKeyring) if err != nil { return describeError(err) } body = string(encryptedBody) manifest = string(encryptedManifest) kind = "manifest" _ = subject } else if kind == "manifest" { // Variables used for attachment search manifestIndex := -1 bodyIndex := -1 // Find indexes of the manifest and the body for index, child := range email.Children { contentType := child.Headers.Get("Content-Type") if strings.Index(contentType, "application/x-pgp-manifest") == 0 { manifestIndex = index } else if strings.Index(contentType, "multipart/alternative") == 0 { bodyIndex = index } if manifestIndex != -1 && bodyIndex != -1 { break } } // Check that we found both parts if manifestIndex == -1 || bodyIndex == -1 { return describeError(fmt.Errorf("Invalid PGP/Manifest email")) } // Search for the body child index bodyChildIndex := -1 for index, child := range email.Children[bodyIndex].Children { contentType := child.Headers.Get("Content-Type") if strings.Index(contentType, "application/pgp-encrypted") == 0 { bodyChildIndex = index break } } // Check that we found it if bodyChildIndex == -1 { return describeError(fmt.Errorf("Invalid PGP/Manifest email body")) } // Find the manifest and the body manifest = string(email.Children[manifestIndex].Body) body = string(email.Children[bodyIndex].Children[bodyChildIndex].Body) subject = "Encrypted email" // Gather attachments and insert them into db for index, child := range email.Children { if index == bodyIndex || index == manifestIndex { continue } _, cdparams, err := mime.ParseMediaType(child.Headers.Get("Content-Disposition")) if err != nil { return describeError(err) } for _, account := range accounts { fid := uniuri.NewLen(uniuri.UUIDLen) if err := gorethink.Db(config.RethinkDatabase).Table("files").Insert(&models.File{ Resource: models.Resource{ ID: fid, DateCreated: time.Now(), DateModified: time.Now(), Name: cdparams["filename"], Owner: account.ID, }, Encrypted: models.Encrypted{ Encoding: "application/pgp-encrypted", Data: string(child.Body), }, }).Exec(session); err != nil { return describeError(err) } if _, ok := fileIDs[account.ID]; !ok { fileIDs[account.ID] = []string{} } fileIDs[account.ID] = append(fileIDs[account.ID], fid) } } } else if kind == "pgpmime" { for _, child := range email.Children { if strings.Index(child.Headers.Get("Content-Type"), "application/pgp-encrypted") != -1 { body = string(child.Body) subject = child.Headers.Get("Subject") break } } } if len(subject) > 1 && subject[0] == '=' && subject[1] == '?' { subject, _, err = quotedprintable.DecodeHeader(subject) if err != nil { return describeError(err) } } // Save the email for each recipient for _, account := range accounts { // Get 3 user's labels cursor, err := gorethink.Db(config.RethinkDatabase).Table("labels").GetAllByIndex("nameOwnerBuiltin", []interface{}{ "Inbox", account.ID, true, }, []interface{}{ "Spam", account.ID, true, }, []interface{}{ "Trash", account.ID, true, }, []interface{}{}).Run(session) if err != nil { return describeError(err) } defer cursor.Close() var labels []*models.Label if err := cursor.All(&labels); err != nil { return describeError(err) } var ( inbox = labels[0] spam = labels[1] trash = labels[2] ) // Get the subject's hash subjectHash := email.Headers.Get("Subject-Hash") if subjectHash == "" { subject := email.Headers.Get("Subject") if subject == "" { subject = "<no subject>" } if len(subject) > 1 && subject[0] == '=' && subject[1] == '?' { subject, _, err = quotedprintable.DecodeHeader(subject) if err != nil { return describeError(err) } } subject = shared.StripPrefixes(strings.TrimSpace(subject)) hash := sha256.Sum256([]byte(subject)) subjectHash = hex.EncodeToString(hash[:]) } // Generate the email ID eid := uniuri.NewLen(uniuri.UUIDLen) // Prepare from, to and cc from := email.Headers.Get("from") if f1, err := email.Headers.AddressList("from"); err == nil && len(f1) > 0 { from = strings.TrimSpace(f1[0].Name + " <" + f1[0].Address + ">") } to := strings.Split(email.Headers.Get("to"), ", ") cc := strings.Split(email.Headers.Get("cc"), ", ") for i, v := range to { to[i] = strings.TrimSpace(v) } for i, v := range cc { cc[i] = strings.TrimSpace(v) } if len(cc) == 1 && cc[0] == "" { cc = nil } // Transform headers into map[string]string fh := map[string]string{} for key, values := range email.Headers { fh[key] = strings.Join(values, ", ") } // Find the thread var thread *models.Thread // First check if either in-reply-to or references headers are set irt := "" if x := email.Headers.Get("In-Reply-To"); x != "" { irt = x } else if x := email.Headers.Get("References"); x != "" { irt = x } if irt != "" { // Per http://www.jwz.org/doc/threading.html: // You can safely assume that the first string between <> in In-Reply-To // is the message ID. x1i := strings.Index(irt, "<") if x1i != -1 { x2i := strings.Index(irt[x1i+1:], ">") if x2i != -1 { irt = irt[x1i+1 : x1i+x2i+1] } } // Look up the parent cursor, err := gorethink.Db(config.RethinkDatabase).Table("emails").GetAllByIndex("messageIDOwner", []interface{}{ irt, account.ID, }).Run(session) if err != nil { return describeError(err) } defer cursor.Close() var emails []*models.Email if err := cursor.All(&emails); err != nil { return describeError(err) } // Found one = that one is correct if len(emails) == 1 { cursor, err := gorethink.Db(config.RethinkDatabase).Table("threads").Get(emails[0].Thread).Run(session) if err != nil { return describeError(err) } defer cursor.Close() if err := cursor.One(&thread); err != nil { return describeError(err) } } } if thread == nil { // Match by subject cursor, err := gorethink.Db(config.RethinkDatabase).Table("threads").GetAllByIndex("subjectOwner", []interface{}{ subjectHash, account.ID, }).Filter(func(row gorethink.Term) gorethink.Term { return row.Field("members").Map(func(member gorethink.Term) gorethink.Term { return member.Match(gorethink.Expr(from)).CoerceTo("string").Ne("null") }).Contains(gorethink.Expr(true)).And( gorethink.Not( row.Field("labels").Contains(spam.ID).Or( row.Field("labels").Contains(trash.ID), ), ), ) }).Run(session) if err != nil { return describeError(err) } defer cursor.Close() var threads []*models.Thread if err := cursor.All(&threads); err != nil { return describeError(err) } if len(threads) > 0 { thread = threads[0] } } if thread == nil { secure := "all" if initialKind == "raw" { secure = "none" } labels := []string{inbox.ID} if isSpam { labels = append(labels, spam.ID) } thread = &models.Thread{ Resource: models.Resource{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Name: "Encrypted thread", Owner: account.ID, }, Emails: []string{eid}, Labels: labels, Members: append(append(to, cc...), from), IsRead: false, SubjectHash: subjectHash, Secure: secure, } if err := gorethink.Db(config.RethinkDatabase).Table("threads").Insert(thread).Exec(session); err != nil { return describeError(err) } } else { var desiredID string if isSpam { desiredID = spam.ID } else { desiredID = inbox.ID } foundLabel := false for _, label := range thread.Labels { if label == desiredID { foundLabel = true break } } if !foundLabel { thread.Labels = append(thread.Labels, desiredID) } thread.Emails = append(thread.Emails, eid) update := map[string]interface{}{ "date_modified": gorethink.Now(), "is_read": false, "labels": thread.Labels, "emails": thread.Emails, } // update thread.secure depending on email's kind if (initialKind == "raw" && thread.Secure == "all") || (initialKind == "manifest" && thread.Secure == "none") || (initialKind == "pgpmime" && thread.Secure == "none") { update["secure"] = "some" } if err := gorethink.Db(config.RethinkDatabase).Table("threads").Get(thread.ID).Update(update).Exec(session); err != nil { return describeError(err) } } // Generate list of all owned emails ownEmails := map[string]struct{}{} for domain, _ := range domains { ownEmails[account.Name+"@"+domain] = struct{}{} } // Remove ownEmails from to and cc to2 := []string{} for _, value := range to { addr, err := mail.ParseAddress(value) if err != nil { // Mail is probably empty continue } if _, ok := ownEmails[addr.Address]; !ok { to2 = append(to2, value) } } to = to2 if cc != nil { cc2 := []string{} for _, value := range cc { addr, err := mail.ParseAddress(value) if err != nil { continue } if _, ok := ownEmails[addr.Address]; !ok { cc2 = append(cc2, value) } } cc = cc2 } // Prepare a new email es := &models.Email{ Resource: models.Resource{ ID: eid, DateCreated: time.Now(), DateModified: time.Now(), Name: subject, Owner: account.ID, }, Kind: kind, From: from, To: to, CC: cc, Body: body, Thread: thread.ID, MessageID: strings.Trim(email.Headers.Get("Message-ID"), "<>"), // todo: create a message id parser Status: "received", } if fileIDs != nil { es.Files = fileIDs[account.ID] } if manifest != "" { es.Manifest = manifest } // Insert the email if err := gorethink.Db(config.RethinkDatabase).Table("emails").Insert(es).Exec(session); err != nil { return describeError(err) } // Prepare a notification message notification, err := json.Marshal(map[string]interface{}{ "id": eid, "owner": account.ID, }) if err != nil { return describeError(err) } // Notify the cluster if err := producer.Publish("email_receipt", notification); err != nil { return describeError(err) } // Trigger the hooks hook, err := json.Marshal(&events.Incoming{ Email: eid, Account: account.ID, }) if err != nil { return describeError(err) } // Push it to nsq if err = producer.Publish("hook_incoming", hook); err != nil { return describeError(err) } log.WithFields(logrus.Fields{ "id": eid, }).Info("Finished processing an email") } return nil } }
func create(w http.ResponseWriter, req *http.Request) { // Decode the body var msg createInput err := json.NewDecoder(req.Body).Decode(&msg) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Fetch the invite from database cursor, err := r.Db(*rethinkName).Table("invites").Get(msg.Token).Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var invite *Invite err = cursor.One(&invite) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Normalize the username styledName := msg.Username msg.Username = utils.RemoveDots(utils.NormalizeUsername(msg.Username)) var account *models.Account // If there's no account id, then simply check args if invite.AccountID == "" { if !govalidator.IsEmail(msg.Email) { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email address", }) return } // Check if address is taken cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session) if err == nil || cursor != nil { writeJSON(w, freeMsg{ Success: false, UsernameTaken: true, }) return } // Check if email is used cursor, err = r.Db(*rethinkAPIName).Table("accounts"). GetAllByIndex("alt_email", msg.Email). Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))). Count().Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var emailCount int err = cursor.One(&emailCount) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if emailCount > 0 { writeJSON(w, freeMsg{ Success: false, EmailUsed: true, }) return } // Prepare a new account account = &models.Account{ Resource: models.MakeResource("", msg.Username), AltEmail: msg.Email, StyledName: styledName, Status: "registered", Type: "supporter", } // Update the invite invite.AccountID = account.ID err = r.Db(*rethinkName).Table("invites").Get(invite.ID).Update(map[string]interface{}{ "account_id": invite.AccountID, }).Exec(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Insert the account into db err = r.Db(*rethinkAPIName).Table("accounts").Insert(account).Exec(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } } else { cursor, err = r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } defer cursor.Close() if err := cursor.One(&account); err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if account.Name != "" && account.Name != msg.Username { writeJSON(w, errorMsg{ Success: false, Message: "Invalid username", }) return } else if account.Name == "" { // Check if address is taken cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session) if err == nil || cursor != nil { writeJSON(w, errorMsg{ Success: false, Message: "Username is taken", }) return } } if account.AltEmail != "" && account.AltEmail != msg.Email { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email", }) return } if account.AltEmail == "" { if !govalidator.IsEmail(msg.Email) { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email address", }) return } // Check if email is used cursor, err = r.Db(*rethinkAPIName).Table("accounts"). GetAllByIndex("alt_email", msg.Email). Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))). Count().Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } defer cursor.Close() var emailCount int err = cursor.One(&emailCount) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if emailCount > 0 { writeJSON(w, errorMsg{ Success: false, Message: "Email is already used", }) return } } if err := r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Update(map[string]interface{}{ "type": "supporter", }).Exec(session); err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } } // Generate a new invite token for the user token := &models.Token{ Resource: models.MakeResource(account.ID, "Invitation token from invite-api"), Type: "verify", Expiring: models.Expiring{ ExpiryDate: time.Now().UTC().Add(time.Hour * 12), }, } // Insert it into db err = r.Db(*rethinkAPIName).Table("tokens").Insert(token).Exec(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Here be dragons. Thou art forewarned. /*go func() { // Watch the changes cursor, err := r.Db(*rethinkAPIName).Table("accounts").Get(account.ID).Changes().Run(session) if err != nil { log.Print("Error while watching changes of user " + account.Name + " - " + err.Error()) return } defer cursor.Close() // Generate a timeout "flag" ts := uniuri.New() // Read them c := make(chan struct{}) go func() { var change struct { NewValue map[string]interface{} `gorethink:"new_val"` } for cursor.Next(&change) { if status, ok := change.NewValue["status"]; ok { if x, ok := status.(string); ok && x == "setup" { c <- struct{}{} return } } if iat, ok := change.NewValue["_invite_api_timeout"]; ok { if x, ok := iat.(string); ok && x == ts { log.Print("Account setup watcher timeout for name " + account.Name) return } } } }() // Block the goroutine select { case <-c: if err := r.Db(*rethinkName).Table("invites").Get(invite.ID).Delete().Exec(session); err != nil { log.Print("Unable to delete an invite. " + invite.ID + " - " + account.ID) return } return case <-time.After(12 * time.Hour): if err := r.Db(*rethinkAPIName).Table("accounts").Get(account.ID).Update(map[string]interface{}{ "_invite_api_timeout": ts, }).Exec(session); err != nil { log.Print("Failed to make a goroutine timeout. " + account.ID) } return } }()*/ // jk f**k that if err := r.Db(*rethinkName).Table("invites").Get(invite.ID).Delete().Exec(session); err != nil { log.Print("Unable to delete an invite. " + invite.ID + " - " + account.ID) return } // Return the token writeJSON(w, createMsg{ Success: true, Code: token.ID, }) }
func StartQueue(config *shared.Flags) { // Initialize a new logger log := logrus.New() if config.LogFormatterType == "text" { log.Formatter = &logrus.TextFormatter{ ForceColors: config.ForceColors, } } else if config.LogFormatterType == "json" { log.Formatter = &logrus.JSONFormatter{} } log.Level = logrus.DebugLevel // Create a new header encoder he := quotedprintable.Q.NewHeaderEncoder("utf-8") // Initialize the database connection session, err := gorethink.Connect(gorethink.ConnectOpts{ Address: config.RethinkAddress, AuthKey: config.RethinkKey, MaxIdle: 10, Timeout: time.Second * 10, }) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to connect to RethinkDB") } // Create a new producer consumer, err := nsq.NewConsumer("send_email", "receive", nsq.NewConfig()) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to create a consumer") } // Connect to NSQ producer, err := nsq.NewProducer(config.NSQDAddress, nsq.NewConfig()) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to connect to NSQd") } // Load a DKIM signer var dkimSigner map[string]*dkim.DKIM if config.DKIMKey != "" { dkimSigner = map[string]*dkim.DKIM{} key, err := ioutil.ReadFile(config.DKIMKey) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to read DKIM private key") } for domain, _ := range domains { dkimConf, err := dkim.NewConf(domain, config.DKIMSelector) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to create a new DKIM conf object") } dk, err := dkim.New(dkimConf, key) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to create a new DKIM signer") } dkimSigner[domain] = dk } } consumer.AddConcurrentHandlers(nsq.HandlerFunc(func(msg *nsq.Message) error { var id string if err := json.Unmarshal(msg.Body, &id); err != nil { return err } // Get the email from the database cursor, err := gorethink.Db(config.RethinkDatabase).Table("emails").Get(id).Run(session) if err != nil { return err } defer cursor.Close() var email *models.Email if err := cursor.One(&email); err != nil { return err } // Get the thread cursor, err = gorethink.Db(config.RethinkDatabase).Table("threads").Get(email.Thread).Run(session) if err != nil { return err } defer cursor.Close() var thread *models.Thread if err := cursor.One(&thread); err != nil { return err } // Get the proper In-Reply-To hasInReplyTo := false inReplyTo := "" // Fetch received emails in the thread cursor, err = gorethink.Db(config.RethinkDatabase).Table("emails").GetAllByIndex("threadStatus", []interface{}{ thread.ID, "received", }).Pluck("date_created", "message_id", "from").OrderBy(gorethink.Desc(gorethink.Row.Field("date_created"))). Filter(func(row gorethink.Term) gorethink.Term { return gorethink.Expr(email.To).Contains(row.Field("from")) }).Limit(1).Run(session) if err != nil { return err } defer cursor.Close() var emid []*models.Email if err := cursor.All(&emid); err != nil { return err } if len(emid) == 1 { hasInReplyTo = true inReplyTo = emid[0].MessageID } // Fetch the files var files []*models.File if email.Files != nil && len(email.Files) > 0 { filesList := []interface{}{} for _, v := range email.Files { filesList = append(filesList, v) } cursor, err = gorethink.Db(config.RethinkDatabase).Table("files").GetAll(filesList...).Run(session) if err != nil { return err } defer cursor.Close() if err := cursor.All(&files); err != nil { return err } } else { files = []*models.File{} } // Fetch the owner cursor, err = gorethink.Db(config.RethinkDatabase).Table("accounts").Get(email.Owner).Run(session) if err != nil { return err } defer cursor.Close() var account *models.Account if err := cursor.One(&account); err != nil { return err } // Declare a contents variable contents := "" ctxFrom := email.From // Check if charset is set if !strings.Contains(email.ContentType, "; charset=") { email.ContentType += "; charset=utf-8" } if email.Kind == "raw" { // Encode the email if files == nil || len(files) == 0 { buffer := &bytes.Buffer{} context := &rawSingleContext{ From: ctxFrom, CombinedTo: strings.Join(email.To, ", "), MessageID: email.MessageID, HasInReplyTo: hasInReplyTo, InReplyTo: inReplyTo, Subject: he.Encode(email.Name), ContentType: email.ContentType, Body: quotedprintable.EncodeToString([]byte(email.Body)), Date: email.DateCreated.Format(time.RubyDate), } if email.CC != nil && len(email.CC) > 0 { context.HasCC = true context.CombinedCC = strings.Join(email.CC, ", ") } if email.ReplyTo != "" { context.HasReplyTo = true context.ReplyTo = email.ReplyTo } if err := rawSingleTemplate.Execute(buffer, context); err != nil { return err } contents = buffer.String() } else { buffer := &bytes.Buffer{} emailFiles := []*emailFile{} for _, file := range files { emailFiles = append(emailFiles, &emailFile{ Encoding: file.Encoding, Name: file.Name, Body: base64.StdEncoding.EncodeToString([]byte(file.Data)), }) } context := &rawMultiContext{ From: ctxFrom, CombinedTo: strings.Join(email.To, ", "), MessageID: email.MessageID, HasInReplyTo: hasInReplyTo, InReplyTo: inReplyTo, Boundary1: uniuri.NewLen(20), Subject: he.Encode(email.Name), ContentType: email.ContentType, Body: quotedprintable.EncodeToString([]byte(email.Body)), Files: emailFiles, Date: email.DateCreated.Format(time.RubyDate), } if email.CC != nil && len(email.CC) > 0 { context.HasCC = true context.CombinedCC = strings.Join(email.CC, ", ") } if email.ReplyTo != "" { context.HasReplyTo = true context.ReplyTo = email.ReplyTo } if err := rawMultiTemplate.Execute(buffer, context); err != nil { return err } contents = buffer.String() } // Fetch owner's account cursor, err = gorethink.Db(config.RethinkDatabase).Table("accounts").Get(email.Owner).Run(session) if err != nil { return err } defer cursor.Close() var account *models.Account if err := cursor.One(&account); err != nil { return err } // Get owner's key var key *models.Key if account.PublicKey != "" { cursor, err = gorethink.Db(config.RethinkDatabase).Table("keys").Get(account.PublicKey).Run(session) if err != nil { return err } defer cursor.Close() if err := cursor.One(&key); err != nil { return err } } else { cursor, err = gorethink.Db(config.RethinkDatabase).Table("keys").GetAllByIndex("owner", account.ID).Run(session) if err != nil { return err } defer cursor.Close() var keys []*models.Key if err := cursor.All(&keys); err != nil { return err } key = keys[0] } // Parse the key keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key.Key)) if err != nil { return err } // From, to and cc parsing fromAddr, err := mail.ParseAddress(email.From) if err != nil { fromAddr = &mail.Address{ Address: email.From, } } toAddr, err := mail.ParseAddressList(strings.Join(email.To, ", ")) if err != nil { toAddr = []*mail.Address{} for _, addr := range email.To { toAddr = append(toAddr, &mail.Address{ Address: addr, }) } } // Prepare a new manifest manifest := &man.Manifest{ Version: semver.Version{ Major: 1, }, From: fromAddr, To: toAddr, Subject: he.Encode(email.Name), Parts: []*man.Part{}, } if email.CC != nil && len(email.CC) > 0 { ccAddr, nil := mail.ParseAddressList(strings.Join(email.CC, ", ")) if err != nil { ccAddr = []*mail.Address{} for _, addr := range email.CC { ccAddr = append(ccAddr, &mail.Address{ Address: addr, }) } } manifest.CC = ccAddr } // Encrypt and hash the body encryptedBody, err := shared.EncryptAndArmor([]byte(email.Body), keyring) if err != nil { return err } hash := sha256.Sum256([]byte(email.Body)) // Append body to the parts manifest.Parts = append(manifest.Parts, &man.Part{ ID: "body", Hash: hex.EncodeToString(hash[:]), ContentType: email.ContentType, Size: len(email.Body), }) // Encrypt the attachments for _, file := range files { // Encrypt the attachment cipher, err := shared.EncryptAndArmor([]byte(file.Data), keyring) if err != nil { return err } // Hash it hash := sha256.Sum256([]byte(file.Data)) // Generate a random ID id := uniuri.NewLen(20) // Push the attachment into the manifest manifest.Parts = append(manifest.Parts, &man.Part{ ID: id, Hash: hex.EncodeToString(hash[:]), Filename: file.Name, ContentType: file.Encoding, Size: len(file.Data), }) // Replace the file in database err = gorethink.Db(config.RethinkDatabase).Table("files").Get(file.ID).Replace(&models.File{ Resource: models.Resource{ ID: file.ID, DateCreated: file.DateCreated, DateModified: time.Now(), Name: id + ".pgp", Owner: account.ID, }, Encrypted: models.Encrypted{ Encoding: "application/pgp-encrypted", Data: string(cipher), }, }).Exec(session) if err != nil { return err } } // Encrypt the manifest strManifest, err := man.Write(manifest) if err != nil { return err } encryptedManifest, err := shared.EncryptAndArmor(strManifest, keyring) if err != nil { return err } err = gorethink.Db(config.RethinkDatabase).Table("emails").Get(email.ID).Replace(&models.Email{ Resource: models.Resource{ ID: email.ID, DateCreated: email.DateCreated, DateModified: time.Now(), Name: "Encrypted message (" + email.ID + ")", Owner: account.ID, }, Kind: "manifest", From: email.From, To: email.To, CC: email.CC, BCC: email.BCC, Files: email.Files, Manifest: string(encryptedManifest), Body: string(encryptedBody), Thread: email.Thread, MessageID: email.MessageID, }).Exec(session) if err != nil { return err } } else if email.Kind == "pgpmime" { buffer := &bytes.Buffer{} context := &pgpContext{ From: ctxFrom, CombinedTo: strings.Join(email.To, ", "), MessageID: email.MessageID, HasInReplyTo: hasInReplyTo, InReplyTo: inReplyTo, Subject: email.Name, ContentType: email.ContentType, Body: email.Body, Date: email.DateCreated.Format(time.RubyDate), } if email.CC != nil && len(email.CC) > 0 { context.HasCC = true context.CombinedCC = strings.Join(email.CC, ", ") } if email.ReplyTo != "" { context.HasReplyTo = true context.ReplyTo = email.ReplyTo } if err := pgpTemplate.Execute(buffer, context); err != nil { return err } contents = buffer.String() } else if email.Kind == "manifest" { if files == nil || len(files) == 0 { buffer := &bytes.Buffer{} context := &manifestSingleContext{ From: ctxFrom, CombinedTo: strings.Join(email.To, ", "), MessageID: email.MessageID, HasInReplyTo: hasInReplyTo, InReplyTo: inReplyTo, Subject: he.Encode(email.Name), Boundary1: uniuri.NewLen(20), Boundary2: uniuri.NewLen(20), ID: email.ID, Body: email.Body, Manifest: email.Manifest, SubjectHash: thread.SubjectHash, Date: email.DateCreated.Format(time.RubyDate), } if email.CC != nil && len(email.CC) > 0 { context.HasCC = true context.CombinedCC = strings.Join(email.CC, ", ") } if email.ReplyTo != "" { context.HasReplyTo = true context.ReplyTo = email.ReplyTo } if err := manifestSingleTemplate.Execute(buffer, context); err != nil { return err } contents = buffer.String() } else { buffer := &bytes.Buffer{} emailFiles := []*emailFile{} for _, file := range files { emailFiles = append(emailFiles, &emailFile{ Encoding: file.Encoding, Name: file.Name, Body: file.Data, }) } context := &manifestMultiContext{ From: ctxFrom, CombinedTo: strings.Join(email.To, ", "), MessageID: email.MessageID, HasInReplyTo: hasInReplyTo, InReplyTo: inReplyTo, Subject: he.Encode(email.Name), Boundary1: uniuri.NewLen(20), Boundary2: uniuri.NewLen(20), ID: email.ID, Body: email.Body, Manifest: email.Manifest, SubjectHash: thread.SubjectHash, Files: emailFiles, Date: email.DateCreated.Format(time.RubyDate), } if email.CC != nil && len(email.CC) > 0 { context.HasCC = true context.CombinedCC = strings.Join(email.CC, ", ") } if email.ReplyTo != "" { context.HasReplyTo = true context.ReplyTo = email.ReplyTo } if err := manifestMultiTemplate.Execute(buffer, context); err != nil { return err } contents = buffer.String() } } recipients := email.To if email.CC != nil { recipients = append(recipients, email.CC...) } nsqmsg, _ := json.Marshal(map[string]interface{}{ "id": email.ID, "owner": email.Owner, }) // Sign the email if dkimSigner != nil { parts := strings.Split(email.From, "@") if len(parts) == 2 { if _, ok := dkimSigner[parts[1]]; ok { // Replace newlines with \r\n contents = strings.Replace(contents, "\n", "\r\n", -1) // Sign it data, err := dkimSigner[parts[1]].Sign([]byte(contents)) if err != nil { log.Print(err) return err } // Replace contents with signed contents = strings.Replace(string(data), "\r\n", "\n", -1) } } } if err := smtp.SendMail(config.SMTPAddress, nil, email.From, recipients, []byte(contents)); err != nil { err := producer.Publish("email_bounced", nsqmsg) if err != nil { log.WithFields(logrus.Fields{ "error": err, }).Error("Unable to publish a bounce msg") } } else { err := producer.Publish("email_delivery", nsqmsg) if err != nil { log.WithFields(logrus.Fields{ "error": err, }).Error("Unable to publish a bounce msg") } } err = gorethink.Db(config.RethinkDatabase).Table("emails").Get(email.ID).Update(map[string]interface{}{ "status": "sent", }).Exec(session) if err != nil { log.WithFields(logrus.Fields{ "error": err, }).Error("Unable to mark an email as sent") } msg.Finish() return nil }), 10) if err := consumer.ConnectToNSQLookupd(config.LookupdAddress); err != nil { log.WithFields(logrus.Fields{ "error": err, }).Fatal("Unable to connect to nsqlookupd") } log.Info("Connected to NSQ and awaiting data") }
func (e *EmailsTable) List( owner string, sort []string, offset int, limit int, thread string, ) ([]*models.Email, error) { filter := map[string]interface{}{} if owner != "" { filter["owner"] = owner } if thread != "" { filter["thread"] = thread } term := e.GetTable().Filter(filter).Filter(gorethink.Not(gorethink.Row.Field("status").Eq(gorethink.Expr("queued")))) // If sort array has contents, parse them and add to the term if sort != nil && len(sort) > 0 { var conds []interface{} for _, cond := range sort { if cond[0] == '-' { conds = append(conds, gorethink.Desc(cond[1:])) } else if cond[0] == '+' || cond[0] == ' ' { conds = append(conds, gorethink.Asc(cond[1:])) } else { conds = append(conds, gorethink.Asc(cond)) } } term = term.OrderBy(conds...) } // Slice the result in 3 cases if offset != 0 && limit == 0 { term = term.Skip(offset) } if offset == 0 && limit != 0 { term = term.Limit(limit) } if offset != 0 && limit != 0 { term = term.Slice(offset, offset+limit) } // Run the query cursor, err := term.Run(e.GetSession()) if err != nil { return nil, err } defer cursor.Close() // Fetch the cursor var resp []*models.Email err = cursor.All(&resp) if err != nil { return nil, err } return resp, nil }
func enrichFilter(input map[string]interface{}) { for k, v1 := range input { if v2, ok := v1.(string); ok { if strings.HasPrefix(v2, "not~") { if len(v2) > 4 { input[k] = gorethink.Not(gorethink.Expr(v2[5:])) } } else if strings.HasPrefix(v2, "lt~") && len(v2) > 3 { if v2[3:] == "now" { input[k] = gorethink.Lt(gorethink.Now()) } else { t, err := time.Parse(time.RFC3339, v2[3:]) if err == nil { input[k] = gorethink.Lt(gorethink.Expr(t)) } else { i, err := strconv.Atoi(v2[3:]) if err == nil { input[k] = gorethink.Lt(gorethink.Expr(i)) } } } } else if strings.HasPrefix(v2, "le~") && len(v2) > 3 { if v2[3:] == "now" { input[k] = gorethink.Le(gorethink.Now()) } else { t, err := time.Parse(time.RFC3339, v2[3:]) if err == nil { input[k] = gorethink.Le(gorethink.Expr(t)) } else { i, err := strconv.Atoi(v2[3:]) if err == nil { input[k] = gorethink.Le(gorethink.Expr(i)) } } } } else if strings.HasPrefix(v2, "gt~") && len(v2) > 3 { if v2[3:] == "now" { input[k] = gorethink.Gt(gorethink.Now()) } else { t, err := time.Parse(time.RFC3339, v2[3:]) if err == nil { input[k] = gorethink.Gt(gorethink.Expr(t)) } else { i, err := strconv.Atoi(v2[3:]) if err == nil { input[k] = gorethink.Gt(gorethink.Expr(i)) } } } } else if strings.HasPrefix(v2, "ge~") && len(v2) > 3 { if v2[3:] == "now" { input[k] = gorethink.Ge(gorethink.Now()) } else { t, err := time.Parse(time.RFC3339, v2[3:]) if err == nil { input[k] = gorethink.Ge(gorethink.Expr(t)) } else { i, err := strconv.Atoi(v2[3:]) if err == nil { input[k] = gorethink.Ge(gorethink.Expr(i)) } } } } } } }
func free(w http.ResponseWriter, req *http.Request) { // Decode the POST body var msg freeInput err := json.NewDecoder(req.Body).Decode(&msg) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Fetch the invite from database cursor, err := r.Db(*rethinkName).Table("invites").Get(msg.Token).Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var invite *Invite err = cursor.One(&invite) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Normalize the username - make it lowercase and remove dots msg.Username = utils.RemoveDots(utils.NormalizeUsername(msg.Username)) if invite.AccountID != "" { // Fetch account from database cursor, err := r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var account *models.Account if err := cursor.One(&account); err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if account.Name != "" && account.Name != msg.Username { writeJSON(w, errorMsg{ Success: false, Message: "Invalid username", }) return } if account.AltEmail != "" && account.AltEmail != msg.Email { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email", }) return } if account.AltEmail == "" && !govalidator.IsEmail(msg.Email) { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email address", }) return } if account.Name == "" { // Check if address is taken cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session) if err == nil || cursor != nil { writeJSON(w, freeMsg{ Success: false, UsernameTaken: true, }) return } } if account.AltEmail == "" { // Check if email is used cursor, err = r.Db(*rethinkAPIName).Table("accounts"). GetAllByIndex("alt_email", msg.Email).Count().Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var emailCount int err = cursor.One(&emailCount) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if emailCount > 0 { writeJSON(w, freeMsg{ Success: false, EmailUsed: true, }) return } } // Return the result writeJSON(w, freeMsg{ Success: true, }) } else { if !govalidator.IsEmail(msg.Email) { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email address", }) return } // Check if address is taken cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session) if err == nil || cursor != nil { writeJSON(w, freeMsg{ Success: false, UsernameTaken: true, }) return } // Check if email is used cursor, err = r.Db(*rethinkAPIName).Table("accounts"). GetAllByIndex("alt_email", msg.Email). Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))). Count().Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var emailCount int err = cursor.One(&emailCount) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if emailCount > 0 { writeJSON(w, freeMsg{ Success: false, EmailUsed: true, }) return } // Return the result writeJSON(w, freeMsg{ Success: true, }) } }