Initial commit

master
Yan 7 months ago
commit cb4f28b1bf

6
.gitignore vendored

@ -0,0 +1,6 @@
/.classpath
/.project
target
conf
logs
.settings

@ -0,0 +1,119 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>guerrilla</artifactId>
<version>1.0.0</version>
<name>guerrilla</name>
<description>Hit and Run</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-boot-dependencies.version>3.4.4</spring-boot-dependencies.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<jsoup.version>1.19.1</jsoup.version>
<java.mail.version>1.6.2</java.mail.version>
<guava.version>33.1.0-jre</guava.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>${java.mail.version}</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>${java.mail.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-dependencies.version}</version>
<configuration>
<mainClass>com.example.guerrilla.Boot</mainClass>
</configuration>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,40 @@
package com.example.guerrilla;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import com.example.guerrilla.service.CaptureService;
@SpringBootApplication
public class Boot {
private static final Logger logger = LoggerFactory.getLogger(Boot.class);
@Autowired
CaptureService captureService;
public static void main(String[] args) throws IOException {
String configDirectory = "conf";
if (args.length > 0) {
configDirectory = args[0];
}
logger.info("config directory: {}", configDirectory);
if (new File(configDirectory).exists() && new File(configDirectory).isDirectory()) {
System.setProperty("spring.config.location", configDirectory + "/springboot.yml");
System.setProperty("logging.config", configDirectory + "/logback.xml");
}
CaptureService captureService = new SpringApplicationBuilder(Boot.class).web(WebApplicationType.NONE).run(args)
.getBean(CaptureService.class);
captureService.run();
System.exit(0);
}
}

@ -0,0 +1,64 @@
package com.example.guerrilla.config;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@Configuration
public class AppConfig {
@Value("${app.main.javamail-config}")
private String javamailCfg;
@Bean
public Properties javaMailProperties() throws IOException {
Properties javaMailProperties = new Properties();
FileInputStream fis = new FileInputStream(javamailCfg);
javaMailProperties.load(fis);
fis.close();
return javaMailProperties;
}
@Bean("objectMapper")
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new Jdk8Module());
return mapper;
}
@Component
@ConfigurationProperties(prefix = "app.htmlunit")
public class WebPageList {
private List<WebPage> vipPages = new ArrayList<>();
public List<WebPage> getVipPages() {
return vipPages;
}
@ConfigurationProperties(prefix = "vip-pages")
public void setVipPages(List<WebPage> vipPages) {
this.vipPages = vipPages;
}
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public record WebPage(String name, String url) {
}
}

