/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.nimingban.client.ac.data;
import android.support.annotation.Nullable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import com.hippo.nimingban.client.ReferenceSpan;
import com.hippo.nimingban.client.ac.ACUrl;
import com.hippo.nimingban.client.data.ACSite;
import com.hippo.nimingban.util.Settings;
import com.hippo.text.Html;
import com.hippo.yorozuya.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.XMLReader;
import java.security.MessageDigest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public final class ACItemUtils {
private ACItemUtils() {}
private static final Pattern REFERENCE_PATTERN = Pattern.compile(">>?(?:No.)?(\\d+)");
private static final Pattern URL_PATTERN = Pattern.compile("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/\\*\\.\\?\\+\\$\\[\\]\\(\\)]+)?/?");
private static final Pattern AC_PATTERN = Pattern.compile("ac\\d+");
private static final String NO_TITLE = "无标题";
private static final String NO_NAME = "无名氏";
private static final int COUNT_NUMBER = '9' - '0' + 1;
private static final int COUNT_UPPERCASE_LETTER = 'z' - 'a' + 1;
private static final int COUNT_LOWERCASE_LETTER = 'Z' - 'A' + 1;
private static final int COUNT_SUM = COUNT_NUMBER + COUNT_UPPERCASE_LETTER + COUNT_LOWERCASE_LETTER;
private static final byte[] IV = new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF};
private static final ACHtmlTagHandler AC_HTML_TAG_HANDLER = new ACHtmlTagHandler();
public static CharSequence handleReference(CharSequence content) {
Matcher m = REFERENCE_PATTERN.matcher(content);
Spannable spannable = null;
while (m.find()) {
// Ensure spannable
if (spannable == null) {
if (content instanceof Spannable) {
spannable = (Spannable) content;
} else {
spannable = new SpannableString(content);
}
}
int start = m.start();
int end = m.end();
ReferenceSpan referenceSpan = new ReferenceSpan(ACSite.getInstance(), m.group(1));
spannable.setSpan(referenceSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable == null ? content : spannable;
}
public static CharSequence handleTextUrl(CharSequence content) {
Matcher m = URL_PATTERN.matcher(content);
Spannable spannable = null;
while (m.find()) {
// Ensure spannable
if (spannable == null) {
if (content instanceof Spannable) {
spannable = (Spannable) content;
} else {
spannable = new SpannableString(content);
}
}
int start = m.start();
int end = m.end();
URLSpan[] links = spannable.getSpans(start, end, URLSpan.class);
if (links.length > 0) {
// There has been URLSpan already, leave it alone
continue;
}
URLSpan urlSpan = new URLSpan(m.group(0));
spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable == null ? content : spannable;
}
public static CharSequence handleAcUrl(CharSequence content) {
Matcher m = AC_PATTERN.matcher(content);
Spannable spannable = null;
while (m.find()) {
// Ensure spannable
if (spannable == null) {
if (content instanceof Spannable) {
spannable = (Spannable) content;
} else {
spannable = new SpannableString(content);
}
}
int start = m.start();
int end = m.end();
URLSpan[] links = spannable.getSpans(start, end, URLSpan.class);
if (links.length > 0) {
// There has been URLSpan already, leave it alone
continue;
}
URLSpan urlSpan = new URLSpan("http://www.acfun.tv/v/" + m.group(0));
spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable == null ? content : spannable;
}
public static CharSequence generateContent(String content) {
CharSequence charSequence;
charSequence = Html.fromHtml(content, null, AC_HTML_TAG_HANDLER);
charSequence = handleReference(charSequence);
charSequence = handleTextUrl(charSequence);
charSequence = handleAcUrl(charSequence);
return charSequence;
}
public static CharSequence generateContent(String content, String sage, String title, String name, String email) {
StringBuilder sb = new StringBuilder(44 + 11 + StringUtils.length(title) +
11 + StringUtils.length(name) + 11 + StringUtils.length(email) +
StringUtils.length(content));
if ("1".equals(sage)) {
sb.append("<font color=\"red\"><b>SAGE</b></font><br><br>");
}
if (!TextUtils.isEmpty(title) && !NO_TITLE.equals(title)) {
sb.append("<b>").append(title).append("</b><br>");
}
if (!TextUtils.isEmpty(name) && !NO_NAME.equals(name)) {
sb.append("<b>").append(name).append("</b><br>");
}
if (!TextUtils.isEmpty(email)) {
sb.append("<b>").append(email).append("</b><br>");
}
sb.append(content);
return generateContent(sb.toString());
}
private static char getReadableChar(byte b) {
int i = (b & 0xFF) % COUNT_SUM;
if (i < COUNT_NUMBER) {
return (char) ('0' + i);
} else if (i < COUNT_NUMBER + COUNT_UPPERCASE_LETTER) {
return (char) ('a' + i - COUNT_NUMBER);
} else if (i < COUNT_NUMBER + COUNT_UPPERCASE_LETTER + COUNT_LOWERCASE_LETTER) {
return (char) ('A' + i - COUNT_NUMBER - COUNT_UPPERCASE_LETTER);
} else {
return '?';
}
}
private static String toReadableString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length);
for (byte b: bytes) {
sb.append(getReadableChar(b));
}
return sb.toString();
}
private static boolean isSimpleUser(@Nullable String userID) {
if (null == userID) {
return false;
}
for (int i = 0, n = userID.length(); i < n; i++) {
char ch = userID.charAt(i);
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z') && !(ch >= 'A' && ch <= 'Z')) {
return false;
}
}
return true;
}
public static CharSequence handleUser(CharSequence user, String postId, String id) {
String key;
switch (Settings.getChaosLevel()) {
case 1: // Relatively chaotic
key = postId;
break;
case 2: // Absolutely chaotic
key = id;
break;
default:
return user;
}
String userStr = user.toString();
if (!isSimpleUser(userStr)) {
return user;
}
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(key.getBytes("utf-8"));
byte[] keyBytes = digest.digest();
byte[] input = userStr.getBytes("utf-8");
IvParameterSpec ivSpec = new IvParameterSpec(IV);
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
cipher.doFinal(cipherText, ctLength);
return toReadableString(cipherText);
} catch (Exception e) {
e.printStackTrace();
return user;
}
}
private static class ACHtmlTagHandler implements Html.TagHandler {
@Override
public boolean handleTag(boolean opening, String tag,
SpannableStringBuilder output, XMLReader xmlReader, Attributes attributes) {
if (tag.equalsIgnoreCase("a")) {
if (opening) {
// Add ac nmb host
String href = attributes.getValue("", "href");
if (!href.startsWith("http")) {
if (href.startsWith("/")){
href = ACUrl.HOST + href;
} else {
href = ACUrl.HOST + '/' + href;
}
}
int len = output.length();
output.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK);
} else {
int len = output.length();
Object obj = getLast(output, Href.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
Href h = (Href) obj;
if (h.mHref != null) {
output.setSpan(new URLSpan(h.mHref), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
return true;
} else {
return false;
}
}
}
private static Object getLast(Spanned text, Class kind) {
/*
* This knows that the last returned object from getSpans()
* will be the most recently added.
*/
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
return objs[objs.length - 1];
}
}
private static class Href {
public String mHref;
public Href(String href) {
mHref = href;
}
}
}