5 | @{
6 | if(lang.code == "en") {
7 | How long will my temporary email address be active for?
8 |
9 | Your temporary address will be initially active for a week. A day before it expires you'll receive an
10 | email allowing you to extend it for another week, but you can only do it once.
11 |
12 | Can I deactivate my temporary email address?
13 |
14 | Yes. In the activation email you received when you created your temporary email address there is a link
15 | to deactivate it whenever you want.
16 |
17 | I've got another question!
18 |
19 | Feel free to contact us , we'll answer as soon as possible.
20 |
21 |
22 | } else if(lang.code == "ca") {
23 | Quant de temps estarà activa la meva adreça temporal?
24 |
25 | La teva adreça temporal estarà activa durant una setmana. Un dia abans de que es desactivi rebràs un
26 | correu que et permetrà extendre-la una setmana més, però només es pot extendre un cop.
27 |
28 | Puc desactivar la meva adreça temporal?
29 |
30 | Si. En el correu d'activació que vas rebre quan vas crear la teva adreça temporal hi ha un enllaç des
31 | d'on pots desactivar-la quan tu vulguis.
32 |
33 | Tinc una altra pregunta!
34 |
35 | Posa't en contacte amb nosaltres, respondrem el més aviat possible.
36 |
37 |
38 | } else if(lang.code == "es") {
39 | Quanto tiempo estará activa mi dirección temporal?
40 |
41 | Tu dirección de correo temporal estará activa durante una semana. Un dia antes de que se desactive
42 | recibirás un correo que te pemitirá extenderla una semana más, pero solamente se puede extender una vez.
43 |
44 | Puedo desactivar mi dirección temporal?
45 |
46 | Sí. En el correo de activación que recibiste cuando creaste tu dirección temporal hay un enlace des del
47 | que puedes desactivarla cuando quieras.
48 |
49 | Tengo otra pregunta!
50 |
51 | Ponte en contacto con nosotros, responderemos lo más pronto posible.
52 |
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/forward-cat-website/app/assets/javascripts/main.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | var proxyForm = $('#proxy-form');
3 | proxyForm.validate({
4 | rules: {
5 | email: {
6 | required: true,
7 | email: true
8 | },
9 | proxy: {
10 | required: true,
11 | minlength: 3,
12 | remote: "/validate"
13 | }
14 | },
15 | messages: {
16 | email: {
17 | required: "Please enter a valid email address",
18 | email: "Please enter a valid email address"
19 | },
20 | proxy: {
21 | required: "Choose an address",
22 | minlength: jQuery.validator.format("Enter at least {0} characters"),
23 | remote: jQuery.validator.format("'{0}@forward.cat' is not valid or already in use")
24 | }
25 | },
26 | errorPlacement: function (error, element) {
27 | error.appendTo(element.parent().find(".help-info"));
28 | },
29 | submitHandler: function (form) {
30 | var userEmail = $('#proxy-form').find('#email').val();
31 | createCookie("user_email", userEmail, 365);
32 | form.submit();
33 | },
34 | success: function (label) {
35 | label.remove();
36 | }
37 | });
38 |
39 | $('[data-toggle="popover"]').popover();
40 |
41 | var userEmail = readCookie("user_email");
42 | if (userEmail !== null) {
43 | proxyForm.find('#email').val(userEmail);
44 | }
45 |
46 | $('#report-form').validate({
47 | rules: {
48 | proxy: {
49 | required: true,
50 | minlength: 3,
51 | email: true
52 | },
53 | message: {
54 | required: true
55 | }
56 | },
57 | messages: {
58 | email: {
59 | required: "Please enter a valid proxy address",
60 | email: "Please enter a valid proxy address"
61 | },
62 | message: {
63 | required: "Required"
64 | }
65 | },
66 | errorPlacement: function (error, element) {
67 | error.appendTo(element.parent().find(".help-info"));
68 | },
69 | submitHandler: function (form) {
70 | form.submit();
71 | },
72 | success: function (label) {
73 | label.remove();
74 | }
75 | });
76 | });
77 |
78 | function createCookie(name, value, days) {
79 | var expires = "";
80 | if (days) {
81 | var date = new Date();
82 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
83 | expires = "; expires=" + date.toGMTString();
84 | }
85 | document.cookie = name + "=" + value + expires + "; path=/";
86 | }
87 |
88 | function readCookie(name) {
89 | var nameEQ = name + "=";
90 | var ca = document.cookie.split(';');
91 | for (var i = 0; i < ca.length; i++) {
92 | var c = ca[i];
93 | while (c.charAt(0) === ' ') c = c.substring(1, c.length);
94 | if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
95 | }
96 | return null;
97 | }
98 |
--------------------------------------------------------------------------------
/forward-cat-website/app/controllers/ConfirmProxy.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.forwardcat.common.RedisKeys;
5 | import com.google.inject.Inject;
6 | import models.Repository;
7 | import models.SpamCatcher;
8 | import models.StatsRepository;
9 | import org.apache.mailet.MailAddress;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import play.mvc.Controller;
13 | import play.mvc.Result;
14 | import play.mvc.With;
15 | import views.html.error_page;
16 | import views.html.proxy_created;
17 |
18 | import java.util.Optional;
19 |
20 | import static java.util.Optional.empty;
21 | import static models.ControllerUtils.isAuthenticated;
22 | import static models.ControllerUtils.toMailAddress;
23 | import static models.ExpirationUtils.formatInstant;
24 |
25 | @With(RedirectAction.class)
26 | public class ConfirmProxy extends Controller {
27 |
28 | private static final Logger LOGGER = LoggerFactory.getLogger(ConfirmProxy.class.getName());
29 | private final StatsRepository statsRepo;
30 | private final Repository repository;
31 | private final SpamCatcher spamCatcher;
32 |
33 | @Inject
34 | ConfirmProxy(StatsRepository statsRepo, Repository repository, SpamCatcher spamCatcher) {
35 | this.statsRepo = statsRepo;
36 | this.repository = repository;
37 | this.spamCatcher = spamCatcher;
38 | }
39 |
40 | public Result confirm(String langCode, String p, String h) throws Exception {
41 | // Checking params
42 | Optional maybeProxyMail = toMailAddress(p);
43 | if (!maybeProxyMail.isPresent() || h == null) {
44 | return badRequest(error_page.render(lang(), empty()));
45 | }
46 |
47 | // Getting the proxy & checking that the hash is correct
48 | MailAddress proxyAddress = maybeProxyMail.get();
49 | Optional maybeProxy = repository.getProxy(proxyAddress);
50 | if (!isAuthenticated(maybeProxy, h)) {
51 | return badRequest(error_page.render(lang(), empty()));
52 | }
53 |
54 | // Checking that the proxy is not already active
55 | ProxyMail proxy = maybeProxy.get();
56 | if (proxy.isActive()) {
57 | LOGGER.debug("Proxy {} is already active", proxy);
58 | return badRequest();
59 | }
60 | proxy.activate();
61 |
62 | if (spamCatcher.isSpam(proxyAddress)) {
63 | proxy.block();
64 | statsRepo.incrementCounter(RedisKeys.SPAMMER_PROXIES_BLOCKED_COUNTER);
65 | }
66 |
67 | repository.save(proxy);
68 | statsRepo.incrementCounter(RedisKeys.PROXIES_ACTIVATED_COUNTER);
69 |
70 | // Generating the response
71 | String expirationDate = formatInstant(proxy.getExpirationTime(), lang());
72 | return ok(proxy_created.render(lang(), proxyAddress, expirationDate));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | 2.2.3
8 |
9 |
10 |
11 | com.forwardcat
12 | parent
13 | 1.0-SNAPSHOT
14 |
15 | common
16 | 1.0-SNAPSHOT
17 | Forward Cat - Common
18 |
19 |
20 |
21 |
22 | org.apache.james
23 | apache-mailet-api
24 | 2.5.1-SNAPSHOT
25 | provided
26 |
27 |
28 |
29 | javax.mail
30 | mail
31 | 1.4
32 |
33 |
34 |
36 | com.google.guava
37 | guava
38 | 13.0.1
39 |
40 |
41 |
42 |
43 |
44 | junit
45 | junit
46 | 4.11
47 | test
48 |
49 |
50 |
51 | org.mockito
52 | mockito-all
53 | 1.9.5
54 | test
55 |
56 |
57 |
58 | org.hamcrest
59 | hamcrest-all
60 | 1.3
61 | test
62 |
63 |
64 |
65 |
66 |
67 |
68 | org.avaje.ebeanorm
69 | avaje-ebeanorm-mavenenhancer
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/forward-cat-website/app/views/simple_email.scala.html:
--------------------------------------------------------------------------------
1 | @(lang: Lang, title: String)(content: Html)
2 |
3 |
4 |
5 |
6 |
7 | Forward Cat - @title
8 |
38 |
39 |
40 | @**@
41 |
42 |
43 |
44 | @*
45 | *@
46 | @content
47 |
48 |
49 |
50 | @**@
51 |
52 |
--------------------------------------------------------------------------------
/forward-cat-website/conf/routes:
--------------------------------------------------------------------------------
1 | # Routes
2 | # This file defines all application routes (Higher priority routes first)
3 | # ~~~~
4 |
5 | GET / @controllers.Landing.index(langCode = "en")
6 | GET /$langCode<[a-z]{2}>/ @controllers.Landing.index(langCode)
7 | GET /faq @controllers.Faq.get(langCode = "en")
8 | GET /$langCode<[a-z]{2}>/faq @controllers.Faq.get(langCode)
9 | GET /report @controllers.Report.reportGet(langCode = "en")
10 | GET /$langCode<[a-z]{2}>/report @controllers.Report.reportGet(langCode)
11 | GET /report-user @controllers.Report.reportUser(langCode = "en", proxy: String, message: String)
12 | GET /$langCode<[a-z]{2}>/report-user @controllers.Report.reportUser(langCode, proxy: String, message: String)
13 | GET /contact @controllers.Contact.contactGet(langCode = "en")
14 | GET /$langCode<[a-z]{2}>/contact @controllers.Contact.contactGet(langCode)
15 | GET /contact-sent @controllers.Contact.contactSent(langCode = "en", email: String, message: String)
16 | GET /$langCode<[a-z]{2}>/contact-sent @controllers.Contact.contactSent(langCode, email: String, message: String)
17 | GET /confirm @controllers.ConfirmProxy.confirm(langCode = "en", p: String, h: String)
18 | GET /$langCode<[a-z]{2}>/confirm @controllers.ConfirmProxy.confirm(langCode, p: String, h: String)
19 | GET /add @controllers.AddProxy.addProxy(langCode = "en", proxy: String, email: String)
20 | GET /$langCode<[a-z]{2}>/add @controllers.AddProxy.addProxy(langCode, proxy: String, email: String)
21 | GET /confirm-deletion @controllers.DeleteProxy.confirmDeletion(langCode = "en", p: String, h: String)
22 | GET /$langCode<[a-z]{2}>/confirm-deletion @controllers.DeleteProxy.confirmDeletion(langCode, p: String, h: String)
23 | GET /delete @controllers.DeleteProxy.delete(langCode = "en", p: String, h: String)
24 | GET /$langCode<[a-z]{2}>/delete @controllers.DeleteProxy.delete(langCode, p: String, h: String)
25 | GET /extend @controllers.ExtendProxy.extend(langCode = "en", p: String, h: String)
26 | GET /$langCode<[a-z]{2}>/extend @controllers.ExtendProxy.extend(langCode, p: String, h: String)
27 | GET /stats @controllers.Stats.render()
28 | GET /validate @controllers.ValidateProxy.validate(proxy: String)
29 |
30 | # Map static resources from the /public folder to the /assets URL path
31 | GET /assets/*file controllers.Assets.at(path="/public", file)
32 |
--------------------------------------------------------------------------------
/forward-cat-website/app/controllers/ExtendProxy.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.Inject;
5 | import models.Repository;
6 | import org.apache.mailet.MailAddress;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import play.mvc.Controller;
10 | import play.mvc.Result;
11 | import play.mvc.With;
12 | import views.html.error_page;
13 | import views.html.proxy_extended;
14 |
15 | import java.time.ZonedDateTime;
16 | import java.util.Date;
17 | import java.util.Optional;
18 |
19 | import static java.util.Optional.empty;
20 | import static models.ControllerUtils.isAuthenticated;
21 | import static models.ControllerUtils.toMailAddress;
22 | import static models.ExpirationUtils.*;
23 |
24 | @With(RedirectAction.class)
25 | public class ExtendProxy extends Controller {
26 |
27 | private static final Logger LOGGER = LoggerFactory.getLogger(ExtendProxy.class.getName());
28 | private final Repository repository;
29 |
30 | @Inject
31 | ExtendProxy(Repository repository) {
32 | this.repository = repository;
33 | }
34 |
35 | public Result extend(String langCode, String p, String h) {
36 | // Checking params
37 | Optional maybeProxyMail = toMailAddress(p);
38 | if (!maybeProxyMail.isPresent() || h == null) {
39 | return badRequest(error_page.render(lang(), empty()));
40 | }
41 |
42 | // Getting the proxy & checking that the hash is correct
43 | MailAddress proxyMail = maybeProxyMail.get();
44 | Optional maybeProxy = repository.getProxy(proxyMail);
45 | if (!isAuthenticated(maybeProxy, h)) {
46 | return badRequest(error_page.render(lang(), empty()));
47 | }
48 |
49 | // Checking that the proxy is active
50 | ProxyMail proxy = maybeProxy.get();
51 | if (!proxy.isActive()) {
52 | LOGGER.debug("Proxy {} is already active", proxy);
53 | return badRequest();
54 | }
55 |
56 | // Checking that the proxy has not been active for more than 15 days
57 |
58 | ZonedDateTime maxExpirationTime = toZonedDateTime(proxy.getCreationTime()).plusDays(getMaxProxyDuration());
59 | ZonedDateTime newExpirationTime = getNewExpirationTime(toZonedDateTime(proxy.getExpirationTime()).plusDays(getIncrementDaysAdded()), maxExpirationTime);
60 |
61 | Date expirationTimeDate = toDate(newExpirationTime);
62 | proxy.setExpirationTime(expirationTimeDate);
63 |
64 | repository.update(proxy);
65 |
66 | // Generating the answer
67 | String date = formatInstant(expirationTimeDate, lang());
68 | return ok(proxy_extended.render(lang(), proxyMail, date));
69 | }
70 |
71 | private ZonedDateTime getNewExpirationTime(ZonedDateTime newExpirationTime, ZonedDateTime maxExpirationTime) {
72 | if (newExpirationTime.isAfter(maxExpirationTime)) {
73 | return maxExpirationTime;
74 | }
75 | return newExpirationTime;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/ExtendProxyTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.AbstractModule;
5 | import models.Repository;
6 | import org.apache.mailet.MailAddress;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.Mock;
10 | import org.mockito.runners.MockitoJUnitRunner;
11 | import play.mvc.Result;
12 | import play.test.FakeRequest;
13 |
14 | import java.io.IOException;
15 |
16 | import static controllers.TestUtils.*;
17 | import static models.ControllerUtils.getHash;
18 | import static org.hamcrest.Matchers.is;
19 | import static org.junit.Assert.assertThat;
20 | import static org.mockito.Matchers.any;
21 | import static org.mockito.Mockito.verify;
22 | import static play.test.Helpers.*;
23 |
24 | @RunWith(MockitoJUnitRunner.class)
25 | public class ExtendProxyTest extends PlayTest {
26 |
27 | MailAddress proxyMail = toMailAddress("test@forward.cat");
28 | ProxyMail proxy = activeProxy(proxyMail, false);
29 | String proxyHash = getHash(proxy);
30 |
31 | @Mock Repository repository;
32 |
33 | @Override
34 | public AbstractModule getModule() throws IOException {
35 | whenAddressReturnProxy(repository, proxyMail, proxy);
36 |
37 | return new AbstractModule() {
38 | @Override
39 | protected void configure() {
40 | bind(Repository.class).toInstance(repository);
41 | }
42 | };
43 | }
44 |
45 | @Test
46 | public void proxyMailMissing_sendBadRequest() throws Exception {
47 | Result route = route(request(null, proxyHash));
48 | assertThat(status(route), is(BAD_REQUEST));
49 | }
50 |
51 | @Test
52 | public void hashMissing_sendBadRequest() throws Exception {
53 | Result route = route(request(proxyMail.toString(), null));
54 | assertThat(status(route), is(BAD_REQUEST));
55 | }
56 |
57 | @Test
58 | public void proxyNonExisting_sendBadRequest() throws Exception {
59 | Result route = route(request("nonValid@forward.cat", proxyHash));
60 | assertThat(status(route), is(BAD_REQUEST));
61 | }
62 |
63 | @Test
64 | public void invalidHash_sendBadRequest() throws Exception {
65 | Result route = route(request(proxyMail.toString(), "wrongHash"));
66 | assertThat(status(route), is(BAD_REQUEST));
67 | }
68 |
69 | @Test
70 | public void everythingFine_sendConfirmationPage() throws Exception {
71 | Result route = route(request(proxyMail.toString(), proxyHash));
72 | assertThat(status(route), is(OK));
73 | verify(repository).update(any(ProxyMail.class));
74 | }
75 |
76 | private FakeRequest request(String proxy, String hash) {
77 | if (proxy == null) {
78 | return fakeRequest(GET, "/extend?h=" + hash);
79 | } else if (hash == null) {
80 | return fakeRequest(GET, "/extend?p=" + proxy);
81 | }
82 | return fakeRequest(GET, "/extend?p=" + proxy + "&h=" + hash);
83 |
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-james/src/main/resources/domainlist.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
58 | forward.cat
59 |
60 | true
61 | true
62 | localhost
63 |
64 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/ValidateProxyTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.google.inject.AbstractModule;
4 | import models.Repository;
5 | import org.apache.mailet.MailAddress;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.Mock;
9 | import org.mockito.runners.MockitoJUnitRunner;
10 | import play.mvc.Result;
11 | import play.test.FakeRequest;
12 |
13 | import java.io.IOException;
14 |
15 | import static org.hamcrest.Matchers.is;
16 | import static org.junit.Assert.assertThat;
17 | import static org.mockito.Matchers.any;
18 | import static org.mockito.Mockito.when;
19 | import static play.test.Helpers.*;
20 |
21 | @RunWith(MockitoJUnitRunner.class)
22 | public class ValidateProxyTest extends PlayTest {
23 |
24 | public static final String USER_IN_USE = "in-use";
25 | public static final String USER_NOT_IN_USE = "not-in-use";
26 | @Mock Repository repository;
27 |
28 | @Override
29 | public AbstractModule getModule() throws IOException {
30 | when(repository.proxyExists(any(MailAddress.class))).thenAnswer(invocationOnMock -> {
31 | MailAddress passedAddress = (MailAddress) invocationOnMock.getArguments()[0];
32 | return (USER_IN_USE + "@forward.cat").equals(passedAddress.toString());
33 | });
34 |
35 | return new AbstractModule() {
36 | @Override
37 | protected void configure() {
38 | bind(Repository.class).toInstance(repository);
39 | }
40 | };
41 | }
42 |
43 | @Test
44 | public void noProxyParam_sendInvalidUsername() throws Exception {
45 | Result route = route(request(""));
46 | assertThatIsInvalid(route);
47 | }
48 |
49 | @Test
50 | public void incorrectProxyParam_sendInvalidUsername() throws Exception {
51 | Result route = route(request("has space"));
52 | assertThatIsInvalid(route);
53 | }
54 |
55 | @Test(expected = RuntimeException.class)
56 | public void redisConnectionError_sendInvalidUsername() throws Exception {
57 | when(repository.proxyExists(any(MailAddress.class))).thenThrow(new RuntimeException());
58 | Result route = route(request(USER_NOT_IN_USE));
59 | assertThatIsInvalid(route);
60 | }
61 |
62 | @Test
63 | public void usernameInUse_sendInvalidUsername() throws Exception {
64 | Result route = route(request(USER_IN_USE));
65 | assertThatIsInvalid(route);
66 | }
67 |
68 | @Test
69 | public void usernameFree_sendValidUsername() throws Exception {
70 | Result route = route(request(USER_NOT_IN_USE));
71 | assertThatIsValid(route);
72 | }
73 |
74 | private FakeRequest request(String proxy) {
75 | if (proxy == null) {
76 | return fakeRequest(GET, "/validate");
77 | }
78 | return fakeRequest(GET, "/validate?proxy=" + proxy);
79 | }
80 |
81 | private void assertThatIsValid(Result route) {
82 | assertThat(status(route), is(OK));
83 | assertThat(contentAsString(route), is("true"));
84 | }
85 |
86 | private void assertThatIsInvalid(Result route) {
87 | assertThat(status(route), is(OK));
88 | assertThat(contentAsString(route), is("false"));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/forward-cat-website/app/models/Options.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import redis.clients.jedis.Protocol;
4 |
5 | /**
6 | * models.Options for the Server
7 | */
8 | public class Options {
9 |
10 | // Redis models.Options
11 | private String redisHost = "localhost";
12 |
13 | private int redisPort = Protocol.DEFAULT_PORT;
14 |
15 | private int maxActiveConnections = Runtime.getRuntime().availableProcessors() * 4;
16 |
17 | private int maxIdleConnections = maxActiveConnections / 2;
18 |
19 | private int minIdleConnections = 1;
20 |
21 | private int numTestsPerEvictionRun = maxActiveConnections;
22 |
23 | private boolean testConnectionWhileIdle = true;
24 |
25 | private boolean testConnectionOnBorrow = true;
26 |
27 | private boolean testConnectionOnReturn = false;
28 |
29 | private long timeBetweenEvictionRunsMillis = 60000;
30 |
31 | private int minutesBetweenAlerts = 5;
32 |
33 | private int timeBetweenAlertMailsMillis = 1000;
34 |
35 | public String getRedisHost() {
36 | return redisHost;
37 | }
38 |
39 | public int getRedisPort() {
40 | return redisPort;
41 | }
42 |
43 | public int getMaxActiveConnections() {
44 | return maxActiveConnections;
45 | }
46 |
47 | public int getMaxIdleConnections() {
48 | return maxIdleConnections;
49 | }
50 |
51 | public int getMinIdleConnections() {
52 | return minIdleConnections;
53 | }
54 |
55 | public int getNumTestsPerEvictionRun() {
56 | return numTestsPerEvictionRun;
57 | }
58 |
59 | public boolean isTestConnectionWhileIdle() {
60 | return testConnectionWhileIdle;
61 | }
62 |
63 | public boolean isTestConnectionOnBorrow() {
64 | return testConnectionOnBorrow;
65 | }
66 |
67 | public boolean isTestConnectionOnReturn() {
68 | return testConnectionOnReturn;
69 | }
70 |
71 | public long getTimeBetweenEvictionRunsMillis() {
72 | return timeBetweenEvictionRunsMillis;
73 | }
74 |
75 | public int getMinutesBetweenAlerts() {
76 | return minutesBetweenAlerts;
77 | }
78 |
79 | public int getTimeBetweenAlertMailsMillis() {
80 | return timeBetweenAlertMailsMillis;
81 | }
82 |
83 | @Override
84 | public String toString() {
85 | return "Options{" +
86 | "redisHost='" + redisHost + '\'' +
87 | ", redisPort=" + redisPort +
88 | ", maxActiveConnections=" + maxActiveConnections +
89 | ", maxIdleConnections=" + maxIdleConnections +
90 | ", minIdleConnections=" + minIdleConnections +
91 | ", numTestsPerEvictionRun=" + numTestsPerEvictionRun +
92 | ", testConnectionWhileIdle=" + testConnectionWhileIdle +
93 | ", testConnectionOnBorrow=" + testConnectionOnBorrow +
94 | ", testConnectionOnReturn=" + testConnectionOnReturn +
95 | ", timeBetweenEvictionRunsMillis=" + timeBetweenEvictionRunsMillis +
96 | ", minutesBetweenAlerts=" + minutesBetweenAlerts +
97 | ", timeBetweenAlertMailsMillis=" + timeBetweenAlertMailsMillis +
98 | '}';
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/forward-cat-website/app/controllers/DeleteProxy.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.Inject;
5 | import models.Repository;
6 | import org.apache.mailet.MailAddress;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import play.mvc.Controller;
10 | import play.mvc.Result;
11 | import play.mvc.With;
12 | import views.html.confirm_deletion;
13 | import views.html.error_page;
14 | import views.html.proxy_deleted;
15 |
16 | import java.util.Optional;
17 |
18 | import static java.util.Optional.empty;
19 | import static models.ControllerUtils.*;
20 | import static models.ExpirationUtils.formatInstant;
21 |
22 | @With(RedirectAction.class)
23 | public class DeleteProxy extends Controller {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(DeleteProxy.class.getName());
26 | private final Repository repository;
27 |
28 | @Inject
29 | DeleteProxy(Repository repository) {
30 | this.repository = repository;
31 | }
32 |
33 | public Result confirmDeletion(String langCode, String p, String h) {
34 | // Checking params
35 | Optional maybeProxyMail = toMailAddress(p);
36 | if (!maybeProxyMail.isPresent() || h == null) {
37 | return badRequest(error_page.render(lang(), empty()));
38 | }
39 |
40 | // Getting the proxy & checking that the hash is correct
41 | MailAddress proxyMail = maybeProxyMail.get();
42 | Optional maybeProxy = repository.getProxy(proxyMail);
43 | if (!isAuthenticated(maybeProxy, h)) {
44 | return badRequest(error_page.render(lang(), empty()));
45 | }
46 |
47 | // Checking that the proxy is active
48 | ProxyMail proxy = maybeProxy.get();
49 | if (!proxy.isActive()) {
50 | LOGGER.debug("Proxy {} is not active", proxy);
51 | return badRequest();
52 | }
53 |
54 | // Generating the answer
55 | String expirationDate = formatInstant(proxy.getExpirationTime(), lang());
56 | String hashValue = getHash(proxy);
57 | return ok(confirm_deletion.render(lang(), proxyMail, expirationDate, hashValue));
58 | }
59 |
60 | public Result delete(String langCode, String p, String h) {
61 | // Checking params
62 | Optional maybeProxyMail = toMailAddress(p);
63 | if (!maybeProxyMail.isPresent() || h == null) {
64 | return badRequest(error_page.render(lang(), empty()));
65 | }
66 |
67 | // Getting the proxy & checking that the hash is correct
68 | MailAddress proxyMail = maybeProxyMail.get();
69 | Optional maybeProxy = repository.getProxy(proxyMail);
70 | if (!isAuthenticated(maybeProxy, h)) {
71 | return badRequest(error_page.render(lang(), empty()));
72 | }
73 |
74 | // Checking whether the proxy is active or not
75 | ProxyMail proxy = maybeProxy.get();
76 | if (!proxy.isActive()) {
77 | LOGGER.debug("Proxy is not active");
78 | return badRequest();
79 | }
80 |
81 | // Removing the proxy
82 | repository.delete(proxy);
83 |
84 | // Sending the response
85 | return ok(proxy_deleted.render(lang()));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/ConfirmDeletionTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.AbstractModule;
5 | import models.Repository;
6 | import org.apache.mailet.MailAddress;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.Mock;
10 | import org.mockito.runners.MockitoJUnitRunner;
11 | import play.mvc.Result;
12 | import play.test.FakeRequest;
13 |
14 | import java.io.IOException;
15 |
16 | import static controllers.TestUtils.*;
17 | import static models.ControllerUtils.getHash;
18 | import static org.hamcrest.Matchers.is;
19 | import static org.junit.Assert.assertThat;
20 | import static org.mockito.Mockito.when;
21 | import static play.mvc.Http.Status.BAD_REQUEST;
22 | import static play.test.Helpers.*;
23 |
24 | @RunWith(MockitoJUnitRunner.class)
25 | public class ConfirmDeletionTest extends PlayTest {
26 |
27 | MailAddress proxyMail = toMailAddress("test@forward.cat");
28 | ProxyMail proxy = activeProxy(proxyMail, false);
29 | String proxyHash = getHash(proxy);
30 |
31 | @Mock Repository repository;
32 |
33 | @Override
34 | public AbstractModule getModule() throws IOException {
35 | whenAddressReturnProxy(repository, proxyMail, proxy);
36 |
37 | return new AbstractModule() {
38 | @Override
39 | protected void configure() {
40 | bind(Repository.class).toInstance(repository);
41 | }
42 | };
43 | }
44 |
45 | @Test
46 | public void proxyMailMissing_sendBadRequest() throws Exception {
47 | Result route = route(request(null, proxyHash));
48 | assertThat(status(route), is(BAD_REQUEST));
49 | }
50 |
51 | @Test
52 | public void hashMissing_sendBadRequest() throws Exception {
53 | Result route = route(request(proxyMail.toString(), null));
54 | assertThat(status(route), is(BAD_REQUEST));
55 | }
56 |
57 | @Test
58 | public void proxyNonExisting_sendBadRequest() throws Exception {
59 | Result route = route(request("nonValid@forward.cat", proxyHash));
60 | assertThat(status(route), is(BAD_REQUEST));
61 | }
62 |
63 | @Test
64 | public void invalidHash_sendBadRequest() throws Exception {
65 | Result route = route(request(proxyMail.toString(), "wrongHash"));
66 | assertThat(status(route), is(BAD_REQUEST));
67 | }
68 |
69 | @Test(expected = RuntimeException.class)
70 | public void redisException_sendBadRequest() throws Exception {
71 | when(repository.getProxy(proxyMail)).thenThrow(new RuntimeException());
72 | Result route = route(request(proxyMail.toString(), proxyHash));
73 | assertThat(status(route), is(BAD_REQUEST));
74 | }
75 |
76 | @Test
77 | public void everythingFine_sendConfirmationPage() throws Exception {
78 | Result route = route(request(proxyMail.toString(), proxyHash));
79 | assertThat(status(route), is(OK));
80 | }
81 |
82 | private FakeRequest request(String proxy, String hash) {
83 | if (proxy == null) {
84 | return fakeRequest(GET, "/confirm-deletion?h=" + hash);
85 | } else if (hash == null) {
86 | return fakeRequest(GET, "/confirm-deletion?p=" + proxy);
87 | }
88 | return fakeRequest(GET, "/confirm-deletion?p=" + proxy + "&h=" + hash);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/ConfirmProxyTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.forwardcat.common.RedisKeys;
5 | import com.google.inject.AbstractModule;
6 | import models.Repository;
7 | import models.StatsRepository;
8 | import org.apache.mailet.MailAddress;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.runners.MockitoJUnitRunner;
13 | import play.mvc.Result;
14 | import play.test.FakeRequest;
15 |
16 | import javax.mail.internet.AddressException;
17 | import java.io.IOException;
18 |
19 | import static controllers.TestUtils.*;
20 | import static models.ControllerUtils.getHash;
21 | import static org.hamcrest.Matchers.is;
22 | import static org.junit.Assert.assertThat;
23 | import static org.mockito.Mockito.verify;
24 | import static play.mvc.Http.Status.BAD_REQUEST;
25 | import static play.test.Helpers.*;
26 |
27 | @RunWith(MockitoJUnitRunner.class)
28 | public class ConfirmProxyTest extends PlayTest {
29 |
30 | MailAddress proxyMail = toMailAddress("test@forward.cat");
31 | ProxyMail proxy = inactiveProxy(proxyMail);
32 | String proxyHash = getHash(proxy);
33 |
34 | @Mock Repository repository;
35 | @Mock StatsRepository statsRepo;
36 |
37 | @Override
38 | public AbstractModule getModule() throws IOException, AddressException {
39 | whenAddressReturnProxy(repository, proxyMail, proxy);
40 |
41 | return new AbstractModule() {
42 | @Override
43 | protected void configure() {
44 | bind(Repository.class).toInstance(repository);
45 | bind(StatsRepository.class).toInstance(statsRepo);
46 | }
47 | };
48 | }
49 |
50 | @Test
51 | public void proxyMailMissing_sendBadRequest() throws Exception {
52 | Result route = route(request(null, proxyHash));
53 | assertThat(status(route), is(BAD_REQUEST));
54 | }
55 |
56 | @Test
57 | public void hashMissing_sendBadRequest() throws Exception {
58 | Result route = route(request(proxyMail.toString(), null));
59 | assertThat(status(route), is(BAD_REQUEST));
60 | }
61 |
62 | @Test
63 | public void proxyNonExisting_sendBadRequest() throws Exception {
64 | Result route = route(request("nonValid@forward.cat", proxyHash));
65 | assertThat(status(route), is(BAD_REQUEST));
66 | }
67 |
68 | @Test
69 | public void invalidHash_sendBadRequest() throws Exception {
70 | Result route = route(request(proxyMail.toString(), "wrongHash"));
71 | assertThat(status(route), is(BAD_REQUEST));
72 | }
73 |
74 | @Test
75 | public void everythingFine_sendConfirmationPage() throws Exception {
76 | Result route = route(request(proxyMail.toString(), proxyHash));
77 | assertThat(status(route), is(OK));
78 | verify(repository).save(proxy);
79 | verify(statsRepo).incrementCounter(RedisKeys.PROXIES_ACTIVATED_COUNTER);
80 | }
81 |
82 | private FakeRequest request(String proxy, String hash) {
83 | if (proxy == null) {
84 | return fakeRequest(GET, "/confirm?h=" + hash);
85 | } else if (hash == null) {
86 | return fakeRequest(GET, "/confirm?p=" + proxy);
87 | }
88 | return fakeRequest(GET, "/confirm?p=" + proxy + "&h=" + hash);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/forward-cat-website/app/alerts/SendAlertJob.java:
--------------------------------------------------------------------------------
1 | package alerts;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.Inject;
5 | import models.MailSender;
6 | import models.Options;
7 | import models.Repository;
8 | import org.apache.mailet.MailAddress;
9 | import org.quartz.Job;
10 | import org.quartz.JobExecutionContext;
11 | import org.quartz.JobExecutionException;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import play.i18n.Lang;
15 | import play.twirl.api.Html;
16 | import views.html.proxy_expiring_email;
17 |
18 | import java.util.Date;
19 | import java.util.Set;
20 |
21 | import static models.ControllerUtils.getHash;
22 | import static models.ControllerUtils.toMailAddress;
23 | import static models.ExpirationUtils.formatInstant;
24 |
25 | /**
26 | * Job that sends alerts to the users of proxies before they expire in order to
27 | * allow them to extend its life.
28 | */
29 | public class SendAlertJob implements Job {
30 |
31 | private static final Logger LOGGER = LoggerFactory.getLogger(SendAlertJob.class.getName());
32 | private final String subject = "Forward Cat";
33 | private final MailSender mailSender;
34 | private final Options options;
35 | private final Repository repository;
36 |
37 | @Inject
38 | SendAlertJob(Repository repository, MailSender mailSender,
39 | Options options) {
40 | this.repository = repository;
41 | this.mailSender = mailSender;
42 | this.options = options;
43 | }
44 |
45 | @Override
46 | public void execute(JobExecutionContext context) throws JobExecutionException {
47 | // Getting the proxies that will expire before tomorrow
48 | Set expiringProxies = repository.getExpiringProxies();
49 |
50 | sendAlerts(expiringProxies);
51 | repository.removeExpiredProxies();
52 | repository.removeNonActivatedProxies();
53 | repository.removeUsersWithoutProxies();
54 | }
55 |
56 | /**
57 | * Send email alerts to the given proxy users and mark is at notified
58 | *
59 | * @param proxies
60 | */
61 | private void sendAlerts(Set proxies) {
62 | for (ProxyMail proxy : proxies) {
63 | try {
64 | // Sending the mail
65 | Lang lang = new Lang(Lang.get(proxy.getLang()).get());
66 | MailAddress address = toMailAddress(proxy.getUser().getEmailAddress()).get();
67 |
68 | Date expirationTime = proxy.getExpirationTime();
69 | String date = formatInstant(expirationTime, lang);
70 |
71 | Html content = proxy_expiring_email.render(lang, proxy.getProxyAddress(), date, getHash(proxy));
72 | mailSender.sendHtmlMail(address, subject, content.toString());
73 |
74 | proxy.setExpirationNotified(true);
75 | repository.update(proxy);
76 | } catch (Exception ex) {
77 | LOGGER.error("Unexpected exception sending expiration notification for proxy: " + proxy, ex);
78 | }
79 |
80 | // Wait before sending another email
81 | try {
82 | Thread.sleep(options.getTimeBetweenAlertMailsMillis());
83 | } catch (InterruptedException e) {
84 | LOGGER.error("Unexpected error", e);
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/DeleteProxyTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.AbstractModule;
5 | import models.Repository;
6 | import org.apache.mailet.MailAddress;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.Mock;
10 | import org.mockito.runners.MockitoJUnitRunner;
11 | import play.mvc.Result;
12 | import play.test.FakeRequest;
13 |
14 | import java.io.IOException;
15 |
16 | import static controllers.TestUtils.*;
17 | import static models.ControllerUtils.getHash;
18 | import static org.hamcrest.Matchers.is;
19 | import static org.junit.Assert.assertThat;
20 | import static org.mockito.Mockito.verify;
21 | import static org.mockito.Mockito.when;
22 | import static play.mvc.Http.Status.BAD_REQUEST;
23 | import static play.test.Helpers.*;
24 |
25 | @RunWith(MockitoJUnitRunner.class)
26 | public class DeleteProxyTest extends PlayTest {
27 |
28 | MailAddress proxyMail = toMailAddress("test@forward.cat");
29 | ProxyMail proxy = activeProxy(proxyMail, false);
30 | String proxyHash = getHash(proxy);
31 |
32 | @Mock Repository repository;
33 |
34 | @Override
35 | public AbstractModule getModule() throws IOException {
36 | whenAddressReturnProxy(repository, proxyMail, proxy);
37 |
38 | return new AbstractModule() {
39 | @Override
40 | protected void configure() {
41 | bind(Repository.class).toInstance(repository);
42 | }
43 | };
44 | }
45 |
46 | @Test
47 | public void proxyMailMissing_sendBadRequest() throws Exception {
48 | Result route = route(request(null, proxyHash));
49 | assertThat(status(route), is(BAD_REQUEST));
50 | }
51 |
52 | @Test
53 | public void hashMissing_sendBadRequest() throws Exception {
54 | Result route = route(request(proxyMail.toString(), null));
55 | assertThat(status(route), is(BAD_REQUEST));
56 | }
57 |
58 | @Test
59 | public void proxyNonExisting_sendBadRequest() throws Exception {
60 | Result route = route(request("nonValid@forward.cat", proxyHash));
61 | assertThat(status(route), is(BAD_REQUEST));
62 | }
63 |
64 | @Test
65 | public void invalidHash_sendBadRequest() throws Exception {
66 | Result route = route(request(proxyMail.toString(), "wrongHash"));
67 | assertThat(status(route), is(BAD_REQUEST));
68 | }
69 |
70 | @Test(expected = RuntimeException.class)
71 | public void redisError_sendInternalError() throws Exception {
72 | when(repository.getProxy(proxyMail)).thenThrow(new RuntimeException());
73 | Result route = route(request(proxyMail.toString(), proxyHash));
74 | assertThat(status(route), is(BAD_REQUEST));
75 | }
76 |
77 | @Test
78 | public void everythingFine_sendConfirmationPage() throws Exception {
79 | Result route = route(request(proxyMail.toString(), proxyHash));
80 | assertThat(status(route), is(OK));
81 | verify(repository).delete(proxy);
82 | }
83 |
84 | private FakeRequest request(String proxy, String hash) {
85 | if (proxy == null) {
86 | return fakeRequest(GET, "/delete?h=" + hash);
87 | } else if (hash == null) {
88 | return fakeRequest(GET, "/delete?p=" + proxy);
89 | }
90 | return fakeRequest(GET, "/delete?p=" + proxy + "&h=" + hash);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/forward-cat-website/app/controllers/AddProxy.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.forwardcat.common.User;
5 | import com.google.inject.Inject;
6 | import models.MailSender;
7 | import models.Repository;
8 | import org.apache.mailet.MailAddress;
9 | import play.i18n.Messages;
10 | import play.mvc.Controller;
11 | import play.mvc.Result;
12 | import play.mvc.With;
13 | import play.twirl.api.Html;
14 | import views.html.email_sent;
15 | import views.html.error_page;
16 | import views.html.proxy_created_email;
17 |
18 | import java.time.ZonedDateTime;
19 | import java.util.Optional;
20 |
21 | import static java.util.Optional.empty;
22 | import static models.ControllerUtils.*;
23 | import static models.ExpirationUtils.now;
24 | import static models.ExpirationUtils.toDate;
25 | import static models.MailAddressNormalizer.normalize;
26 |
27 | @With(RedirectAction.class)
28 | public class AddProxy extends Controller {
29 |
30 | private static final int INITIAL_PROXY_DURATION = 7;
31 | private static final int MAX_PROXIES = 3;
32 | private final Repository repository;
33 | private final MailSender mailSender;
34 |
35 | @Inject
36 | AddProxy(Repository repository, MailSender mailSender) {
37 | this.repository = repository;
38 | this.mailSender = mailSender;
39 | }
40 |
41 | public Result addProxy(String langCode, String proxy, String email) {
42 | // Checking params
43 | Optional maybeUserMail = normalize(toMailAddress(email));
44 | if (proxy == null || !maybeUserMail.isPresent()) {
45 | return badRequest(error_page.render(lang(), empty()));
46 | }
47 |
48 | MailAddress userMail = maybeUserMail.get();
49 | // Don't allow chained proxies
50 | if (isLocal(userMail)) {
51 | return badRequest(error_page.render(lang(), empty()));
52 | }
53 |
54 | Optional maybeProxyMailAddress = getMailAddress(proxy);
55 | if (!maybeProxyMailAddress.isPresent()) {
56 | return badRequest(error_page.render(lang(), empty()));
57 | }
58 |
59 | MailAddress proxyMailAddress = maybeProxyMailAddress.get();
60 |
61 | ZonedDateTime creationTime = now();
62 | ZonedDateTime expirationTime = creationTime.plusDays(INITIAL_PROXY_DURATION);
63 | ProxyMail proxyMail = ProxyMail.create(proxyMailAddress, userMail, toDate(creationTime), toDate(expirationTime), lang().code());
64 |
65 | // Creating the proxy
66 | if (repository.proxyExists(proxyMailAddress)) {
67 | return badRequest(error_page.render(lang(), empty())); // Proxy already exists: cannot create a new one
68 | }
69 |
70 | User user = repository.getUser(userMail).orElseGet(() ->
71 | User.create(userMail, toDate(creationTime)));
72 |
73 | if (user.getProxies().size() >= MAX_PROXIES) {
74 | return badRequest(error_page.render(lang(), Optional.of("error.too_many_proxies")));
75 | }
76 |
77 | user.getProxies().add(proxyMail);
78 | repository.save(user);
79 |
80 | // Sending the confirmation mail
81 | String subject = Messages.get(lang(), "proxy_created_email.title");
82 | Html content = proxy_created_email.render(lang(), proxyMailAddress, getHash(proxyMail));
83 | mailSender.sendHtmlMail(userMail, subject, content.toString());
84 |
85 | return ok(email_sent.render(lang()));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/RedirectActionTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.google.common.collect.ImmutableMap;
4 | import com.google.common.collect.Lists;
5 | import com.google.inject.AbstractModule;
6 | import org.junit.Test;
7 | import play.mvc.Http;
8 | import play.mvc.Result;
9 | import play.test.FakeRequest;
10 |
11 | import java.io.IOException;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.Map.Entry;
15 |
16 | import static controllers.ContainsHtmlLang.containsHtmlLang;
17 | import static org.hamcrest.Matchers.*;
18 | import static org.junit.Assert.assertThat;
19 | import static play.mvc.Http.Status.MOVED_PERMANENTLY;
20 | import static play.test.Helpers.*;
21 |
22 | public class RedirectActionTest extends PlayTest {
23 |
24 | @Test
25 | public void noLangIsDefined_contentShouldBeInEnglish() throws Exception {
26 | List pages = Lists.newArrayList("/", "/report");
27 |
28 | for (String test : pages) {
29 | Result result = route(request("forward.cat", test + "?param1=a"));
30 | assertThat(status(result), is(OK));
31 | assertThat(contentAsString(result), containsHtmlLang("en"));
32 | }
33 | }
34 |
35 | @Test
36 | public void enInPath_contentShouldBeInEnglish() throws Exception {
37 | List pages = Lists.newArrayList("/en/", "/en/report");
38 |
39 | for (String test : pages) {
40 | Result result = route(request("forward.cat", test + "?param1=a"));
41 | assertThat(status(result), is(NOT_FOUND));
42 | }
43 | }
44 |
45 | @Test
46 | public void langInPath_contentShouldBeInTheGivenLang() throws Exception {
47 | Map tests = ImmutableMap.builder().
48 | put("/es/", "es").
49 | put("/ca/", "ca").
50 | put("/es/report", "es").
51 | put("/ca/report", "ca").
52 | build();
53 |
54 | for (Entry entry : tests.entrySet()) {
55 | String path = entry.getKey() + "?param1=a";
56 | Result result = route(request("forward.cat", path));
57 |
58 | assertThat(status(result), is(OK));
59 | assertThat(contentAsString(result), containsHtmlLang(entry.getValue()));
60 | }
61 | }
62 |
63 | @Test
64 | public void incorrectPathStartsWithLang_shouldNotMatchAnyController() throws Exception {
65 | Result result = route(request("forward.cat", "/calculator?param1=a"));
66 | assertThat(result, is(nullValue()));
67 | }
68 |
69 | @Test
70 | public void wwwInSubdomain_requestShouldBeRedirected() throws Exception {
71 | Result result = route(request("www.forward.cat", "/?param1=a¶m2=b"));
72 | assertThat(status(result), is(MOVED_PERMANENTLY));
73 | assertThat(location(result), is("https://forward.cat/?param1=a¶m2=b"));
74 | }
75 |
76 | private FakeRequest request(String host, String path) {
77 | return fakeRequest(GET, path)
78 | .withHeader(Http.HeaderNames.HOST, host);
79 | }
80 |
81 | private String location(Result result) {
82 | return result.toScala().header().headers().apply(LOCATION);
83 | }
84 |
85 | @Override
86 | public AbstractModule getModule() throws IOException {
87 | return new AbstractModule() {
88 | @Override
89 | protected void configure() {
90 | // Nothing to do
91 | }
92 | };
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/forward-cat-website/app/models/DomainManager.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import com.google.common.collect.ImmutableMap;
4 | import play.i18n.Lang;
5 | import play.libs.F;
6 | import play.mvc.Action;
7 | import play.mvc.Http;
8 | import play.mvc.Result;
9 |
10 | import java.util.Map;
11 | import java.util.Optional;
12 | import java.util.regex.Matcher;
13 | import java.util.regex.Pattern;
14 |
15 | import static com.google.common.base.Preconditions.checkArgument;
16 | import static play.mvc.Results.movedPermanently;
17 | import static play.mvc.Results.notFound;
18 |
19 | public class DomainManager {
20 |
21 | private static final Lang DEFAULT_LANG = Lang.forCode("en");
22 | private static final Pattern PATH_LANG_PATTTERN = Pattern.compile("^/(?[a-z]{2})([/\\?].*)?");
23 | private static final Map SUBDOMAIN_REDIRECTS = ImmutableMap.builder()
24 | .put("www", "https://forward.cat")
25 | .build();
26 |
27 | public static F.Promise redirect(Http.Context ctx, Action> delegate) throws Throwable {
28 | Http.Request request = ctx.request();
29 | String host = request.host();
30 |
31 | // www subdomain: redirect
32 | if (host != null && host.startsWith("www.")) {
33 | String redirectUrl = buildUrl("www", request.path(), request.queryString());
34 | return promise(movedPermanently(redirectUrl));
35 | }
36 |
37 | // Lang in path: validate and set
38 | Matcher matcher = PATH_LANG_PATTTERN.matcher(request.path());
39 | if (matcher.matches()) {
40 | String lang = matcher.group("lang");
41 | if (!isValidLang(lang) || DEFAULT_LANG.code().equals(lang)) {
42 | return promise(notFound());
43 | } else {
44 | ctx.changeLang(lang);
45 | return delegate.call(ctx);
46 | }
47 | }
48 |
49 | // No lang: use default
50 | ctx.changeLang(DEFAULT_LANG);
51 | return delegate.call(ctx);
52 | }
53 |
54 | public static String buildUrl(play.api.i18n.Lang lang, String path) {
55 | checkArgument(path.startsWith("/"), "path must start with /");
56 | checkArgument(isValidLang(lang.language()), "%s is not a valid language", lang.language());
57 |
58 | String language = lang.language();
59 | if (language.equals(DEFAULT_LANG.language())) {
60 | return path;
61 | } else {
62 | return String.format("/%s%s", language, path);
63 | }
64 | }
65 |
66 | private static String buildUrl(String subdomain, String path, Map query) {
67 | String hostAndPath = SUBDOMAIN_REDIRECTS.get(subdomain);
68 | return hostAndPath + path + queryString(query);
69 | }
70 |
71 | private static String queryString(Map query) {
72 | Optional params = query.entrySet().stream()
73 | .filter(e -> e.getKey() != null && e.getValue() != null && e.getValue().length > 0)
74 | .map(e -> e.getKey() + "=" + e.getValue()[0])
75 | .reduce((p1, p2) -> p1 + "&" + p2);
76 | return params.map(p -> "?" + p).orElse("");
77 | }
78 |
79 | private static boolean isValidLang(String langCode) {
80 | Lang lang = Lang.forCode(langCode);
81 | return Lang.availables().contains(lang);
82 | }
83 |
84 | private static F.Promise promise(A result) {
85 | return F.Promise.pure(result);
86 | }
87 |
88 | private DomainManager() {
89 | // Non-instantiable
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/forward-cat-website/app/models/ControllerUtils.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.common.base.Charsets;
5 | import com.google.common.hash.HashFunction;
6 | import com.google.common.hash.Hasher;
7 | import com.google.common.hash.Hashing;
8 | import org.apache.mailet.MailAddress;
9 | import play.i18n.Lang;
10 | import play.mvc.Http;
11 |
12 | import javax.mail.internet.AddressException;
13 | import java.util.Optional;
14 | import java.util.regex.Pattern;
15 |
16 | public class ControllerUtils {
17 | private static final Pattern HOST_LANG_PATTTERN = Pattern.compile("^[a-z]{2}\\..*");
18 | private static final String DOMAIN_NAME = "forward.cat";
19 |
20 | public static Lang getBestLanguage(Http.Request request, Lang preferred) {
21 | // Getting the info from the hostname
22 | String host = request.host();
23 | if (host != null && HOST_LANG_PATTTERN.matcher(host).matches()) {
24 | Lang lang = Lang.forCode(host.substring(0, 2));
25 | if (Lang.availables().contains(lang)) {
26 | return lang;
27 | }
28 | }
29 |
30 | // Returning the preferred language
31 | return preferred;
32 | }
33 |
34 | private static HashFunction HASH_FUNCTION = Hashing.murmur3_32(0); // Setting seed for consistent hashing
35 |
36 | /**
37 | * Given a {@link com.forwardcat.common.ProxyMail}, returns a hash that identifies it uniquely
38 | */
39 | public static String getHash(ProxyMail proxy) {
40 | Hasher hasher = HASH_FUNCTION.newHasher();
41 | hasher.putString(proxy.getCreationTime().toString(), Charsets.UTF_8);
42 | return hasher.hash().toString();
43 | }
44 |
45 | /**
46 | * Given a {@link com.forwardcat.common.ProxyMail} and a hash, returns true if the hash is valid
47 | */
48 | public static boolean isAuthenticated(Optional proxy, String hash) {
49 | return proxy.isPresent() && hash.equals(getHash(proxy.get()));
50 | }
51 |
52 | /**
53 | * Given a username and a domain name, returns a {@link org.apache.mailet.MailAddress}
54 | */
55 | public static Optional getMailAddress(String username) {
56 | return getMailAddress(username, DOMAIN_NAME);
57 | }
58 |
59 | /**
60 | * Given a username and a domain name, returns a {@link org.apache.mailet.MailAddress}
61 | */
62 | public static Optional getMailAddress(String username, String domainName) {
63 | if (username != null && domainName != null) {
64 | try {
65 | return Optional.of(new MailAddress(username.toLowerCase(), domainName.toLowerCase()));
66 | } catch (AddressException e) {
67 | // Do nothing
68 | }
69 | }
70 | return Optional.empty();
71 | }
72 |
73 | /**
74 | * Returns true if the given {@link MailAddress} has the local domain name
75 | */
76 | public static boolean isLocal(MailAddress userMail) {
77 | return DOMAIN_NAME.equals(userMail.getDomain());
78 | }
79 |
80 | /**
81 | * Converts the given String to a {@link MailAddress}
82 | */
83 | public static Optional toMailAddress(String emailAddress) {
84 | if (emailAddress != null) {
85 | try {
86 | return Optional.of(new MailAddress(emailAddress.toLowerCase()));
87 | } catch (AddressException ex) {
88 | // Do nothing
89 | }
90 | }
91 | return Optional.empty();
92 | }
93 |
94 | private ControllerUtils() {
95 | // Non-instantiable
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/forward-cat-website/app/models/Repository.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import com.avaje.ebean.Ebean;
4 | import com.avaje.ebean.RawSql;
5 | import com.avaje.ebean.RawSqlBuilder;
6 | import com.avaje.ebean.bean.EntityBean;
7 | import com.forwardcat.common.ProxyMail;
8 | import com.forwardcat.common.User;
9 | import org.apache.mailet.MailAddress;
10 |
11 | import javax.annotation.Nonnull;
12 | import java.util.Date;
13 | import java.util.List;
14 | import java.util.Optional;
15 | import java.util.Set;
16 |
17 | import static models.ExpirationUtils.now;
18 | import static models.ExpirationUtils.toDate;
19 |
20 | public class Repository {
21 |
22 | public void save(EntityBean entity) {
23 | Ebean.save(entity);
24 | }
25 |
26 | public void delete(ProxyMail proxy) {
27 | Ebean.delete(proxy);
28 | }
29 |
30 | /**
31 | * Returns the {@link ProxyMail} linked to the given mail address
32 | */
33 | public Optional getProxy(@Nonnull MailAddress address) {
34 | return Optional.ofNullable(Ebean.find(ProxyMail.class, toString(address)));
35 | }
36 |
37 | public Optional getUser(@Nonnull MailAddress address) {
38 | return Optional.ofNullable(
39 | Ebean.find(User.class)
40 | .where()
41 | .eq("email_address", toString(address))
42 | .findUnique()
43 | );
44 | }
45 |
46 | public boolean proxyExists(@Nonnull MailAddress proxyMailAddress) {
47 | int count = Ebean.createQuery(ProxyMail.class)
48 | .where()
49 | .eq("proxy_address", toString(proxyMailAddress))
50 | .findRowCount();
51 | return count >= 1;
52 | }
53 |
54 | public void update(@Nonnull ProxyMail proxy) {
55 | Ebean.update(proxy);
56 | }
57 |
58 | public Set getExpiringProxies() {
59 | return Ebean.find(ProxyMail.class)
60 | .where()
61 | .lt("expiration_time", toDate(now().plusDays(1)))
62 | .eq("expiration_notified", false)
63 | .findSet();
64 | }
65 |
66 | public void removeExpiredProxies() {
67 | List expiredProxies = Ebean.find(ProxyMail.class)
68 | .where()
69 | .lt("expiration_time", new Date())
70 | .findIds();
71 |
72 | delete(ProxyMail.class, expiredProxies);
73 | }
74 |
75 | public void removeNonActivatedProxies() {
76 | List ids = Ebean.find(ProxyMail.class)
77 | .where()
78 | .eq("active", false)
79 | .lt("creation_time", toDate(now().plusMinutes(-30)))
80 | .findIds();
81 |
82 | delete(ProxyMail.class, ids);
83 | }
84 |
85 | public void removeUsersWithoutProxies() {
86 | String sql = "select u.id from users u where u.id not in " +
87 | "(select user_id from proxy_mail)";
88 |
89 | RawSql rawSql = RawSqlBuilder
90 | .parse(sql)
91 | .columnMapping("u.id", "id")
92 | .create();
93 |
94 | List ids = Ebean.find(User.class)
95 | .setRawSql(rawSql)
96 | .findIds();
97 |
98 | delete(User.class, ids);
99 | }
100 |
101 | private void delete(Class clazz, List ids) {
102 | if (!ids.isEmpty()) {
103 | Ebean.delete(clazz, ids);
104 | }
105 | }
106 |
107 | private String toString(@Nonnull MailAddress address) {
108 | return address.toString().toLowerCase();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-james/src/main/java/com/forwardcat/james/FileDKIMSign.java:
--------------------------------------------------------------------------------
1 | package com.forwardcat.james;
2 |
3 | import org.apache.commons.io.IOUtils;
4 | import org.apache.commons.ssl.PKCS8Key;
5 | import org.apache.james.jdkim.mailets.DKIMSign;
6 | import org.springframework.context.ResourceLoaderAware;
7 | import org.springframework.core.io.Resource;
8 | import org.springframework.core.io.ResourceLoader;
9 |
10 | import javax.mail.MessagingException;
11 | import java.io.ByteArrayInputStream;
12 | import java.io.IOException;
13 | import java.security.GeneralSecurityException;
14 | import java.security.NoSuchAlgorithmException;
15 | import java.security.PrivateKey;
16 | import java.security.spec.InvalidKeySpecException;
17 |
18 | /**
19 | * Mailet based on {@link DKIMSign} that loads the private key from a file
20 | * instead of from a init parameter.
21 | *
22 | * Since the field privateKey is private it {@link DKIMSign}, a new field privateKey
23 | * is declared in order to override {@link #getPrivateKey()}.
24 | *
25 | * The {@link #init()} method of the superclass is called first in order to load all
26 | * the other init parameters.
27 | */
28 | public class FileDKIMSign extends DKIMSign implements ResourceLoaderAware {
29 |
30 | static final String PRIVATE_KEY_FILE_PARAM = "privateKeyFile";
31 |
32 | private PrivateKey privateKey;
33 | private ResourceLoader resourceLoader;
34 |
35 | @Override
36 | protected PrivateKey getPrivateKey() {
37 | return privateKey;
38 | }
39 |
40 | @Override
41 | public void setResourceLoader(ResourceLoader resourceLoader) {
42 | this.resourceLoader = resourceLoader;
43 | }
44 |
45 | @Override
46 | public void init() throws MessagingException {
47 | // Loading all the parameters: privateKey will fail
48 | try {
49 | super.init();
50 | } catch (Exception ex) {
51 | // Do nothing!
52 | }
53 |
54 | byte[] privateKey = loadPrivateKey();
55 | String privateKeyPassword = getInitParameter("privateKeyPassword", null);
56 | try {
57 | PKCS8Key pkcs8 = new PKCS8Key(new ByteArrayInputStream(
58 | privateKey),
59 | privateKeyPassword != null ? privateKeyPassword
60 | .toCharArray() : null);
61 | this.privateKey = pkcs8.getPrivateKey();
62 | // privateKey = DKIMSigner.getPrivateKey(privateKeyString);
63 | } catch (NoSuchAlgorithmException e) {
64 | throw new MessagingException("Unknown private key algorithm: "
65 | + e.getMessage(), e);
66 | } catch (InvalidKeySpecException e) {
67 | throw new MessagingException(
68 | "PrivateKey should be in base64 encoded PKCS8 (der) format: "
69 | + e.getMessage(), e);
70 | } catch (GeneralSecurityException e) {
71 | throw new MessagingException("General security exception: "
72 | + e.getMessage(), e);
73 | }
74 | }
75 |
76 | private byte[] loadPrivateKey() throws MessagingException {
77 | String privateKeyFile = getInitParameter(PRIVATE_KEY_FILE_PARAM);
78 | if (privateKeyFile == null) {
79 | throw new MessagingException("The init parameter 'privateKeyFile' has not been set");
80 | }
81 |
82 | Resource resource = resourceLoader.getResource(privateKeyFile);
83 | if (!resource.exists()) {
84 | throw new MessagingException("Resource " + privateKeyFile + " has not been found. Make sure " +
85 | "is in the conf/ directory");
86 | }
87 |
88 | try {
89 | return IOUtils.toByteArray(resource.getInputStream());
90 | } catch (IOException e) {
91 | throw new MessagingException("Error while reading private key", e);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/ReportTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.google.inject.AbstractModule;
5 | import models.MailSender;
6 | import models.Repository;
7 | import org.apache.mailet.MailAddress;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.Mock;
11 | import org.mockito.runners.MockitoJUnitRunner;
12 | import play.mvc.Result;
13 | import play.test.FakeRequest;
14 |
15 | import java.io.IOException;
16 | import java.util.Optional;
17 |
18 | import static controllers.TestUtils.*;
19 | import static org.hamcrest.Matchers.is;
20 | import static org.junit.Assert.assertThat;
21 | import static org.mockito.Matchers.anyString;
22 | import static org.mockito.Mockito.*;
23 | import static play.test.Helpers.*;
24 |
25 | @RunWith(MockitoJUnitRunner.class)
26 | public class ReportTest extends PlayTest {
27 |
28 | MailAddress inactiveProxy = toMailAddress("inactive@forward.cat");
29 | ProxyMail inactive = inactiveProxy(inactiveProxy);
30 |
31 | MailAddress blockedProxy = toMailAddress("blocked@forward.cat");
32 | ProxyMail blocked = activeProxy(blockedProxy, true);
33 |
34 | MailAddress spammerProxy = toMailAddress("spammer@forward.cat");
35 | ProxyMail spammer = activeProxy(blockedProxy, false);
36 |
37 | @Mock Repository repository;
38 | @Mock MailSender mailSender;
39 |
40 | @Override
41 | public AbstractModule getModule() throws IOException {
42 | when(repository.getProxy(any(MailAddress.class))).thenAnswer(invocationOnMock -> {
43 | String strAddress = invocationOnMock.getArguments()[0].toString();
44 | if (inactive.toString().equals(strAddress)) {
45 | return Optional.of(inactive);
46 | } else if (blocked.toString().equals(strAddress)) {
47 | return Optional.of(blocked);
48 | } else if (spammerProxy.toString().equals(strAddress)) {
49 | return Optional.of(spammer);
50 | }
51 | return Optional.empty();
52 | });
53 |
54 | return new AbstractModule() {
55 | @Override
56 | protected void configure() {
57 | bind(Repository.class).toInstance(repository);
58 | bind(MailSender.class).toInstance(mailSender);
59 | }
60 | };
61 | }
62 |
63 | @Test
64 | public void nonLocalEmail_dontSendEmail() throws Exception {
65 | Result route = route(request("user@mail.com"));
66 | verifyZeroInteractions(mailSender);
67 | assertThat(status(route), is(OK));
68 | }
69 |
70 | @Test
71 | public void nonExistantProxy_dontSendEmail() throws Exception {
72 | Result route = route(request("not_there@forward.cat"));
73 | verifyZeroInteractions(mailSender);
74 | assertThat(status(route), is(OK));
75 | }
76 |
77 | @Test
78 | public void nonActiveProxy_dontSendEmail() throws Exception {
79 | Result route = route(request(inactiveProxy.toString()));
80 | verifyZeroInteractions(mailSender);
81 | assertThat(status(route), is(OK));
82 | }
83 |
84 | @Test
85 | public void blockedProxy_dontSendEmail() throws Exception {
86 | Result route = route(request(blockedProxy.toString()));
87 | verifyZeroInteractions(mailSender);
88 | assertThat(status(route), is(OK));
89 | }
90 |
91 | @Test
92 | public void regularProxy_sendEmail() throws Exception {
93 | Result route = route(request(spammerProxy.toString()));
94 | assertThat(status(route), is(OK));
95 | verify(mailSender).sendHtmlMail(any(MailAddress.class), anyString(), anyString());
96 | }
97 |
98 | private FakeRequest request(String proxy) {
99 | return fakeRequest(GET, "/report-user?proxy=" + proxy + "&message=hey%20there");
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-james/src/main/java/com/forwardcat/james/ForwardCatResourcesProvider.java:
--------------------------------------------------------------------------------
1 | package com.forwardcat.james;
2 |
3 | import com.avaje.ebean.EbeanServer;
4 | import com.avaje.ebean.EbeanServerFactory;
5 | import com.avaje.ebean.config.DataSourceConfig;
6 | import com.avaje.ebean.config.ServerConfig;
7 | import com.forwardcat.common.ProxyMail;
8 | import com.forwardcat.common.User;
9 | import redis.clients.jedis.JedisPool;
10 | import redis.clients.jedis.JedisPoolConfig;
11 |
12 | import javax.annotation.PostConstruct;
13 | import javax.annotation.PreDestroy;
14 | import java.io.IOException;
15 | import java.util.Properties;
16 |
17 | /**
18 | * Utility class that initializes and holds the resources used by the Forward Cat
19 | * mailets
20 | */
21 | public class ForwardCatResourcesProvider {
22 |
23 | private static final String PROPS_FILE = "forward-cat.properties";
24 |
25 | private JedisPool pool;
26 | private EbeanServer ebeanServer;
27 |
28 | public JedisPool getPool() {
29 | return pool;
30 | }
31 |
32 | public EbeanServer getEbeanServer() {
33 | return ebeanServer;
34 | }
35 |
36 | @PostConstruct
37 | public void init() {
38 | // Loading properties from file
39 | Properties props = new Properties();
40 | try {
41 | ClassLoader loader = Thread.currentThread().getContextClassLoader();
42 | props.load(loader.getResourceAsStream(PROPS_FILE));
43 | } catch (IOException e) {
44 | // We can't do anything about it
45 | throw new IllegalStateException("Is " + PROPS_FILE + " in the classpath?", e);
46 | }
47 |
48 | configEbean(props);
49 | configRedis(props);
50 | }
51 |
52 | private void configRedis(Properties props) {
53 | // Configuring Jedis
54 | JedisPoolConfig config = new JedisPoolConfig();
55 | config.setMaxActive(Integer.parseInt(props.getProperty("max-active-connections", "10")));
56 | config.setMinIdle(Integer.parseInt(props.getProperty("min-idle-connections", "1")));
57 |
58 | String redisHost = props.getProperty("redis-host", "localhost");
59 | pool = new JedisPool(config, redisHost);
60 | }
61 |
62 | private void configEbean(Properties props) {
63 | // http://www.avaje.org/ebean/getstarted_programmatic.html
64 | ServerConfig config = new ServerConfig();
65 | config.setName("fwdconnection");
66 |
67 | // Define DataSource parameters
68 | DataSourceConfig postgresDb = new DataSourceConfig();
69 | postgresDb.setDriver(props.getProperty("datasource.pg.databaseDriver"));
70 | postgresDb.setUsername(props.getProperty("datasource.pg.username"));
71 | postgresDb.setPassword(props.getProperty("datasource.pg.password"));
72 | postgresDb.setUrl(props.getProperty("datasource.pg.databaseUrl"));
73 | postgresDb.setHeartbeatSql(props.getProperty("datasource.pg.heartbeatsql"));
74 |
75 | config.setDataSourceConfig(postgresDb);
76 |
77 | // set DDL options...
78 | config.setDdlGenerate(false);
79 | config.setDdlRun(false);
80 |
81 | // config.setDefaultServer(false);
82 | // config.setRegister(false);
83 |
84 | // automatically determine the DatabasePlatform
85 | // using the jdbc driver
86 | // config.setDatabasePlatform(new PostgresPlatform());
87 |
88 | config.addClass(ProxyMail.class);
89 | config.addClass(User.class);
90 |
91 | // specify jars to search for entity beans
92 | // config.addJar("someJarThatContainsEntityBeans.jar");
93 |
94 | // create the EbeanServer instance
95 | ebeanServer = EbeanServerFactory.create(config);
96 | }
97 |
98 | @PreDestroy
99 | public void shutdown() {
100 | pool.destroy();
101 | ebeanServer.shutdown(true, true);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-common/src/main/java/com/forwardcat/common/ProxyMail.java:
--------------------------------------------------------------------------------
1 | package com.forwardcat.common;
2 |
3 | import org.apache.mailet.MailAddress;
4 |
5 | import javax.persistence.*;
6 | import java.time.Instant;
7 | import java.util.Date;
8 |
9 | import static com.google.common.base.Preconditions.checkState;
10 | import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
11 |
12 | /**
13 | * Temporary mail
14 | */
15 | @Entity(name = "proxies")
16 | public class ProxyMail {
17 |
18 | @Id @Column(nullable = false)
19 | private String proxyAddress;
20 | @ManyToOne
21 | private User user;
22 | @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP)
23 | private Date creationTime;
24 | @Column(nullable = false)
25 | private String lang;
26 | @Column(nullable = false) @Temporal(TemporalType.TIMESTAMP)
27 | private Date expirationTime;
28 | private boolean blocked = false;
29 | private boolean active = false;
30 | private boolean expirationNotified = false;
31 |
32 | public String getProxyAddress() {
33 | return proxyAddress;
34 | }
35 |
36 | public void setProxyAddress(String proxyAddress) {
37 | this.proxyAddress = proxyAddress;
38 | }
39 |
40 | public User getUser() {
41 | return user;
42 | }
43 |
44 | public void setUser(User user) {
45 | this.user = user;
46 | }
47 |
48 | public Date getCreationTime() {
49 | return creationTime;
50 | }
51 |
52 | public void setCreationTime(Date creationTime) {
53 | this.creationTime = creationTime;
54 | }
55 |
56 | public Date getExpirationTime() {
57 | return expirationTime;
58 | }
59 |
60 | public void setExpirationTimeStr(String expirationTime) {
61 | this.expirationTime = Date.from(Instant.from(ISO_OFFSET_DATE_TIME.parse(expirationTime)));
62 | }
63 |
64 | public void setExpirationTime(Date expirationTime) {
65 | this.expirationTime = expirationTime;
66 | }
67 |
68 | public String getLang() {
69 | return lang;
70 | }
71 |
72 | public void setLang(String lang) {
73 | this.lang = lang;
74 | }
75 |
76 | public void block() {
77 | checkState(!blocked, "Proxy is already blocked");
78 | blocked = true;
79 | }
80 |
81 | public void setBlocked(boolean blocked) {
82 | this.blocked = blocked;
83 | }
84 |
85 | public boolean isBlocked() {
86 | return blocked;
87 | }
88 |
89 | public void activate() {
90 | checkState(!active, "Proxy is already active");
91 | active = true;
92 | }
93 |
94 | public boolean isActive() {
95 | return active;
96 | }
97 |
98 | public void setActive(boolean active) {
99 | this.active = active;
100 | }
101 |
102 | public boolean isExpirationNotified() {
103 | return expirationNotified;
104 | }
105 |
106 | public void setExpirationNotified(boolean expirationNotified) {
107 | this.expirationNotified = expirationNotified;
108 | }
109 |
110 | @Override
111 | public String toString() {
112 | return "ProxyMail{" +
113 | "proxyAddress='" + proxyAddress + '\'' +
114 | ", creationTime='" + creationTime + '\'' +
115 | ", lang='" + lang + '\'' +
116 | ", expirationTime='" + expirationTime + '\'' +
117 | ", blocked=" + blocked +
118 | ", active=" + active +
119 | ", expirationNotified=" + expirationNotified +
120 | '}';
121 | }
122 |
123 | public static ProxyMail create(MailAddress proxyAddress, MailAddress userAddress, Date creationTime, Date expirationTime, String lang) {
124 | ProxyMail proxy = new ProxyMail();
125 | proxy.setProxyAddress(proxyAddress.toString().toLowerCase());
126 | proxy.setCreationTime(creationTime);
127 | proxy.setExpirationTime(expirationTime);
128 | proxy.setLang(lang);
129 | return proxy;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-james/src/test/java/com/forwardcat/james/FileDKIMSignTest.java:
--------------------------------------------------------------------------------
1 | package com.forwardcat.james;
2 |
3 | import org.apache.mailet.MailetConfig;
4 | import org.apache.mailet.MailetContext;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.Mock;
9 | import org.mockito.runners.MockitoJUnitRunner;
10 | import org.springframework.core.io.Resource;
11 | import org.springframework.core.io.ResourceLoader;
12 |
13 | import javax.mail.MessagingException;
14 | import java.io.ByteArrayInputStream;
15 | import java.io.IOException;
16 | import java.net.UnknownHostException;
17 |
18 | import static org.mockito.Mockito.when;
19 |
20 | @RunWith(MockitoJUnitRunner.class)
21 | public class FileDKIMSignTest {
22 |
23 | FileDKIMSign mailet;
24 |
25 | String privateKeyFile = "privateKey.pem";
26 |
27 | @Mock ResourceLoader resourcesLoader;
28 | @Mock Resource resource;
29 | @Mock MailetConfig config;
30 | @Mock MailetContext context;
31 |
32 | @Before
33 | public void setUp() throws MessagingException, UnknownHostException {
34 | mailet = new FileDKIMSign();
35 | mailet.setResourceLoader(resourcesLoader);
36 | when(resourcesLoader.getResource(privateKeyFile)).thenReturn(resource);
37 |
38 | when(config.getMailetContext()).thenReturn(context);
39 | }
40 |
41 | @Test(expected = MessagingException.class)
42 | public void fileParamNotSet_shouldThrowException() throws MessagingException {
43 | mailet.init(config);
44 | }
45 |
46 | @Test(expected = MessagingException.class)
47 | public void resourceDoesNotExist_shouldThrowException() throws MessagingException {
48 | when(config.getInitParameter(FileDKIMSign.PRIVATE_KEY_FILE_PARAM)).thenReturn(privateKeyFile);
49 | when(resource.exists()).thenReturn(false);
50 |
51 | mailet.init(config);
52 | }
53 |
54 | @Test(expected = MessagingException.class)
55 | public void privateKeyNotValid_shouldThrowException() throws MessagingException, IOException {
56 | when(config.getInitParameter(FileDKIMSign.PRIVATE_KEY_FILE_PARAM)).thenReturn(privateKeyFile);
57 | when(resource.exists()).thenReturn(true);
58 | when(resource.getInputStream()).thenReturn(new ByteArrayInputStream("not a valid private key".getBytes()));
59 |
60 | mailet.init(config);
61 | }
62 |
63 | @Test
64 | public void privateKeyValid_everythingEndsFine() throws MessagingException, IOException {
65 | when(config.getInitParameter(FileDKIMSign.PRIVATE_KEY_FILE_PARAM)).thenReturn(privateKeyFile);
66 | when(resource.exists()).thenReturn(true);
67 | when(resource.getInputStream()).thenReturn(new ByteArrayInputStream(getPrivateKeyByteArray()));
68 |
69 | mailet.init(config);
70 | }
71 |
72 | private byte[] getPrivateKeyByteArray() {
73 | return ("-----BEGIN RSA PRIVATE KEY-----\n" +
74 | "MIICXQIBAAKBgQDMiitd8SM3uN+c8a/73ESdTrcISLAF306hHvQxglBC4/WPIdvr\n" +
75 | "fTURDPeSVPNZ6bjP6sVPy/VsaFfAwF1b6ZxF7PEXlK8gYp6E8lkGSWI7y722aGzJ\n" +
76 | "5CIu6DM8NtHmXlAs45vEX4poX0pfeMVqNakyxa0pm8tAFe0ewzW8pblEtQIDAQAB\n" +
77 | "AoGADO4jJbIrxscCI9rHhEV9dPBX88cckZJ3Vwos58BUMJZWnLDIRU/J/gTy1aZX\n" +
78 | "J/T1gPdXd97t6eeCvKWsgTX4cfoxqgfYdTW2MoZuQWDpS6xhKjDEHI0Q4M0wta34\n" +
79 | "j3pLVRlRk3+TCLVOjE1ZjJ1Hd+Syn8HodRBvf5x13hVeZPUCQQD2Ac/eUIATd2BL\n" +
80 | "biayEmVvZF87ps3ej67+JY4d/aQuce60dhtCCpBwfwFqTrDGOWMOo539nEUK1B8D\n" +
81 | "etyOe3jHAkEA1NklI8VxZjzRMgXDprfLoRIPcJnobE3bq2120FVfu2dlAq19DIko\n" +
82 | "Dfume1lAlS+Zk2npQpiVR73avRrtPNeyowJAQlT0vqYIEreigFRAHM23ChUPVJ9C\n" +
83 | "bVtivOZVbqLAjUFtMr2R1fnRPnQQZqC3K4u3uO/HHuXu+998SUzsgYKrawJBAJfs\n" +
84 | "bj/8HBb3bfIgfygupB/RvkeG84jqgdL4jQfjCDPBdy3UGx+pfneMmaYNbLWPhjTc\n" +
85 | "Meyg8FyGvOyhnZgB9bUCQQC45QKz2CYbtJKxMGHzuzaIgyPrSsqMDHR8Gj0uifyi\n" +
86 | "o8PZOBroz7zUJr11oQb80PuGPKYeSKlCeCGmpPZ0ffFG\n" +
87 | "-----END RSA PRIVATE KEY-----").getBytes();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/forward-cat-website/app/models/StatsRepository.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import com.avaje.ebean.Ebean;
4 | import com.avaje.ebean.FutureRowCount;
5 | import com.forwardcat.common.ProxyMail;
6 | import com.forwardcat.common.User;
7 | import com.google.common.base.Strings;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import redis.clients.jedis.Jedis;
11 | import redis.clients.jedis.JedisPool;
12 | import redis.clients.jedis.Pipeline;
13 | import redis.clients.jedis.Response;
14 |
15 | import javax.inject.Inject;
16 |
17 | import static com.forwardcat.common.RedisKeys.*;
18 | import static models.JedisHelper.returnJedisIfNotNull;
19 | import static models.JedisHelper.returnJedisOnException;
20 |
21 | public class StatsRepository {
22 |
23 | private static final Logger LOGGER = LoggerFactory.getLogger(StatsRepository.class.getName());
24 | private final JedisPool jedisPool;
25 |
26 | @Inject
27 | public StatsRepository(JedisPool jedisPool) {
28 | this.jedisPool = jedisPool;
29 | }
30 |
31 | public void incrementCounter(String counter) {
32 | Jedis jedis = null;
33 | try {
34 | jedis = jedisPool.getResource();
35 | jedis.incr(counter);
36 | } catch (Exception ex) {
37 | LOGGER.error("Error while connecting to Redis", ex);
38 | returnJedisOnException(jedisPool, jedis, ex);
39 | jedis = null;
40 | } finally {
41 | returnJedisIfNotNull(jedisPool, jedis);
42 | }
43 | }
44 |
45 | public StatsCounters getStats(){
46 | Jedis jedis = null;
47 | StatsCounters counters = null;
48 | try {
49 | jedis = jedisPool.getResource();
50 |
51 | Pipeline pipeline = jedis.pipelined();
52 |
53 | FutureRowCount activeUsersRs = Ebean.find(User.class).findFutureRowCount();
54 | FutureRowCount activeProxiesRs = Ebean.find(ProxyMail.class).findFutureRowCount();
55 | Response emailsBlockedRs = pipeline.get(EMAILS_BLOCKED_COUNTER);
56 | Response emailsForwardedRs = pipeline.get(EMAILS_FORWARDED_COUNTER);
57 | Response proxiesActivatedRs = pipeline.get(PROXIES_ACTIVATED_COUNTER);
58 | Response spammerProxiesBlockedRs = pipeline.get(SPAMMER_PROXIES_BLOCKED_COUNTER);
59 |
60 | pipeline.sync();
61 |
62 | counters = new StatsCounters(
63 | activeUsersRs.get(),
64 | activeProxiesRs.get(),
65 | toInt(emailsBlockedRs.get()),
66 | toInt(emailsForwardedRs.get()),
67 | toInt(proxiesActivatedRs.get()),
68 | toInt(spammerProxiesBlockedRs.get()));
69 |
70 | } catch (Exception ex) {
71 | LOGGER.error("Error while connecting to Redis", ex);
72 | returnJedisOnException(jedisPool, jedis, ex);
73 | jedis = null;
74 | } finally {
75 | returnJedisIfNotNull(jedisPool, jedis);
76 | }
77 | return counters;
78 | }
79 |
80 | private int toInt(String str) {
81 | if (Strings.isNullOrEmpty(str)) {
82 | return 0;
83 | }
84 | return Integer.parseInt(str);
85 | }
86 |
87 | public static class StatsCounters {
88 | public final int activeUsers;
89 | public final int activeProxies;
90 | public final int emailsBlocked;
91 | public final int emailsForwarded;
92 | public final int proxiesActivated;
93 | public final int spammerProxiesBlocked;
94 |
95 | public StatsCounters(int activeUsers, int activeProxies, int emailsBlocked, int emailsForwarded, int
96 | proxiesActivated, int spammerProxiesBlocked) {
97 | this.activeUsers = activeUsers;
98 | this.activeProxies = activeProxies;
99 | this.emailsBlocked = emailsBlocked;
100 | this.emailsForwarded = emailsForwarded;
101 | this.proxiesActivated = proxiesActivated;
102 | this.spammerProxiesBlocked = spammerProxiesBlocked;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/forward-cat-website/test/controllers/AddProxyTest.java:
--------------------------------------------------------------------------------
1 | package controllers;
2 |
3 | import com.forwardcat.common.ProxyMail;
4 | import com.forwardcat.common.User;
5 | import com.google.inject.AbstractModule;
6 | import models.MailSender;
7 | import models.Repository;
8 | import org.apache.mailet.MailAddress;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.runners.MockitoJUnitRunner;
13 | import play.mvc.Result;
14 | import play.test.FakeRequest;
15 |
16 | import javax.mail.internet.AddressException;
17 | import java.io.IOException;
18 | import java.util.Date;
19 | import java.util.Optional;
20 |
21 | import static controllers.TestUtils.toMailAddress;
22 | import static controllers.TestUtils.whenAddressExistsReturn;
23 | import static java.util.Optional.empty;
24 | import static org.hamcrest.Matchers.is;
25 | import static org.junit.Assert.assertThat;
26 | import static org.mockito.Matchers.anyString;
27 | import static org.mockito.Mockito.*;
28 | import static play.test.Helpers.*;
29 |
30 | @RunWith(MockitoJUnitRunner.class)
31 | public class AddProxyTest extends PlayTest {
32 |
33 | MailAddress proxyMail = toMailAddress("test@forward.cat");
34 |
35 | @Mock MailSender mailSender;
36 | @Mock Repository repository;
37 |
38 | @Override
39 | public AbstractModule getModule() throws IOException, AddressException {
40 | return new AbstractModule() {
41 | @Override
42 | protected void configure() {
43 | bind(Repository.class).toInstance(repository);
44 | bind(MailSender.class).toInstance(mailSender);
45 | }
46 | };
47 | }
48 |
49 | @Test
50 | public void wrongEmail_sendBadRequest() throws Exception {
51 | Result route = route(request("test", "not an address"));
52 | assertThat(status(route), is(BAD_REQUEST));
53 | }
54 |
55 | @Test
56 | public void wrongUsername_sendBadRequest() throws Exception {
57 | Result route = route(request("not valid", "user@mail.com"));
58 | assertThat(status(route), is(BAD_REQUEST));
59 | }
60 |
61 | @Test
62 | public void chainedProxy_sendBadRequest() throws Exception {
63 | Result route = route(request("test", "email@forward.cat"));
64 | assertThat(status(route), is(BAD_REQUEST));
65 | }
66 |
67 | @Test
68 | public void proxyAlreadyExists_sendBadRequest() throws Exception {
69 | whenAddressExistsReturn(repository, proxyMail, true);
70 |
71 | Result route = route(request("test", "user@mail.com"));
72 | assertThat(status(route), is(BAD_REQUEST));
73 | }
74 |
75 | @Test(expected = RuntimeException.class)
76 | public void connectionError_sendBadRequest() throws Exception {
77 | when(repository.proxyExists(proxyMail)).thenThrow(new RuntimeException());
78 |
79 | Result route = route(request("test", "user@mail.com"));
80 | assertThat(status(route), is(BAD_REQUEST));
81 | }
82 |
83 | @Test
84 | public void tooManyProxies_sendBadRequest() throws Exception {
85 | ProxyMail[] proxies = {mock(ProxyMail.class), mock(ProxyMail.class), mock(ProxyMail.class)};
86 | User user = User.create(toMailAddress("user@mail.com"), new Date(), proxies);
87 | whenAddressExistsReturn(repository, proxyMail, false);
88 | when(repository.getUser(toMailAddress("user@mail.com"))).thenReturn(Optional.of(user));
89 |
90 | Result route = route(request("test", "user@mail.com"));
91 | assertThat(status(route), is(BAD_REQUEST));
92 | }
93 |
94 | @Test
95 | public void everythingFine_sendMailAndGoodResponse() throws Exception {
96 | whenAddressExistsReturn(repository, proxyMail, false);
97 | when(repository.getUser(toMailAddress("user@mail.com"))).thenReturn(empty());
98 |
99 | Result route = route(request("test", "user@mail.com"));
100 | assertThat(status(route), is(OK));
101 |
102 | verify(repository).save(any(User.class));
103 | verify(mailSender).sendHtmlMail(any(MailAddress.class), anyString(), anyString());
104 | }
105 |
106 | private FakeRequest request(String proxy, String email) {
107 | return fakeRequest(GET, "/add?proxy=" + proxy + "&email=" + email);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-james/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | com.forwardcat
9 | parent
10 | 1.0-SNAPSHOT
11 |
12 | james
13 | 1.0-SNAPSHOT
14 | Forward Cat - James
15 |
16 |
17 | 3.0.0-beta5-SNAPSHOT
18 |
19 |
20 |
21 |
22 | com.forwardcat
23 | common
24 | 1.0-SNAPSHOT
25 |
26 |
27 | org.apache.james
28 | james-server-mailets
29 | ${james.server.version}
30 | provided
31 |
32 |
33 | org.apache.james
34 | james-server-core
35 | ${james.server.version}
36 | provided
37 |
38 |
39 |
40 | org.apache.james.jdkim
41 | apache-jdkim-library
42 | 0.2
43 |
44 |
45 | org.apache.james.jdkim
46 | apache-jdkim-mailets
47 | 0.2
48 |
49 |
50 | org.springframework
51 | spring-context
52 | 3.1.0.RELEASE
53 | provided
54 |
55 |
56 |
57 | org.hamcrest
58 | hamcrest-all
59 | test
60 | 1.3
61 |
62 |
63 | org.avaje.ebeanorm
64 | avaje-ebeanorm
65 | 3.3.1
66 |
67 |
68 | postgresql
69 | postgresql
70 | 9.1-901-1.jdbc4
71 |
72 |
73 | org.slf4j
74 | slf4j-api
75 | 1.7.12
76 |
77 |
78 |
80 | redis.clients
81 | jedis
82 | 2.1.0
83 | jar
84 |
85 |
86 |
88 | commons-configuration
89 | commons-configuration
90 | 1.9
91 |
92 |
93 |
94 | junit
95 | junit
96 | 4.11
97 | test
98 |
99 |
100 |
101 | org.mockito
102 | mockito-all
103 | 1.9.5
104 | test
105 |
106 |
107 |
108 |
109 |
110 |
111 | org.apache.maven.plugins
112 | maven-shade-plugin
113 | 1.7.1
114 |
115 |
116 | package
117 |
118 | shade
119 |
120 |
121 | ${project.artifactId}
122 | false
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/forward-cat-website/test/models/TestHttpRequest.java:
--------------------------------------------------------------------------------
1 | package models;
2 |
3 | import play.api.http.MediaRange;
4 | import play.i18n.Lang;
5 | import play.mvc.Http;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | public class TestHttpRequest extends Http.Request {
11 |
12 | private Http.RequestBody body;
13 | private String uri;
14 | private String method = "GET";
15 | private String version = "HTTP/1.1";
16 | private String remoteAddress = "127.0.0.1";
17 | private boolean secure = false;
18 | private String host;
19 | private String path;
20 | private List acceptLanguages;
21 | private List accept;
22 | private List acceptedTypes;
23 | private boolean accepts;
24 | private Map queryString;
25 | private Http.Cookies cookies;
26 | private Map headers;
27 |
28 | @Override
29 | public Http.RequestBody body() {
30 | return body;
31 | }
32 |
33 | public TestHttpRequest setBody(Http.RequestBody body) {
34 | this.body = body;
35 | return this;
36 | }
37 |
38 | @Override
39 | public String uri() {
40 | return uri;
41 | }
42 |
43 | public TestHttpRequest setUri(String uri) {
44 | this.uri = uri;
45 | return this;
46 | }
47 |
48 | @Override
49 | public String method() {
50 | return method;
51 | }
52 |
53 | public TestHttpRequest setMethod(String method) {
54 | this.method = method;
55 | return this;
56 | }
57 |
58 | @Override
59 | public String version() {
60 | return version;
61 | }
62 |
63 | public TestHttpRequest setVersion(String version) {
64 | this.version = version;
65 | return this;
66 | }
67 |
68 | @Override
69 | public String remoteAddress() {
70 | return remoteAddress;
71 | }
72 |
73 | public TestHttpRequest setRemoteAddress(String remoteAddress) {
74 | this.remoteAddress = remoteAddress;
75 | return this;
76 | }
77 |
78 | @Override
79 | public boolean secure() {
80 | return secure;
81 | }
82 |
83 | public TestHttpRequest setSecure(boolean secure) {
84 | this.secure = secure;
85 | return this;
86 | }
87 |
88 | @Override
89 | public String host() {
90 | return host;
91 | }
92 |
93 | public TestHttpRequest setHost(String host) {
94 | this.host = host;
95 | return this;
96 | }
97 |
98 | @Override
99 | public String path() {
100 | return path;
101 | }
102 |
103 | public TestHttpRequest setPath(String path) {
104 | this.path = path;
105 | return this;
106 | }
107 |
108 | @Override
109 | public List acceptLanguages() {
110 | return acceptLanguages;
111 | }
112 |
113 | public TestHttpRequest setAcceptLanguages(List acceptLanguages) {
114 | this.acceptLanguages = acceptLanguages;
115 | return this;
116 | }
117 |
118 | @Override
119 | public List accept() {
120 | return accept;
121 | }
122 |
123 | public TestHttpRequest setAccept(List accept) {
124 | this.accept = accept;
125 | return this;
126 | }
127 |
128 | @Override
129 | public List acceptedTypes() {
130 | return acceptedTypes;
131 | }
132 |
133 | public TestHttpRequest setAcceptedTypes(List acceptedTypes) {
134 | this.acceptedTypes = acceptedTypes;
135 | return this;
136 | }
137 |
138 | @Override
139 | public boolean accepts(String s) {
140 | return accepts;
141 | }
142 |
143 | public TestHttpRequest setAccepts(boolean accepts) {
144 | this.accepts = accepts;
145 | return this;
146 | }
147 |
148 | @Override
149 | public Map queryString() {
150 | return queryString;
151 | }
152 |
153 | public TestHttpRequest setQueryString(Map queryString) {
154 | this.queryString = queryString;
155 | return this;
156 | }
157 |
158 | @Override
159 | public Http.Cookies cookies() {
160 | return cookies;
161 | }
162 |
163 | public TestHttpRequest setCookies(Http.Cookies cookies) {
164 | this.cookies = cookies;
165 | return this;
166 | }
167 |
168 | @Override
169 | public Map headers() {
170 | return headers;
171 | }
172 |
173 | public TestHttpRequest setHeaders(Map headers) {
174 | this.headers = headers;
175 | return this;
176 | }
177 |
178 | @Override
179 | public String toString() {
180 | return toString(this);
181 | }
182 |
183 | public static String toString(Http.Request request) {
184 | return "Http.Request{" +
185 | "body=" + request.body() +
186 | ", uri='" + request.uri() + '\'' +
187 | ", method='" + request.method() + '\'' +
188 | ", version='" + request.version() + '\'' +
189 | ", remoteAddress='" + request.remoteAddress() + '\'' +
190 | ", secure=" + request.secure() +
191 | ", host='" + request.host() + '\'' +
192 | ", path='" + request.path() + '\'' +
193 | ", acceptLanguages=" + request.acceptLanguages() +
194 | ", accept=" + request.accept() +
195 | ", acceptedTypes=" + request.acceptedTypes() +
196 | ", queryString=" + request.queryString() +
197 | ", cookies=" + request.cookies() +
198 | ", headers=" + request.headers() +
199 | '}';
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/forward-cat/forward-cat-james/src/test/java/com/forwardcat/james/ForwardMailetTest.java:
--------------------------------------------------------------------------------
1 | package com.forwardcat.james;
2 |
3 | import com.avaje.ebean.EbeanServer;
4 | import com.forwardcat.common.ProxyMail;
5 | import com.forwardcat.common.User;
6 | import com.google.common.base.Throwables;
7 | import org.apache.james.core.MailImpl;
8 | import org.apache.james.dnsservice.api.DNSService;
9 | import org.apache.mailet.Mail;
10 | import org.apache.mailet.MailAddress;
11 | import org.apache.mailet.MailetConfig;
12 | import org.apache.mailet.MailetContext;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 | import org.junit.runner.RunWith;
16 | import org.mockito.ArgumentCaptor;
17 | import org.mockito.Captor;
18 | import org.mockito.Mock;
19 | import org.mockito.runners.MockitoJUnitRunner;
20 | import redis.clients.jedis.Jedis;
21 | import redis.clients.jedis.JedisPool;
22 |
23 | import javax.mail.MessagingException;
24 | import javax.mail.Session;
25 | import javax.mail.internet.MimeMessage;
26 | import javax.mail.internet.MimeMultipart;
27 | import java.io.Serializable;
28 | import java.net.InetAddress;
29 | import java.net.UnknownHostException;
30 | import java.util.Collections;
31 | import java.util.Date;
32 |
33 | import static org.hamcrest.CoreMatchers.is;
34 | import static org.hamcrest.CoreMatchers.notNullValue;
35 | import static org.junit.Assert.assertThat;
36 | import static org.mockito.Mockito.*;
37 |
38 | @RunWith(MockitoJUnitRunner.class)
39 | public class ForwardMailetTest {
40 |
41 | ForwardMailet mailet = new ForwardMailet();
42 |
43 | @Captor ArgumentCaptor mailCaptor;
44 | @Mock ForwardCatResourcesProvider resourcesProvider;
45 | @Mock JedisPool pool;
46 | @Mock Jedis jedis;
47 | @Mock EbeanServer ebeanServer;
48 | @Mock MailetContext context;
49 |
50 | String prefix = "[Forward.cat] ";
51 | MailAddress recipient;
52 | MailAddress userAddress;
53 |
54 | @Before
55 | public void setUp() throws MessagingException, UnknownHostException {
56 | when(pool.getResource()).thenReturn(jedis);
57 | when(resourcesProvider.getPool()).thenReturn(pool);
58 | when(resourcesProvider.getEbeanServer()).thenReturn(ebeanServer);
59 |
60 | DNSService dns = mock(DNSService.class);
61 | when(dns.getLocalHost()).thenReturn(InetAddress.getLocalHost());
62 |
63 | mailet.setDNSService(dns);
64 | mailet.setResourceProvider(resourcesProvider);
65 |
66 | recipient = new MailAddress("recipient@forward.cat");
67 | userAddress = new MailAddress("user@mail.com");
68 | MailetConfig config = mock(MailetConfig.class);
69 | when(config.getInitParameter("sender")).thenReturn("no-reply@forward.cat");
70 | when(config.getInitParameter("prefix")).thenReturn(prefix);
71 | when(config.getInitParameter("static")).thenReturn("true");
72 | when(config.getMailetContext()).thenReturn(context);
73 | mailet.init(config);
74 | }
75 |
76 | @Test
77 | public void proxyNotFound_shouldBounceMail() throws MessagingException {
78 | Mail mail = newMail();
79 | mailet.service(mail);
80 |
81 | verify(pool, atLeastOnce()).returnResource(jedis);
82 | assertThatIsBounced(mail);
83 | assertNoMailHasBeenSent();
84 | }
85 |
86 | @Test
87 | public void proxyNotFoundAndSenderIgnorable_shouldIgnoreMail() throws MessagingException {
88 | Mail mail = newMail("do-not-reply@mail.com");
89 | mailet.service(mail);
90 |
91 | assertThatIsIgnored(mail);
92 | assertNoMailHasBeenSent();
93 | }
94 |
95 | @Test
96 | public void proxyNotFoundAndSenderIsFbUpdate_shouldIgnoreMail() throws MessagingException {
97 | Mail mail = newMail("notification+zj4za=zy2==c@facebookmail.com");
98 | mailet.service(mail);
99 |
100 | assertThatIsIgnored(mail);
101 | assertNoMailHasBeenSent();
102 | }
103 |
104 | @Test
105 | public void proxyNotActive_shouldBounceMail() throws MessagingException {
106 | ProxyMail proxy = proxy();
107 | when(ebeanServer.find(ProxyMail.class, recipient.toString())).thenReturn(proxy);
108 |
109 | Mail mail = newMail();
110 | mailet.service(mail);
111 |
112 | assertThatIsBounced(mail);
113 | assertNoMailHasBeenSent();
114 | }
115 |
116 | @Test
117 | public void proxyBlocked_shouldSwallowMail() throws MessagingException {
118 | ProxyMail proxy = proxy();
119 | proxy.activate();
120 | proxy.block();
121 | when(ebeanServer.find(ProxyMail.class, recipient.toString())).thenReturn(proxy);
122 |
123 | Mail mail = newMail();
124 | mailet.service(mail);
125 |
126 | assertThatIsIgnored(mail);
127 | assertNoMailHasBeenSent();
128 | }
129 |
130 | @Test
131 | public void proxyActive_shouldForwardMail() throws MessagingException {
132 | ProxyMail proxy = proxy();
133 | proxy.activate();
134 | when(ebeanServer.find(ProxyMail.class, recipient.toString())).thenReturn(proxy);
135 |
136 | Mail mail = newMail();
137 | mailet.service(mail);
138 |
139 | assertThat(mail.getState(), is(Mail.GHOST));
140 | verify(context).sendMail(mailCaptor.capture());
141 |
142 | Mail forwardMail = mailCaptor.getValue();
143 | assertThat(forwardMail, notNullValue());
144 | }
145 |
146 | private ProxyMail proxy() {
147 | ProxyMail proxy = ProxyMail.create(recipient, userAddress, daysOffset(-2), daysOffset(5), "en");
148 | proxy.setUser(User.create(userAddress, new Date(), proxy));
149 | return proxy;
150 | }
151 |
152 | private Mail newMail() throws MessagingException {
153 | return newMail("sender@mail.com");
154 | }
155 |
156 | private Mail newMail(String sender) throws MessagingException {
157 | MimeMessage message = new MimeMessage(Session.getDefaultInstance(System.getProperties()));
158 | message.setContent(new MimeMultipart());
159 | message.setSubject("Some subject");
160 |
161 | return new MailImpl("test email", new MailAddress(sender), Collections.singletonList(recipient), message);
162 | }
163 |
164 | private void assertThatIsBounced(Mail mail) {
165 | Serializable attr = mail.getAttribute(ForwardMailet.BOUNCE_ATTRIBUTE);
166 | assertThat(attr, is("true"));
167 | }
168 |
169 | private void assertThatIsIgnored(Mail mail) {
170 | String attr = mail.getState();
171 | assertThat(attr, is(Mail.GHOST));
172 | }
173 |
174 | private void assertNoMailHasBeenSent() {
175 | try {
176 | verify(context, never()).sendMail(any(Mail.class));
177 | } catch (MessagingException e) {
178 | Throwables.propagate(e);
179 | }
180 | }
181 |
182 | private Date daysOffset(int days) {
183 | return new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * days);
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/forward-cat-website/app/views/forward.scala.html:
--------------------------------------------------------------------------------
1 | @import models.DomainManager.buildUrl
2 | @(lang: Lang)
3 |
4 |
5 |
6 |
7 |
8 | @Messages("index.title")(lang)
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
39 |
40 |
41 |
42 |
43 | @navbar(lang)
44 |
45 |
46 |
47 |
@Messages("index.header")(lang)
48 |
@Messages("index.subheader")(lang)
49 |
50 |
51 |
87 |
88 |
89 |
90 |
@Messages("index.about.header")(lang)
91 |
@Messages("index.about.content")(lang)
92 |
@Html(Messages("index.about.spam")(lang))
93 |
@Html(Messages("index.about.contact")(lang))
94 |
95 |
96 |
@Messages("index.about.section1.title")(lang)
97 |
@Messages("index.about.section1.content")(lang)
98 |
99 |
@Messages("index.about.section2.title")(lang)
100 |
@Messages("index.about.section2.content")(lang)
101 |
102 |
103 |
@Messages("index.about.section3.title")(lang)
104 |
@Messages("index.about.section3.content")(lang)
105 |
106 |
@Messages("index.about.section4.title")(lang)
107 |
@Messages("index.about.section4.content")(lang)
108 |
109 |
110 |
111 |
112 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/forward-cat-website/conf/messages.en:
--------------------------------------------------------------------------------
1 | navbar.faq = Frequent Questions
2 | navbar.report = Report User
3 | navbar.contact = Contact
4 |
5 | index.title = Forward Cat - Temporary email addresses
6 | index.description = Get a temporary email address that automatically forwards to your real email address. It''s fast, free and anonymous!
7 |
8 | index.header = Get a temporary email address
9 | index.subheader = It''s fast, free and anonymous
10 | index.form.address.label = Your email address:
11 | index.form.address.title = Your email address
12 | index.form.address.content = Insert your real email address. We need it to forward you your emails.
13 | index.form.address.placeholder = Email
14 | index.form.proxy.label = Temporary address:
15 | index.form.proxy.title = Username
16 | index.form.proxy.content = This will be your username
17 | index.form.proxy.placeholder = example
18 | index.form.lang = en
19 | index.form.submit = Let''s do this!
20 | index.about.header = About
21 | index.about.content = Forward Cat is a service that allows you to quickly create a temporary email address that automatically forwards to your real email address.
22 | index.about.spam = Important: we reserve the right to cancel or block your account without any further notice if you are found to be sending spam. If you believe someone is using this service to spam, please report it .
23 | index.about.contact = You can also contact us for any other issue.
24 | index.about.section1.title = Don''t reveal your address
25 | index.about.section1.content = Forward Cat is anonymous- give out a disposable email address when you don''t want to disclose your real address.
26 | index.about.section2.title = Avoid spam
27 | index.about.section2.content = Forward Cat provides a way to stop companies and individuals from continuing to send you emails after you''ve concluded your desired transaction. Keep your address out of sight!
28 | index.about.section3.title = When to use Forward Cat
29 | index.about.section3.content = Use a temporary email address to register for websites that ask for your details in exchange for access, or to sell something on a classifieds site.
30 | index.about.section4.title = Totally private
31 | index.about.section4.content = Forward Cat doesn''t store your emails, and they''re not visible to anyone except you. Your temporary email address will be totally deactivated after its lifespan comes to an end.
32 | index.footer.by = Made by Dani Solà . Special thanks to Sophia Casas . Illustrations by Raúl Vélez .
33 | index.footer.code = Code licensed under the Apache License v2.0 and hosted on GitHub .
34 | index.footer.langs = Other languages: Català - Español
35 |
36 | email_sent.title = Activate your temporary email address!
37 | email_sent.header = Activate your temporary email address!
38 | email_sent.content1 = We have sent you an email, activate your new temporary email address within the next 30 minutes to start using it.
39 | email_sent.content2 = If you don''t see the email, please check your spam folder.
40 | email_sent.back = Back to Forward Cat
41 |
42 | contact.title = How can we help?
43 | contact.header = How can we help?
44 | contact.content = Feel free to contact us about anything!
45 | contact.email = Your email:
46 | contact.message = Message:
47 | contact.submit = Submit
48 |
49 | contact_sent.title = Message sent
50 | contact_sent.header = Message sent
51 | contact_sent.content = Your message has been sent, we'll come back to you shortly.
52 | contact_sent.thanks = Thanks for contacting us!
53 |
54 | report_user.title = Report abuse
55 | report_user.header = Report abuse
56 | report_proxy.content = If you believe that someone is abusing Forward Cat to use it for spamming or any other fraudulent purpose, please fill the following form:
57 | report_user.user = Reported user:
58 | report_user.reason = Content of the message:
59 | report_user.report = Report
60 |
61 | user_reported.title = User reported
62 | user_reported.header = User reported
63 | user_reported.content = The user has been reported and will be investigated soon. However, bear in mind that even if we block his account we cannot stop him from sending more emails.
64 | user_reported.thanks = Thanks for letting us know!
65 |
66 | proxy_created_email.title = Forward Cat - Activate your temporary email address
67 | proxy_created_email.intro = The address {0} is nearly ready:
68 | proxy_created_email.activation_url = https://forward.cat/confirm?p={0}&h={1}
69 | proxy_created_email.activate = Activate temporary address
70 | proxy_created_email.deactivate = To deactivate your temporary email address at any time after it''s been activated, click here .
71 | proxy_created_email.forget_it = If you have not created this temporary email address, just delete this mail and forget about it :)
72 | proxy_created_email.thanks = Thanks for using Forward Cat!
73 |
74 | proxy_created.title = Temporary address activated
75 | proxy_created.header = Congratulations!
76 | proxy_created.content1 = {0} will be active for a week.
77 | proxy_created.content2 = If you want to deactivate it earlier, just click on the deactivation link in the confirmation email you just received.
78 | proxy_created.content3 = We hope you find Forward Cat useful!
79 |
80 | confirm_deletion.title = Deactivating temporary email address
81 | confirm_deletion.header = Deactivating temporary email address
82 | confirm_deletion.content1 = {0} is set to be active until {1}.
83 | confirm_deletion.content2 = Do you want to delete {0}, effective now?
84 | confirm_deletion.deactivate = Deactivate it!
85 |
86 | proxy_deleted.title = Temporary email address deactivated
87 | proxy_deleted.header = Temporary email address deactivated
88 | proxy_deleted.content1 = You won''t receive any more email from this temporary email address.
89 | proxy_deleted.content2 = We hope Forward Cat has been useful!
90 | proxy_deleted.back = Back to Forward Cat
91 |
92 | proxy_expiring_email.title = Your temporary email address will expire soon
93 | proxy_expiring_email.content1 = The temporary address {0} will expire on {1}
94 | proxy_expiring_email.content2 = Click here to extend its life for a few more days.
95 | proxy_expiring_email.content3 = To deactivate your temporary email address whenever you want, click here .
96 | proxy_expiring_email.content4 = Thanks for using Forward Cat!
97 |
98 | proxy_extended.title = Temporary email address successfully extended
99 | proxy_extended.header = Temporary email address successfully extended
100 | proxy_extended.content1 = Your temporary address {0} will be active until {1}.
101 | proxy_extended.content2 = If you want to deactivate it earlier, just click on the deactivation link in the email you just received.
102 | proxy_extended.content3 = Happy forwarding!
103 |
104 | error_page.title = Something went wrong
105 | error_page.header = Something went wrong
106 | error_page.content = We're very sorry, but we cannot fulfill your request.
107 | error_page.back = Back to Forward Cat
108 |
109 | error.too_many_proxies = To fight against spammers, we currently allow a maximum of 3 temporary addresses. If you need more, please contact us explaining why.
110 |
111 | faq.title = Frequent questions
112 |
--------------------------------------------------------------------------------
/forward-cat-website/conf/messages.ca:
--------------------------------------------------------------------------------
1 | navbar.faq = Preguntes Freqüents
2 | navbar.report = Reportar Usuari
3 | navbar.contact = Contactar
4 |
5 | index.title = Forward Cat - Adreces temporals d''email
6 | index.description = Crea una adreça temporal d''e-mail que reenvia automàticament els correus a la teva adreça real. És gratuït, ràpid i anònim!
7 |
8 | index.header = Crea una adreça temporal d''e-mail
9 | index.subheader = És gratuït, ràpid i anònim
10 | index.form.address.label = La teva adreça:
11 | index.form.address.title = La teva adreça
12 | index.form.address.content = Introdueix la teva adreça e-mail real. La necessitem per reenviar-te els emails.
13 | index.form.address.placeholder = Adreça
14 | index.form.proxy.label = Nom d''usuari:
15 | index.form.proxy.title = Nom d''usuari
16 | index.form.proxy.content = Serà el nom d''usuari de la teva adreça temporal
17 | index.form.proxy.placeholder = exemple
18 | index.form.lang = ca
19 | index.form.submit = Som-hi!
20 | index.about.header = Quant a
21 | index.about.content = Forward Cat és un servei que permet crear fàcilment una adreça temporal d''email que reenvia automàticament els correus a la teva adreça real.
22 | index.about.spam = Important: ens reservem el dret de cancel·lar o bloquejar la teva compta si descobrim que la fas servir per enviar spam. Si creus que algú està fent servir Forward Cat per enviar spam, reporta-ho si us plau.
23 | index.about.contact = També pots contactar-nos per a qualsevol altre assumpte.
24 | index.about.section1.title = No revelis la teva adreça
25 | index.about.section1.content = Forward Cat és anònim, dóna una adreça temporal quan no vulguis revelar la teva adreça real.
26 | index.about.section2.title = Evita el correu no desitjat
27 | index.about.section2.content = Forward Cat et proporciona una eina per evitar que empreses i persones continuen enviant-te correus un cop hagueu acabat la vostra comunicació. Mantén la teva adreça oculta!
28 | index.about.section3.title = Quan utilitzar Forward Cat
29 | index.about.section3.content = Fes servir una adreça d''email temporal quan et registris a pàgines que demanen que et registris per accedir al contingut, o per vendre alguna cosa a una pàgina d''anuncis classificats.
30 | index.about.section4.title = Totalment privat
31 | index.about.section4.content = Forward Cat no guarda el teu correu, i no és visible per a ningú a part de tu. La teva adreça de correu temporal serà totalment esborrada un cop arribi a la fi de la seva durada.
32 | index.footer.by = Fet per Dani Solà . Agraïment especial a Sophia Casas . Il·lustracions fetes per Raúl Vélez .
33 | index.footer.code = Codi sota llicència Apache License v2.0 hostatjat a GitHub .
34 | index.footer.langs = Altres llengües: English - Español
35 |
36 | email_sent.title = Activeu la vostra adreça temporal!
37 | email_sent.header = Activeu la vostra adreça temporal!
38 | email_sent.content1 = T''hem enviat un email, activa l''adreça temporal en els propers 30 minuts per començar a utilitzar-la.
39 | email_sent.content2 = Si no trobes el correu, comprova que no hagi anat a parar a la carpeta de correu no desitjat.
40 | email_sent.back = Tornar a Forward Cat
41 |
42 | contact.title = Com podem ajudar?
43 | contact.header = Com podem ajudar?
44 | contact.content = No dubtis a contactar-nos pel que sigui!
45 | contact.email = El teu email:
46 | contact.message = Missatge:
47 | contact.submit = Enviar
48 |
49 | contact_sent.title = Missatge enviat
50 | contact_sent.header = Missatge enviat
51 | contact_sent.content = El teu missatge ha estat enviat i respondrem el més aviat possible.
52 | contact_sent.thanks = Gràcies per contactar-nos!
53 |
54 | report_user.title = Reportar us indegut
55 | report_user.header = Reportar us indegut
56 | report_proxy.content = Si crees que algú fa servir Forward Cat per enviar spam o per qualsevol altre ús fraudulent, omple el següent formulari si us plau:
57 | report_user.user = Usuari:
58 | report_user.reason = Contingut del mail:
59 | report_user.report = Reportar
60 |
61 | user_reported.title = Usuari reportat
62 | user_reported.header = Usuari reportat
63 | user_reported.content = L'usuari ha sigut reportat i aviat serà investigat. De totes formes, tingues en compte que encara que bloquegem la seva compta no podem evitar que segueixi enviant spam.
64 | user_reported.thanks = Gràcies per informar-nos!
65 |
66 | proxy_created_email.title = Forward Cat - Activa la teva adreça d''email temporal
67 | proxy_created_email.intro = L''adreça {0} està quasi a punt:
68 | proxy_created_email.activation_url = https://forward.cat/ca/confirm?p={0}&h={1}
69 | proxy_created_email.activate = Activar l''adreça temporal
70 | proxy_created_email.deactivate = Per desactivar l''adreça temporal en qualsevol moment, fes clic aquí .
71 | proxy_created_email.forget_it = Si no has creat aquesta adreça, només cal que eliminis aquest correu i l''oblidis :)
72 | proxy_created_email.thanks = Gràcies per utilitzar Forward Cat!
73 |
74 | proxy_created.title = Adreça temporal activada
75 | proxy_created.header = Felicitats!
76 | proxy_created.content1 = {0} estarà activa durant una setmana.
77 | proxy_created.content2 = Si vols desactivar-la abans, només cal que cliquis a l''enllaç corresponent del correu que acabes de rebre.
78 | proxy_created.content3 = Esperem que Forward Cat et sigui útil!
79 |
80 | confirm_deletion.title = Elimina l''adreça temporal
81 | confirm_deletion.header = Vols eliminar la teva adreça temporal?
82 | confirm_deletion.content1 = {0} estarà activa fins al {1}.
83 | confirm_deletion.content2 = Vols desactivar i eliminar {0} ara?
84 | confirm_deletion.deactivate = Eliminar-la!
85 |
86 | proxy_deleted.title = L''adreça temporal ha sigut eliminada
87 | proxy_deleted.header = L''adreça temporal ha sigut eliminada
88 | proxy_deleted.content1 = No rebràs més correus d''aquesta adreça temporal.
89 | proxy_deleted.content2 = Esperem que Forward Cat t''hagi sigut útil!
90 | proxy_deleted.back = Tornar a Forward Cat
91 |
92 | proxy_expiring_email.title = La teva adreça temporal està a punt de ser eliminada
93 | proxy_expiring_email.content1 = L''adreça temporal {0} serà eliminada el {1}
94 | proxy_expiring_email.content2 = Fes clic aquí per mantenir-la activa uns dies més.
95 | proxy_expiring_email.content3 = Per desactivar l''adreça temporal en qualsevol moment, fes clic aquí .
96 | proxy_expiring_email.content4 = Gràcies per utilitzar Forward Cat!
97 |
98 | proxy_extended.title = Duració de l''adreça temporal correctament perllongada
99 | proxy_extended.header = Duració de l''adreça temporal correctament perllongada
100 | proxy_extended.content1 = L''adreça temporal {0} estarà activa fins a {1}.
101 | proxy_extended.content2 = Si vols desactivar-la abans, només cal que cliquis a l''enllaç corresponent del correu que acabes de rebre.
102 | proxy_extended.content3 = Que tinguis un bon dia!
103 |
104 | error_page.title = Alguna cosa ha fallat
105 | error_page.header = Alguna cosa ha fallat
106 | error_page.content = Ho sentim molt, però la petició no s'ha pogut portar a terme.
107 | error_page.back = Tornar a Forward Cat
108 |
109 | error.too_many_proxies = Per lluitar contra el correu indesitjat, actualment només permetem un màxim de 3 correus temporals. Si en necessites més, envia un correu explicant el perquè.
110 |
111 | faq.title = Preguntes freqüents
112 |
--------------------------------------------------------------------------------
/forward-cat-website/conf/messages.es:
--------------------------------------------------------------------------------
1 | navbar.faq = Preguntas Frequentes
2 | navbar.report = Reportar Usuario
3 | navbar.contact = Contactar
4 |
5 | index.title = Forward Cat - Direcciones temporales de email
6 | index.description = Crea una dirección temporal de email que reenvía automáticamente el correo a tu dirección real. ¡Es rápido, gratuito y anónimo!
7 |
8 | index.header = Crea una dirección temporal de correo
9 | index.subheader = Es rápido, gratuito y anónimo
10 | index.form.address.label = Tu dirección:
11 | index.form.address.title = Tu dirección
12 | index.form.address.content = Introduce tu dirección de correo real. La necesitamos para reenviarte tus emails.
13 | index.form.address.placeholder = Dirección
14 | index.form.proxy.label = Nombre de usuario:
15 | index.form.proxy.title = Nombre de usuario
16 | index.form.proxy.content = Será el nombre de usuario de tu dirección de correo temporal
17 | index.form.proxy.placeholder = ejemplo
18 | index.form.lang = es
19 | index.form.submit = ¡Adelante!
20 | index.about.header = Acerca de
21 | index.about.content = Forward Cat es un servicio permite crear fácilmente una dirección de correo temporal que te reenvía automáticamente el correo a tu dirección real.
22 | index.about.spam = Importante: nos reservamos el derecho de cancelar o bloquear tu cuenta si descubrimos que la usas para enviar spam. Si crees que alguien usa Forward Cat para enviar spam, repórtalo por favor.
23 | index.about.contact = También puedes contactarnos para cualquier otro asunto.
24 | index.about.section1.title = No reveles tu dirección
25 | index.about.section1.content = Forward Cat es anónimo, así puedes dar una dirección temporal cuando no quieres revelar tu dirección real.
26 | index.about.section2.title = Evita el correo no deseado
27 | index.about.section2.content = Forward Cat te proporciona una herramienta para evitar que empresas y personas te envíen correos una vez haya acabado vuestra transacción. ¡Mantén oculta tu dirección!
28 | index.about.section3.title = Cuando utilizar Forward Cat
29 | index.about.section3.content = Usa una dirección de email temporal cuando te registres en páginas que exigen tus datos para acceder a los contenidos, o para vender algo en una página de anuncios clasificados.
30 | index.about.section4.title = Totalmente privado
31 | index.about.section4.content = Forward Cat no guarda tus correos y no son visibles para nadie que no seas tú. Tu dirección de correo temporal será destruida completamente cuando sea desactivada.
32 | index.footer.by = Creado por Dani Solà . Agradecimiento especial a Sophia Casas . Ilustraciones realizadas por Raúl Vélez .
33 | index.footer.code = Código bajo licencia Apache License v2.0 hospedado en GitHub .
34 | index.footer.langs = Otros idiomas: Català - English
35 |
36 | email_sent.title = ¡Activa tu dirección temporal!
37 | email_sent.header = ¡Activa tu dirección temporal!
38 | email_sent.content1 = Te hemos enviado un email, activa tu dirección de correo temporal en los siguientes 30 minutos para empezar a usarla.
39 | email_sent.content2 = Si no recibes el email, comprueba que no esté en la carpeta de correo no deseado.
40 | email_sent.back = Volver a Forward Cat
41 |
42 | contact.title = Como podemos ayudar?
43 | contact.header = Como podemos ayudar?
44 | contact.content = No dudes en contactarnos por cualquier cosa!
45 | contact.email = Tu email:
46 | contact.message = Mensaje:
47 | contact.submit = Enviar
48 |
49 | contact_sent.title = Mensaje enviado
50 | contact_sent.header = Mensaje enviado
51 | contact_sent.content = Tu mensaje ha sido enviado y responderemos lo más pronto posible.
52 | contact_sent.thanks = Gracias por contactarnos!
53 |
54 | report_user.title = Reportar uso indebido
55 | report_user.header = Reportar uso indebido
56 | report_proxy.content = Si crees que alguien usa Forward Cat para enviar spam o para cualquier otro uso fraudulento, rellena el siguiente formulario por favor:
57 | report_user.user = Usuario:
58 | report_user.reason = Contenido del mail:
59 | report_user.report = Reportar
60 |
61 | user_reported.title = Usuario reportado
62 | user_reported.header = Usuario reportado
63 | user_reported.content = El usuario ha sido reportado y pronto será investigado. De todas formas, ten en cuenta que aunque bloqueemos su cuenta no podemos evitar que siga enviando spam.
64 | user_reported.thanks = Gracias por informarnos!
65 |
66 | proxy_created_email.title = Forward Cat - Activa tu dirección de correo temporal
67 | proxy_created_email.intro = La dirección {0} está quasi lista:
68 | proxy_created_email.activation_url = https://forward.cat/es/confirm?p={0}&h={1}
69 | proxy_created_email.activate = Activar la dirección temporal
70 | proxy_created_email.deactivate = Para desactivar la dirección temporal en cualquier momento, haz clic aquí .
71 | proxy_created_email.forget_it = Si no has creado esta dirección, elimina este correo y olvídalo :)
72 | proxy_created_email.thanks = ¡Gracias por usar Forward Cat!
73 |
74 | proxy_created.title = Dirección temporal activada
75 | proxy_created.header = ¡Felicidades!
76 | proxy_created.content1 = {0} estará activa durante una semana.
77 | proxy_created.content2 = Si deseas desactivarla antes, solo tienes que hacer clic en el enlace correspondiente en el email que acabas de recibir.
78 | proxy_created.content3 = ¡Esperamos que Forward Cat te resulte útil!
79 |
80 | confirm_deletion.title = Eliminar dirección temporal
81 | confirm_deletion.header = ¿Quieres eliminar tu dirección temporal?
82 | confirm_deletion.content1 = {0} estará activa hasta el {1}.
83 | confirm_deletion.content2 = ¿Quieres eliminar {0} ahora?
84 | confirm_deletion.deactivate = Elimínala!
85 |
86 | proxy_deleted.title = Dirección temporal eliminada
87 | proxy_deleted.header = Dirección temporal eliminada
88 | proxy_deleted.content1 = No recibirás más correos de esta dirección temporal.
89 | proxy_deleted.content2 = ¡Esperamos que Forward Cat te haya resultado útil!
90 | proxy_deleted.back = Volver a Forward Cat
91 |
92 | proxy_expiring_email.title = Su dirección temporal está a punto de ser desactivada
93 | proxy_expiring_email.content1 = La dirección temporal {0} será desactivada y eliminada el {1}
94 | proxy_expiring_email.content2 = Haz clic aquí para mantenerla activa unos días más.
95 | proxy_expiring_email.content3 = Para desactivar la dirección temporal en cualquier momento, haz clic aquí .
96 | proxy_expiring_email.content4 = ¡Gracias por usar Forward Cat!
97 |
98 | proxy_extended.title = Caducidad de la dirección temporal extendida
99 | proxy_extended.header = Caducidad de la dirección temporal extendida
100 | proxy_extended.content1 = La dirección temporal {0} estará activa hasta el {1}.
101 | proxy_extended.content2 = Si deseas desactivarla antes, solo tienes que hacer clic en el enlace correspondiente del email que acabas de recibir.
102 | proxy_extended.content3 = ¡Que tengas un buen día!
103 |
104 | error_page.title = Algo ha fallado
105 | error_page.header = Algo ha fallado
106 | error_page.content = Lo sentimos mucho, pero su petición no se ha podido llevar a cabo.
107 | error_page.back = Volver a Forward Cat
108 |
109 | error.too_many_proxies = Para luchar contra el correo indeseado, actualmente solo permitimos la creación de 3 direcciones temporales. Si necesitas más, envía un correo explicando el porqué.
110 |
111 | faq.title = Preguntas frequentes
112 |
--------------------------------------------------------------------------------