/**
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo.domaintest.email;
import static com.google.common.base.CharMatcher.WHITESPACE;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Iterables.getFirst;
import static foo.domaintest.util.Key.Type.STASH;
import static foo.domaintest.util.Key.Type.TOKEN;
import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.appengine.api.memcache.Expiration;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import foo.domaintest.action.Action.PostAction;
import foo.domaintest.action.HttpErrorException.BadRequestException;
import foo.domaintest.action.annotation.ForPath;
import foo.domaintest.action.annotation.RequestData;
import foo.domaintest.config.SystemProperty;
import foo.domaintest.email.EmailApiModule.EmailHeader;
import foo.domaintest.email.EmailApiModule.RawEmailHeaders;
import foo.domaintest.util.Key;
import foo.domaintest.util.Memcache;
import foo.domaintest.util.TempUrlFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.inject.Inject;
/** Action that receives email from sendgrid and sends autoreplies. */
@ForPath("/autoreply")
public class AutoreplyAction implements PostAction {
private static final Logger logger = Logger.getLogger(AutoreplyAction.class.getName());
/**
* Stash emails for 15 minutes.
* <p>
* This is longer than /stash caches things, due to the vagaries of email delivery.
*/
private static final Expiration STASH_EXPIRATION =
Expiration.byDeltaSeconds((int) MINUTES.toSeconds(15));
@Inject @SystemProperty("autoreplykey") String requiredApiKey;
@Inject @RequestData("queryString") String presentedApiKey; // The whole query string is the key.
@Inject @RawEmailHeaders String rawHeaders;
@Inject @EmailHeader("from") String from;
@Inject @EmailHeader("to") String to;
@Inject @EmailHeader("reply-to") String replyTo;
@Inject @EmailHeader("subject") String subject;
@Inject @EmailHeader("message-id") String messageId;
@Inject @EmailHeader("in-reply-to") String inReplyTo;
@Inject @EmailHeader("references") String references;
@Inject Emailer emailer;
@Inject Memcache memcache;
@Inject TempUrlFactory tempUrlFactory;
@Override
public void run() {
if (!presentedApiKey.equals(requiredApiKey)) {
throw new BadRequestException("Invalid API key");
}
Iterable<String> subjectWords = Splitter
.on(CharMatcher.WHITESPACE)
.omitEmptyStrings()
.trimResults()
.split(nullToEmpty(subject));
// For spam-ignoring purposes, we only respond if the subject starts with the word "TEST".
if (!"TEST".equalsIgnoreCase(getFirst(subjectWords, null))) {
logger.info("Subject did not begin with the word 'TEST'");
return;
}
String newBody = null;
// Try to consider the second word (if any) as a stash token. If it is one, stash the headers.
String token = Iterables.get(subjectWords, 1, null);
if (token != null && memcache.load(new Key(TOKEN, token)) != null) {
Map<String, Object> params = new HashMap<>();
params.put("payload", rawHeaders);
memcache.save(new Key(STASH, token), params, STASH_EXPIRATION);
newBody = tempUrlFactory.getTempUrl(token);
}
String newSender = "tester@" + Splitter.on('@').splitToList(to).get(1);
String newRecipient = Optional.fromNullable(replyTo).or(from);
// We construct the references and in-reply-to headers according to RFC 5322 3.6.4. The new
// references is the old message id. The new in-reply-to is the old references plus the old
// message id. If there's no old references but there is an old in-reply-to with exactly one id,
// we use that in place of the references.
String newInReplyTo = messageId;
if (isNullOrEmpty(references)
&& !isNullOrEmpty(inReplyTo)
&& !WHITESPACE.matchesAnyOf(inReplyTo.trim())) {
references = inReplyTo;
}
String newReferences = emptyToNull(Joiner.on(' ').skipNulls().join(references, messageId));
logger.info(String.format("Sending from %s to %s", newSender, newRecipient));
logger.info(String.format("in-reply-to: %s, references: %s", newInReplyTo, newReferences));
emailer.send(
newSender,
newRecipient,
newBody,
newInReplyTo,
newReferences);
}
}