@ -0,0 +1,193 @@
package com.example.guerrilla.service;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlButton;
import org.htmlunit.html.HtmlForm;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.html.HtmlPasswordInput;
import org.htmlunit.html.HtmlTextInput;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.example.guerrilla.config.AppConfig.WebPage;
import com.example.guerrilla.config.AppConfig.WebPageList;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.MapDifference;
import com.google.common.collect.MapDifference.ValueDifference;
import com.google.common.collect.Maps;
@Service
public class CaptureService {
private static final Logger logger = LoggerFactory.getLogger(CaptureService.class);
@Value("${app.htmlunit.login-url}")
private String loginUrl;
@Value("${app.htmlunit.login-btn-selector}")
private String loginBtnSelector;
@Value("${app.htmlunit.login-wait-millis:3000}")
private Long loginWaitMillis;
@Value("${app.htmlunit.logout-url}")
private String logoutUrl;
@Value("${app.htmlunit.selector1}")
private String selector1;
@Value("${app.htmlunit.selector2}")
private String selector2;
@Value("${app.htmlunit.selector3}")
private String selector3;
@Value("${app.htmlunit.username}")
private String username;
@Value("${app.htmlunit.password}")
private String password;
@Value("${app.htmlunit.user-page-url}")
private String userPageUrl;
@Autowired
private WebPageList webPageList;
@Value("${app.main.expected-result-file-path}")
private String expectedResultFilePath;
@Value("${app.main.email-password:}")
private String smtpPassword;
@Value("${app.main.email-sender:}")
private String fromAddress;
@Value("${app.main.email-recipient:}")
private String[] toAddresses;
@Value("${app.main.alert-subject}")
private String alertSubject;
@Autowired
private EmailService emailService;
@Autowired
private ObjectMapper objectMapper;
public HashMap<String, Object> convertStringToMap(String jsonString) throws IOException {
TypeReference<java.util.HashMap<String, Object>> ref = new TypeReference<>() {
};
return new HashMap<>(objectMapper.readValue(jsonString, ref));
}
public void run() throws IOException {
logger.info("webPageList: {}", webPageList);
File jsonFile = new File(expectedResultFilePath);
if (!jsonFile.exists()) {
logger.info("file does not exist: {}", expectedResultFilePath);
return;
}
String jsonString = FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8);
HashMap<String, Object> expectedMap = convertStringToMap(jsonString);
logger.info("expected result: {}", expectedMap);
Map<String, Object> actualMap = getData();
logger.info("actual result: {}", actualMap);
MapDifference<String, Object> diff = Maps.difference(expectedMap, actualMap);
logger.info("diff.areEqual: {}", diff.areEqual());
if (!diff.areEqual()) {
StringBuilder contentBuilder = new StringBuilder();
Map<String, ValueDifference<Object>> entriesDiffering = diff.entriesDiffering();
logger.info("entriesDiffering.size(): {}", entriesDiffering.size());
entriesDiffering.entrySet().stream().forEach(entry -> {
logger.info("entriesDiffering key: {}", entry.getKey());
logger.info("entriesDiffering expected: {}", entry.getValue().leftValue());
logger.info("entriesDiffering actual: {}", entry.getValue().rightValue());
contentBuilder.append(entry.getKey() + "<br/>\n");
contentBuilder.append("EXPECTED: " + entry.getValue().leftValue() + "<br/>\n");
contentBuilder.append("ACTUAL: " + entry.getValue().rightValue() + "<br/>\n");
contentBuilder.append("<br/>\n");
});
Pair<Boolean, Boolean> sendEmailResult = Pair.of(Boolean.TRUE, emailService.sendEmail(fromAddress,
smtpPassword, toAddresses, alertSubject, contentBuilder.toString()));
logger.info("sendEmailResult: {}", sendEmailResult);
}
}
public Map<String, Object> getData() {
Map<String, Object> result = new HashMap<>();
try (final WebClient webClient = new WebClient()) {
final HtmlPage loginPage = webClient.getPage(loginUrl);
logger.info("loginPage: {}", loginPage.asXml());
final HtmlForm form = loginPage.getForms().get(0);
final HtmlTextInput usernameField = form.getInputByName("username");
usernameField.type(username);
logger.info("entered username: {}", username);
final HtmlPasswordInput passwordField = form.getInputByName("password");
passwordField.type(password);
logger.info("entered password: {}", password);
HtmlButton loginBtn = loginPage.querySelector(loginBtnSelector);
logger.info("loginBtn type: {}", loginBtn.getType());
loginBtn.click();
Thread.sleep(loginWaitMillis);
final HtmlPage userPage = webClient.getPage(userPageUrl);
logger.info("userPage: {}", userPage.asXml());
webPageList.getVipPages().forEach(webPage -> {
try {
fillResponseMap(webClient, webPage, result);
} catch (Exception e) {
logger.error("getData error!", e);
}
});
webClient.getPage(logoutUrl);
} catch (Exception e) {
logger.error("getData error!", e);
}
return result;
}
private void fillResponseMap(WebClient webClient, WebPage webPage, Map<String, Object> result) throws Exception {
Map<String, List<String>> reply = new HashMap<>();
HtmlPage vipPage = webClient.getPage(webPage.url());
logger.info("{}: {}", webPage.name(), vipPage.asXml());
Document doc = Jsoup.parse(vipPage.asXml());
for (String selector : List.of(selector1, selector2, selector3)) {
Elements elements = doc.select(selector);
List<String> selectedList = new ArrayList<>();
for (Element element : elements) {
logger.info("[fillResponseMap] {} -> {}", selector, element.html());
selectedList.add(element.html());
}
reply.put(selector, selectedList);
}
result.put(webPage.name(), reply);
}
}

@ -0,0 +1,70 @@
package com.example.guerrilla.service;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
@Autowired
private Properties javaMailProperties;
public boolean sendEmail(String fromAddress, String smtpPassword, String[] toAddresses, String subject,
String content) {
try {
logger.debug("[sendEmail] fromAddress: {}, toAddresses: {}", fromAddress, toAddresses);
if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(smtpPassword) || StringUtils.isEmpty(fromAddress)
|| toAddresses.length == 0) {
logger.warn("[sendEmail] subject, smtp-password, source-address or target-address is not set");
return false;
}
String emailHost = javaMailProperties.getProperty("mail.smtp.host");
logger.debug("[sendEmail] {} -> {}, mail.smtp.host: {}", fromAddress, List.of(toAddresses), emailHost);
if (StringUtils.isNotBlank(emailHost) && StringUtils.isNotBlank(fromAddress) && toAddresses.length > 0) {
logger.debug("[sendEmail] starting to send message from: {}", fromAddress);
Session session = Session.getInstance(javaMailProperties);
MimeMessage message = new MimeMessage(session);
message.setContent(content, "text/html; charset=utf-8");
message.setFrom(new InternetAddress(fromAddress));
Arrays.asList(toAddresses).forEach(toAddress -> {
try {
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddress));
} catch (Exception e) {
logger.error("sendEmail error!", e);
}
});
message.setSubject(subject);
Transport transport = session.getTransport("smtp");
transport.connect(emailHost, fromAddress, smtpPassword);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
logger.debug("sendEmail to: {}", Arrays.asList(toAddresses));
return true;
} else {
logger.warn("[sendEmail] missing either from-field, to-field or host address!");
}
} catch (Exception e) {
logger.error("sendEmail error!", e);
}
return false;
}
}
Loading…
Cancel
Save