aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/ru/sape
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/ru/sape')
-rw-r--r--src/main/java/ru/sape/Sape.java25
-rw-r--r--src/main/java/ru/sape/SapeConnection.java107
-rw-r--r--src/main/java/ru/sape/SapePageLinks.java95
-rw-r--r--src/main/java/ru/sape/SerializedPhpParser.java221
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 00000000..c00054ae
--- /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 00000000..8c794b08
--- /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 00000000..498aeac0
--- /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 00000000..a24551b9
--- /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();
+ }
+ }
+}