diff options
Diffstat (limited to 'src/main/java/ru/sape')
-rw-r--r-- | src/main/java/ru/sape/Sape.java | 25 | ||||
-rw-r--r-- | src/main/java/ru/sape/SapeConnection.java | 107 | ||||
-rw-r--r-- | src/main/java/ru/sape/SapePageLinks.java | 95 | ||||
-rw-r--r-- | src/main/java/ru/sape/SerializedPhpParser.java | 221 |
4 files changed, 448 insertions, 0 deletions
diff --git a/src/main/java/ru/sape/Sape.java b/src/main/java/ru/sape/Sape.java new file mode 100644 index 000000000..c00054aec --- /dev/null +++ b/src/main/java/ru/sape/Sape.java @@ -0,0 +1,25 @@ +/* + * http://code.google.com/p/javasape/ + */ +package ru.sape; + +import javax.servlet.http.Cookie; + +public class Sape { + + private final String sapeUser; + private final SapeConnection sapePageLinkConnection; + + public Sape(String sapeUser, String host, int socketTimeout, int cacheLifeTime) { + this.sapeUser = sapeUser; + + this.sapePageLinkConnection = new SapeConnection( + "/code.php?user=" + sapeUser + "&host=" + host, + "SAPE_Client PHP", socketTimeout, cacheLifeTime); + } + public boolean debug = false; + + public SapePageLinks getPageLinks(String requestUri, Cookie[] cookies) { + return new SapePageLinks(sapePageLinkConnection, sapeUser, requestUri, cookies, debug); + } +} diff --git a/src/main/java/ru/sape/SapeConnection.java b/src/main/java/ru/sape/SapeConnection.java new file mode 100644 index 000000000..8c794b086 --- /dev/null +++ b/src/main/java/ru/sape/SapeConnection.java @@ -0,0 +1,107 @@ +package ru.sape; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SapeConnection { + + private final String version = "1.0.3"; + private final List<String> serverList = Arrays.asList("dispenser-01.sape.ru", "dispenser-02.sape.ru"); + private final String dispenserPath; + private final String userAgent; + private final int socketTimeout; + private final int cacheLifeTime; + + public SapeConnection(String dispenserPath, String userAgent, int socketTimeout, int cacheLifeTime) { + this.dispenserPath = dispenserPath; + this.userAgent = userAgent; + this.socketTimeout = socketTimeout; + this.cacheLifeTime = cacheLifeTime; + } + + protected String fetchRemoteFile(String host, String path) throws IOException { + Reader r = null; + + try { + HttpURLConnection connection = (HttpURLConnection) ((new URL(("http://" + host + path)).openConnection())); + + if (socketTimeout > 0) { + connection.setConnectTimeout(socketTimeout); + connection.setReadTimeout(socketTimeout); + } + + connection.addRequestProperty("User-Agent", userAgent + ' ' + version); + + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setUseCaches(false); + connection.setRequestMethod("GET"); + connection.connect(); + + r = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); + + StringWriter sw = new StringWriter(); + + int b; + + while ((b = r.read()) != -1) { + sw.write(b); + } + + return sw.toString(); + } finally { + if (r != null) { + r.close(); + } + } + } + Map<String, Object> cached; + long cacheUpdated; + + @SuppressWarnings("unchecked") + public Map<String, Object> getData() { + if (cacheLifeTime <= (System.currentTimeMillis() - cacheUpdated) / 1000) { + for (String server : serverList) { + String data; + + try { + data = fetchRemoteFile(server, dispenserPath + "&charset=UTF-8"); + } catch (IOException e1) { + continue; + } + + if (data.startsWith("FATAL ERROR:")) { + System.err.println("Sape responded with error: " + data); + + continue; + } + + try { + cached = (Map<String, Object>) new SerializedPhpParser(data).parse(); + } catch (Exception e) { + System.err.println("Can't parse Sape data: " + e); + continue; + } + + cacheUpdated = System.currentTimeMillis(); + + return cached; + } + + System.err.println("Unable to fetch Sape data"); + + return new HashMap<String, Object>(); + } + + return cached; + } +} diff --git a/src/main/java/ru/sape/SapePageLinks.java b/src/main/java/ru/sape/SapePageLinks.java new file mode 100644 index 000000000..498aeac06 --- /dev/null +++ b/src/main/java/ru/sape/SapePageLinks.java @@ -0,0 +1,95 @@ +package ru.sape; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.servlet.http.Cookie; + +public class SapePageLinks { + + private boolean showCode; + + public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, Cookie[] cookies) { + this(sapeConnection, sapeUser, requestUri, cookies, false); + } + + @SuppressWarnings("unchecked") + public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, Cookie[] cookies, boolean showCode) { + if (sapeUser.equals(getCookieValue(cookies, "sape_cookie"))) { + showCode = true; + } + + Map<String, Object> data = sapeConnection.getData(); + + if (data.containsKey("__sape_delimiter__")) { + linkDelimiter = (String) data.get("__sape_delimiter__"); + } + + if (data.containsKey(requestUri)) { + pageLinks = new ArrayList<String>(((Map<Object, String>) data.get(requestUri)).values()); + } + + if (data.containsKey("__sape_new_url__")) { + if (showCode) { + Object newUrl = data.get("__sape_new_url__"); + + if (newUrl instanceof Map) { + pageLinks = new ArrayList<String>(((Map<Object, String>) newUrl).values()); + } else { + pageLinks = new ArrayList<String>(Arrays.asList((String) newUrl)); + } + } + } + + this.showCode = showCode; + } + private String linkDelimiter = "."; + private List<String> pageLinks = new ArrayList<String>(); + + public String render() { + return render(-1); + } + + public String render(int count) { + StringBuilder s = new StringBuilder(); + + if (count < 0) { + count = pageLinks.size(); + } + + for (Iterator<String> i = pageLinks.iterator(); i.hasNext() && count > 0; count--) { + if (s.length() > 0) { + s.append(linkDelimiter); + } + + String l = i.next(); + + s.append(l); + + i.remove(); + } + + if (showCode) { + s.insert(0, "<sape_noindex>"); + s.append("</sape_noindex>"); + } + + return s.toString(); + } + + private static String getCookieValue(Cookie[] cookies, String name) { + if (cookies == null) { + return null; + } + + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) { + return cookie.getValue(); + } + } + + return null; + } +} diff --git a/src/main/java/ru/sape/SerializedPhpParser.java b/src/main/java/ru/sape/SerializedPhpParser.java new file mode 100644 index 000000000..a24551b9d --- /dev/null +++ b/src/main/java/ru/sape/SerializedPhpParser.java @@ -0,0 +1,221 @@ +/* +Copyright (c) 2007 Zsolt Szász <zsolt at lorecraft dot com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package ru.sape; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Deserializes a serialized PHP data structure into corresponding Java objects. It supports + * the integer, float, boolean, string primitives that are mapped to their Java + * equivalent, plus arrays that are parsed into <code>Map</code> instances and objects + * that are represented by {@link SerializedPhpParser.PhpObject} instances. + * <p> + * Example of use: + * <pre> + * String input = "O:8:"TypeName":1:{s:3:"foo";s:3:"bar";}"; + * SerializedPhpParser serializedPhpParser = new SerializedPhpParser(input); + * Object result = serializedPhpParser.parse(); + * </pre> + * + * The <code>result</code> object will be a <code>PhpObject</code> with the name "TypeName" and + * the attribute "foo" = "bar". + */ +class SerializedPhpParser { + + private final String input; + private int index; + private boolean assumeUTF8 = true; + private Pattern acceptedAttributeNameRegex = null; + + public SerializedPhpParser(String input) { + this.input = input; + } + + public Object parse() { + char type = input.charAt(index); + switch (type) { + case 'i': + index += 2; + return parseInt(); + case 'd': + index += 2; + return parseFloat(); + case 'b': + index += 2; + return parseBoolean(); + case 's': + index += 2; + return parseString(); + case 'a': + index += 2; + return parseArray(); + case 'O': + index += 2; + return parseObject(); + case 'N': + index += 2; + return NULL; + default: + throw new IllegalStateException("Encountered unknown type [" + type + "], str=" + input.substring(index)); + } + } + + private Object parseObject() { + PhpObject phpObject = new PhpObject(); + int strLen = readLength(); + phpObject.name = input.substring(index, index + strLen); + index = index + strLen + 2; + int attrLen = readLength(); + for (int i = 0; i < attrLen; i++) { + Object key = parse(); + Object value = parse(); + if (isAcceptedAttribute(key)) { + phpObject.attributes.put(key, value); + } + } + index++; + return phpObject; + } + + private Map<Object, Object> parseArray() { + int arrayLen = readLength(); + Map<Object, Object> result = new LinkedHashMap<Object, Object>(); + for (int i = 0; i < arrayLen; i++) { + Object key = parse(); + Object value = parse(); + if (isAcceptedAttribute(key)) { + result.put(key, value); + } + } + index++; + return result; + } + + private boolean isAcceptedAttribute(Object key) { + if (acceptedAttributeNameRegex == null) { + return true; + } + if (!(key instanceof String)) { + return true; + } + return acceptedAttributeNameRegex.matcher((String) key).matches(); + } + + private int readLength() { + int delimiter = input.indexOf(':', index); + int arrayLen = Integer.valueOf(input.substring(index, delimiter)); + index = delimiter + 2; + return arrayLen; + } + + /** + * Assumes strings are utf8 encoded + * + * @return + */ + private String parseString() { + int strLen = readLength(); + + int utfStrLen = 0; + int byteCount = 0; + while (byteCount != strLen) { + char ch = input.charAt(index + utfStrLen++); + + /* + if (ch == '\'') { + utfStrLen -= 1; + break; + } + */ + + if (assumeUTF8) { + if ((ch >= 0x0001) && (ch <= 0x007F)) { + byteCount++; + } else if (ch > 0x07FF) { + byteCount += 3; + } else { + byteCount += 2; + } + } else { + byteCount++; + } + } + String value = input.substring(index, index + utfStrLen); + index = index + utfStrLen + 2; + return value; + } + + private Boolean parseBoolean() { + int delimiter = input.indexOf(';', index); + String value = input.substring(index, delimiter); + if (value.equals("1")) { + value = "true"; + } else if (value.equals("0")) { + value = "false"; + } + index = delimiter + 1; + return Boolean.valueOf(value); + } + + private Double parseFloat() { + int delimiter = input.indexOf(';', index); + String value = input.substring(index, delimiter); + index = delimiter + 1; + return Double.valueOf(value); + } + + private Integer parseInt() { + int delimiter = input.indexOf(';', index); + String value = input.substring(index, delimiter); + index = delimiter + 1; + return Integer.valueOf(value); + } + + public void setAcceptedAttributeNameRegex(String acceptedAttributeNameRegex) { + this.acceptedAttributeNameRegex = Pattern.compile(acceptedAttributeNameRegex); + } + public static final Object NULL = new Object() { + + @Override + public String toString() { + return "NULL"; + } + }; + + /** + * Represents an object that has a name and a map of attributes + */ + public static class PhpObject { + + public String name; + public Map<Object, Object> attributes = new HashMap<Object, Object>(); + + @Override + public String toString() { + return "\"" + name + "\" : " + attributes.toString(); + } + } +} |