package org.kalipo.service;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.RedirectLocations;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.kalipo.domain.Markup;
import org.kalipo.security.Privileges;
import org.kalipo.security.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Service for render plaintext to HTML.
*/
@Service
public class MarkupService {
private final Logger log = LoggerFactory.getLogger(MarkupService.class);
private static final Pattern REGEX_HASHTAG = Pattern.compile("#(\\w+)", Pattern.CASE_INSENSITIVE);
private static final Pattern REGEX_QUOTE = Pattern.compile("[\n\r\t ]*[>]+([^\n\r]+)[\n\r]?", Pattern.DOTALL);
private static final Pattern REGEX_LINK = Pattern.compile("\\(?\\bhttp[s]?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]", Pattern.CASE_INSENSITIVE);
/**
* Translates plaintext to HTML. It creates quotes, links and hashtags. URL shortening is resolved if possible, to increase anonymity.
*
* @param plainText the plain text
* @return the HTML markup
*/
public Markup toHtml(String plainText) {
Markup markup = new Markup(plainText);
renderQuotes(markup);
renderLinks(markup);
renderHashtags(markup);
return markup;
}
private void renderLinks(Markup markup) {
StringBuffer htmlBuffer = markup.buffer();
Matcher matcher = REGEX_LINK.matcher(htmlBuffer);
while (matcher.find()) {
String replacement;
final String url = matcher.group().trim();
try {
final URI targetUri = resolveRedirects(url);
markup.uris().add(targetUri);
if (SecurityUtils.hasPrivilege(Privileges.CREATE_COMMENT_WITH_LINK)) {
replacement = createHref(targetUri);
} else {
replacement = String.format("%s [%s]", targetUri.toASCIIString(), targetUri.getHost());
}
} catch (URISyntaxException e) {
replacement = url;
}
htmlBuffer.replace(matcher.start(), matcher.end(), replacement);
matcher.region(matcher.start() + replacement.length(), htmlBuffer.length());
}
}
/**
* Follows all redirects and uses the final location as url
*
* @param url the url
* @return the final url, after all redirects
* @throws URISyntaxException
*/
public URI resolveRedirects(String url) throws URISyntaxException {
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
HttpGet request = new HttpGet(url);
HttpContext context = new BasicHttpContext();
httpClient.execute(request, context);
URI finalUrl = request.getURI();
RedirectLocations locations = (RedirectLocations) context.getAttribute(HttpClientContext.REDIRECT_LOCATIONS);
if (locations != null) {
finalUrl = locations.getAll().get(locations.getAll().size() - 1);
}
return finalUrl;
} catch (Exception e) {
log.error(String.format("Failed to follow url %s", url), e);
} finally {
try {
httpClient.close();
} catch (IOException e1) {
// ignore
}
}
return new URI(url);
}
private String createHref(URI targetUri) {
String asciiUrl = targetUri.toASCIIString();
String label = asciiUrl;
if (asciiUrl.length() > 35) {
label = asciiUrl.substring(0, 25) + "…" + asciiUrl.substring(asciiUrl.length() - 10, asciiUrl.length());
}
// todo pipe links though a forwarder for log purposes
return String.format("<a href=\"%s\">%s</a> [%s]", asciiUrl, label, targetUri.getHost());
}
private void renderHashtags(Markup markup) {
Matcher matcher = REGEX_HASHTAG.matcher(markup.buffer());
while (matcher.find()) {
String label = matcher.group(1).trim();
markup.hashtags().add(label);
String replacement = createHashtagLink(label);
markup.buffer().replace(matcher.start(), matcher.end(), replacement);
matcher.region(matcher.start() + replacement.length(), markup.buffer().length());
}
}
private String createHashtagLink(String hashtag) {
return String.format("<a href=\"/#search/?query=%1$s\">#%1$s</a>", hashtag);
}
private void renderQuotes(Markup markup) {
Matcher matcher = REGEX_QUOTE.matcher(markup.buffer());
while (matcher.find()) {
String replacement = createQuote(matcher.group(1).trim());
markup.buffer().replace(matcher.start(), matcher.end(), replacement);
matcher.region(matcher.start() + replacement.length(), markup.buffer().length());
}
}
private String createQuote(String quote) {
return String.format("<div class=\"quote\">%s</div>", quote);
}
}