Skip to content

Commit

Permalink
V1.2: add log, user_agent, availability
Browse files Browse the repository at this point in the history
Add an availability controller
Add logs file and a logs controller (with password)
Add user_agent in the footer of email (with simplified)
  • Loading branch information
VictorHachard committed May 27, 2023
1 parent 5bd5600 commit 1c65320
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 7 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>com.mailit</groupId>
<artifactId>mail-it</artifactId>
<version>1.1</version>
<version>1.2</version>
<name>mail-it</name>
<description>mail-it</description>
<properties>
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/mailit/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ public class Environment {
*/
public final List<String> ACCESS_CONTROL_ALLOW_ORIGIN_URL;

/**
* This variable enable the simplified user agent string that is used to identify the client or browser.
*/
public final Boolean USER_AGENT_SIMPLIFIED;

/**
* This variable is a string that represents the password used to access /logs.
*/
public final String LOGS_PASSWORD;

/**
* This variable is a string that represents the email account that will be used to send the email message.
*/
Expand Down Expand Up @@ -58,8 +68,10 @@ public Environment(String file) throws IOException, ParseException {
result = result.replace("\n", "");

ACCESS_CONTROL_ALLOW_ORIGIN_URL = access_control_allow_origin_url;
USER_AGENT_SIMPLIFIED = jo.get("user_agent_simplified") == null || (Boolean) jo.get("user_agent_simplified");
EMAIL_USERNAME = (String) jo.get("email_username");
EMAIL_PASSWORD = (String) jo.get("email_password");
LOGS_PASSWORD = jo.get("user_agent_simplified") != null ? (String) jo.get("logs_password") : "admin";
ALIAS = alias;
STYLE_CSS = result;
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/mailit/MailItApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public static void main(String[] args) throws IOException, ParseException {
environment = new Environment(args[i + 1]);
} else if (args[i].equals("-port") && args[i + 1] != null) {
System.setProperty("server.port", args[i + 1]);
} else if (args[i].equals("-log") && args[i + 1] != null) {
System.setProperty("logging.file.name", args[i + 1]);
}
required_args.remove(args[i]);
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/mailit/controller/AvailabilityController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.mailit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;


@Controller
public class AvailabilityController {

@ResponseBody
@GetMapping("/availability")
public String checkAvailability() {
return "{\"result\": \"success\"," +
"\"description\": \"Service is available\"" +
"}";
}

}
52 changes: 47 additions & 5 deletions src/main/java/com/mailit/controller/HtmlEmailController.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Controller
public class HtmlEmailController {
Expand All @@ -34,6 +36,38 @@ public class HtmlEmailController {
@Autowired
private JavaMailSender emailSender;

private Map<String, String> analyzeUserAgent(String userAgent) {
Map<String, String> userInfo = new LinkedHashMap<>();

// Extract browser information
Pattern browserPattern = Pattern.compile(".*?(Chrome|Firefox|Safari).*?/(\\d+\\.\\d+).*");
Matcher browserMatcher = browserPattern.matcher(userAgent);
if (browserMatcher.matches()) {
String browserName = browserMatcher.group(1);
String browserVersion = browserMatcher.group(2);
userInfo.put("browser", browserName);
userInfo.put("version", browserVersion);
}

// Extract operating system information
Pattern osPattern = Pattern.compile(".*?\\((.*?)\\).*");
Matcher osMatcher = osPattern.matcher(userAgent);
if (osMatcher.matches()) {
String osInfo = osMatcher.group(1);
userInfo.put("operating system", osInfo);
}

// Extract device information
Pattern devicePattern = Pattern.compile(".*?\\((.*?)\\).*");
Matcher deviceMatcher = devicePattern.matcher(userAgent);
if (deviceMatcher.matches()) {
String deviceInfo = deviceMatcher.group(1);
userInfo.put("device", deviceInfo);
}

return userInfo;
}

private static boolean isValidEmailAddress(String email) {
boolean result = true;
try {
Expand Down Expand Up @@ -109,13 +143,10 @@ public String sendSimpleMessage(@ModelAttribute EmailValidator validator,
// Values
String fromPersonal = validator.getFromApplication() != null ? "[" + validator.getFromApplication() + "]" : "[mail-it]";
if (validator.getFromName() != null) {
fromPersonal += validator.getFromName();
fromPersonal += " " + validator.getFromName();
}
String subject = validator.getSubject() != null ? validator.getSubject() : "No Subject";
String htmlMessage = validator.getMessage();
if (validator.getFromName() != null) {
htmlMessage = validator.getFromName() + "\n\n" + htmlMessage;
}
if (validator.getReplaceMessageBreak()) {
htmlMessage = htmlMessage.replace("\n", "<br>");
}
Expand All @@ -133,7 +164,18 @@ public String sendSimpleMessage(@ModelAttribute EmailValidator validator,
footer += "<li><span>domain: </span><em>" + request.getHeader(HttpHeaders.ORIGIN) + "</em></li>";
}
if (request.getHeader(HttpHeaders.USER_AGENT) != null) {
footer += "<li><span>user agent: </span><em>" + request.getHeader(HttpHeaders.USER_AGENT) + "</em></li>";
if (MailItApplication.environment.USER_AGENT_SIMPLIFIED) {
StringBuilder userInfo = new StringBuilder();
Map<String, String> analysedUserInfo = this.analyzeUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
for (Map.Entry<String, String> entry : analysedUserInfo.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
userInfo.append("<li><span>").append(key).append(": </span><em>").append(value).append("</em></li>");
}
footer += userInfo;
} else {
footer += "<li><span>user agent: </span><em>" + request.getHeader(HttpHeaders.USER_AGENT) + "</em></li>";
}
}
footer += "</ul><p class='sent_with'>Sent with &#10084;</p></div>";
}
Expand Down
125 changes: 124 additions & 1 deletion src/main/java/com/mailit/controller/MainController.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
package com.mailit.controller;

import com.mailit.MailItApplication;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Controller
public class MainController {

@ResponseBody
private static final Set<String> authenticatedSessions = new HashSet<>();

@Value("${logging.file.name}")
private String file_name;

@GetMapping("/")
public String index() {
String style = "<style>" +
Expand All @@ -28,4 +53,102 @@ public String index() {
return style + "<div class='center'><h1>mail-it</h1></div>";
}

@GetMapping("/logs")
public ResponseEntity<String> getLogs(HttpServletRequest request, HttpServletResponse response) throws IOException {
// Check if the user is already authenticated
HttpSession session = request.getSession();
if (authenticatedSessions.contains(session.getId())) {
// User is already authenticated, proceed with generating and returning the logs page
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);

return new ResponseEntity<>(this.createLogPage(), headers, HttpStatus.OK);
}

// User is not authenticated, display the password form
String message = StringUtils.isEmpty(request.getParameter("message")) ? "" : request.getParameter("message");
String formHtml = "<html><head><style>body {font-family: Arial, sans-serif;}</style></head><body><div style=\"width: 300px; margin: 0 auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px;\"><h2>Access Logs</h2>" + "<p style=\"color: red;\">"+ message +"</p>" + "<form method=\"POST\" action=\"/logs\"><label for=\"password\">Password:</label><br><input type=\"password\" id=\"password\" name=\"password\" style=\"width: 100%; padding: 8px 12px; margin-bottom: 10px;\"><br><button type=\"submit\" style=\"padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;\">Submit</button></form></div></body></html>";
return ResponseEntity.ok(formHtml);
}

@PostMapping("/logs")
public ResponseEntity<String> submitPassword(@RequestParam("password") String enteredPassword, HttpServletRequest request) {
if (StringUtils.isEmpty(enteredPassword) || !enteredPassword.equals(MailItApplication.environment.LOGS_PASSWORD)) {
// Incorrect password, return unauthorized status
String errorMessage = "Incorrect password";
return ResponseEntity.status(HttpStatus.SEE_OTHER).header(HttpHeaders.LOCATION, "/logs?message=" + errorMessage).build();
}

// Password is correct, mark the session ID as authenticated
HttpSession session = request.getSession();
authenticatedSessions.add(session.getId());

// Redirect the user back to the logs page
return ResponseEntity.status(HttpStatus.SEE_OTHER).header(HttpHeaders.LOCATION, "/logs").build();
}

private String createLogPage() throws IOException {
Path logFile = Paths.get(this.file_name);
List<String> allLines = Files.readAllLines(logFile);
int file_start = Math.max(0, allLines.size() - 500);
List<String> last500Lines = allLines.subList(file_start, allLines.size());
Collections.reverse(last500Lines);
String logs = String.join(System.lineSeparator(), last500Lines);
// Reverse the order of lines

StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append("<html><head><title>Logs</title><style>p {white-space: nowrap; margin: 0;}.DEBUG {color: #006400;}.INFO {color: #0000FF;}.WARN {color: #FFA500;}.ERROR {color: #FF0000;}</style></head><body>");

htmlBuilder.append("<div><label>Search:</label><input type=\"text\" id=\"searchInput\"></div>");

htmlBuilder.append("<div style=\"margin: 5px 0;\">");

Pattern pattern = Pattern.compile("\\b(DEBUG|INFO|WARN|ERROR)\\b");
int lastEnd = 0;

String[] lines = logs.split("\n");
for (String line : lines) {
Matcher lineMatcher = pattern.matcher(line);
StringBuilder lineHtml = new StringBuilder();
while (lineMatcher.find()) {
int start = lineMatcher.start();
int end = lineMatcher.end();
String level = lineMatcher.group(1);
String colorClass = level.toUpperCase();
lineHtml.append(line, lastEnd, start);
lineHtml.append("<span class=\"").append(colorClass).append("\">[").append(level).append("]</span>");
lastEnd = end;
}
lineHtml.append(line.substring(lastEnd));
lastEnd = 0;
htmlBuilder.append("<p>").append(lineHtml.toString()).append("</p>\n");
}

htmlBuilder.append("</div>");

htmlBuilder.append("<script>\n" +
" const searchInput = document.getElementById('searchInput');\n" +
" const logs = document.querySelectorAll('div p');\n" +
" const clearBtn = document.getElementById('clear-logs');\n" +
"\n" +
" searchInput.addEventListener('input', filterLogs);\n" +
"\n" +
" function filterLogs() {\n" +
" const filterValue = searchInput.value.toLowerCase();\n" +
" logs.forEach(log => {\n" +
" const logText = log.innerText.toLowerCase();\n" +
" if (logText.includes(filterValue)) {\n" +
" log.style.display = 'block';\n" +
" } else {\n" +
" log.style.display = 'none';\n" +
" }\n" +
" });\n" +
" }\n" +
"</script>");

htmlBuilder.append("</body></html>");

return htmlBuilder.toString();
}

}
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Application
server.port=5686
server.error.include-stacktrace=never
logging.file.name=mail-it.log

## MULTIPART (MultipartProperties)
spring.servlet.multipart.enabled=true
Expand Down

0 comments on commit 1c65320

Please sign in to comment.