b963dcc583e02e30a04c009667e53e3ede4ac229
[phpeclipse.git] /
1 package net.sourceforge.phpeclipse.wiki.actions.mediawiki.connect;
2 //Parts of this sources are copied and modified from the jEdit Wikipedia plugin:
3 //http://www.djini.de/software/wikipedia/index.html
4 //
5 //The modified sources are available under the "Common Public License"
6 //with permission from the original author: Daniel Wunsch
7
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.UnsupportedEncodingException;
11 import java.net.URLDecoder;
12 import java.util.regex.Matcher;
13 import java.util.regex.Pattern;
14
15 import net.sourceforge.phpeclipse.wiki.actions.mediawiki.config.IWikipedia;
16 import net.sourceforge.phpeclipse.wiki.actions.mediawiki.exceptions.MethodException;
17 import net.sourceforge.phpeclipse.wiki.actions.mediawiki.exceptions.PageNotEditableException;
18 import net.sourceforge.phpeclipse.wiki.actions.mediawiki.exceptions.UnexpectedAnswerException;
19 import net.sourceforge.phpeclipse.wiki.editor.WikiEditorPlugin;
20
21 import org.apache.commons.httpclient.ConnectMethod;
22 import org.apache.commons.httpclient.HttpClient;
23 import org.apache.commons.httpclient.HttpConnection;
24 import org.apache.commons.httpclient.HttpException;
25 import org.apache.commons.httpclient.HttpMethod;
26 import org.apache.commons.httpclient.HttpState;
27 import org.apache.commons.httpclient.HttpStatus;
28 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
29 import org.apache.commons.httpclient.NameValuePair;
30 import org.apache.commons.httpclient.URI;
31 import org.apache.commons.httpclient.UsernamePasswordCredentials;
32 import org.apache.commons.httpclient.methods.GetMethod;
33 import org.apache.commons.httpclient.methods.PostMethod;
34 import org.apache.commons.httpclient.protocol.Protocol;
35 import org.apache.commons.httpclient.util.EncodingUtil;
36
37 /**
38  * This class gets the wikitext from a wikipedia edit page
39  * 
40  * The basic coding was copied from the commons-httpclient example <code>MediaWikiConnector.java</code>
41  */
42 public class MediaWikiConnector {
43   //pattern used to scarp an edit page
44   private static final Pattern BODY_PATTERN = Pattern.compile(
45   /*
46    * action=".*?title=(.*?)(&amp;|\") <form id="editform" name="editform" method="post"
47    * action="/w/wiki.phtml?title=Ammersee&amp;action=submit" locked pages: <textarea cols='80' rows='25' readonly>
48    */
49   ".*<form[^>]*\\sid=\"editform\"[^>]*title=(.*?)&amp;[^>]*>" + ".*<textarea[^>]*\\sname=\"wpTextbox1\"[^>]*>(.*?)</textarea>"
50       + ".*<input[^>]*\\svalue=\"(\\d*)\"[^>]*\\sname=\"wpEdittime\"[^>]*>" + ".*", Pattern.DOTALL);
51
52   //setup default user agent
53   final static public String userAgent = "PHPeclipse.de/0.0";
54
55   // create a ConnectionManager
56   private MultiThreadedHttpConnectionManager manager;
57
58   private HttpClient client;
59
60   /**
61    * Delay a new store to 1 second
62    */
63   private Throttle storeThrottle = new Throttle(1000);
64
65   class Throttle {
66     private long nextTime = 0;
67
68     private final long minimumDelay;
69
70     public Throttle(long minimumDelay) {
71       this.minimumDelay = minimumDelay;
72     }
73
74     /** this is called from the client */
75     public synchronized void delay() throws InterruptedException {
76       long delay = nextTime - System.currentTimeMillis();
77       if (delay > 0)
78         Thread.sleep(delay);
79       nextTime = System.currentTimeMillis() + minimumDelay;
80     }
81   }
82
83   public MediaWikiConnector() {
84     // <a href="javascript:window.location.href='http://127.0.0.1:8009/open/?' + window.location.href">bookmarklet</a>
85     manager = new MultiThreadedHttpConnectionManager();
86     manager.setMaxConnectionsPerHost(6);
87     manager.setMaxTotalConnections(18);
88     manager.setConnectionStaleCheckingEnabled(true);
89     // open the conversation
90     client = new HttpClient(manager);
91     //client.State.CookiePolicy = CookiePolicy.COMPATIBILITY;
92     //client.HostConfiguration.setHost(LOGON_SITE, LOGON_PORT, "http");
93   }
94
95   /** destructor freeing all resources. the Connection is not usable any more after calling this method */
96   public void destroy() {
97     manager.shutdown();
98   }
99
100   /** log in - returns success */
101   public boolean login(IWikipedia config, String actionUrl, String user, String password, boolean remember)
102       throws UnexpectedAnswerException, MethodException {
103     PostMethod method = new PostMethod(actionUrl);
104     method.setFollowRedirects(false);
105     method.addRequestHeader("User-Agent", userAgent);
106     NameValuePair[] params = new NameValuePair[] { new NameValuePair("title", config.getLoginTitle()), new NameValuePair("action", "submit"),
107         new NameValuePair("wpName", user), new NameValuePair("wpPassword", password),
108         new NameValuePair("wpRemember", remember ? "1" : "0"), new NameValuePair("wpLoginattempt", "submit") };
109     method.addParameters(params);
110
111     boolean result;
112     try {
113       int responseCode = client.executeMethod(method);
114       String responseBody = method.getResponseBodyAsString();
115
116       //### debugging
117       //log(responseBody);
118       //                        log(method);
119
120       if (responseCode == 302 && responseBody.length() == 0 || responseCode == 200
121           && responseBody.matches(config.getLoginSuccess())) {
122         result = true;
123       } else if (responseCode == 200 && responseBody.matches(config.getLoginWrongPw()) || responseCode == 200
124           && responseBody.matches(config.getLoginNoUser())) {
125         result = false;
126       } else {
127         throw new UnexpectedAnswerException("login not successful: " + method.getStatusLine());
128       }
129     } catch (HttpException e) {
130       throw new MethodException("method failed", e);
131     } catch (IOException e) {
132       throw new MethodException("method failed", e);
133     } finally {
134       method.releaseConnection();
135     }
136     /*
137      * // display cookies System.err.println("login: " + result); for (var cookie : client.State.Cookies) {
138      * System.err.println("cookie: " + cookie); }
139      */
140
141     // remember state
142     SiteState state = SiteState.siteState(config);
143     state.loggedIn = result;
144     state.userName = user;
145
146     return result;
147   }
148
149   /** log out - return success */
150   public boolean logout(IWikipedia config,String actionUrl) throws UnexpectedAnswerException, MethodException {
151     GetMethod method = new GetMethod(actionUrl);
152     method.setFollowRedirects(false);
153     method.addRequestHeader("User-Agent", userAgent);
154     NameValuePair[] params = new NameValuePair[] { new NameValuePair("title", config.getLogoutTitle()),
155         new NameValuePair("action", "submit") };
156     method.setQueryString(EncodingUtil.formUrlEncode(params, config.getCharSet()));
157
158     boolean result;
159     try {
160       int responseCode = client.executeMethod(method);
161       String responseBody = method.getResponseBodyAsString();
162       //                        log(method);
163
164       if (responseCode == 302 && responseBody.length() == 0 || responseCode == 200
165           && responseBody.matches(config.getLoginSuccess())) {
166         //                              config.getloggedIn = false;
167         result = true;
168       } else if (responseCode == 200) {
169         //### should check for a failure message
170         result = false;
171       } else {
172         throw new UnexpectedAnswerException("logout not successful: " + method.getStatusLine());
173       }
174     } catch (HttpException e) {
175       throw new MethodException("method failed", e);
176     } catch (IOException e) {
177       throw new MethodException("method failed", e);
178     } finally {
179       method.releaseConnection();
180     }
181
182     // remember state
183     SiteState state = SiteState.siteState(config);
184     state.loggedIn = false;
185
186     return result;
187   }
188
189   /** parses a returned editform into a Content object with UNIX-EOLs ("\n") */
190   private Parsed parseBody(String charSet, String responseBody) throws PageNotEditableException, UnsupportedEncodingException {
191     Matcher matcher = BODY_PATTERN.matcher(responseBody);
192     if (!matcher.matches())
193       throw new PageNotEditableException("cannot find editform form");
194
195     String title = matcher.group(1);
196     String body = matcher.group(2);
197     String timestamp = matcher.group(3);
198
199     title = URLDecoder.decode(title, charSet);
200     body = body.replaceAll("&quot;", "\"").replaceAll("&apos;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll(
201         "&amp;", "&").replaceAll("\r\n", "\n").replace('\r', '\n');
202
203     return new Parsed(timestamp, title, body);
204   }
205
206   /** load a Page Version - returns a Loaded Object */
207   public Loaded load(String actionURL, String charSet, String title) throws UnexpectedAnswerException, MethodException,
208       PageNotEditableException {
209     GetMethod method = new GetMethod(actionURL);
210     method.setFollowRedirects(false);
211     method.addRequestHeader("User-Agent", userAgent);
212     NameValuePair[] params = new NameValuePair[] { new NameValuePair("title", title), new NameValuePair("action", "edit") };
213     method.setQueryString(EncodingUtil.formUrlEncode(params, charSet));
214
215     Loaded result;
216     try {
217       int responseCode = client.executeMethod(method);
218       String responseBody = method.getResponseBodyAsString();
219       //                        log(method);
220
221       if (responseCode == 200) {
222         Parsed parsed = parseBody(charSet, responseBody);
223         Content content = new Content(parsed.timestamp, parsed.body);
224         result = new Loaded(actionURL, charSet, parsed.title, content);
225       } else {
226         throw new UnexpectedAnswerException("load not successful: expected 200 OK, got " + method.getStatusLine());
227       }
228     } catch (HttpException e) {
229       throw new MethodException("method failed", e);
230     } catch (IOException e) {
231       throw new MethodException("method failed", e);
232     } finally {
233       method.releaseConnection();
234     }
235     return result;
236   }
237
238   /**
239    * store a Page Version - returns a Stored object 
240    * 
241    * @param config - WiKipedia predefined properties 
242    * @param actionURL
243    * @param title
244    * @param content
245    * @param summary
246    * @param minorEdit
247    * @param watchThis
248    * @return
249    * @throws UnexpectedAnswerException
250    * @throws MethodException
251    * @throws PageNotEditableException
252    * @throws InterruptedException
253    */
254   public Stored store(IWikipedia config, String actionUrl, String title, Content content, String summary, boolean minorEdit,
255       boolean watchThis) throws UnexpectedAnswerException, MethodException, PageNotEditableException, InterruptedException {
256     //### workaround: prevent too many stores at a time
257     storeThrottle.delay();
258
259     PostMethod method = new PostMethod(actionUrl);
260     method.setFollowRedirects(false);
261     method.addRequestHeader("User-Agent", userAgent);
262     method.addRequestHeader("Content-Type", PostMethod.FORM_URL_ENCODED_CONTENT_TYPE + "; charset=" + config.getCharSet());
263     NameValuePair[] params = new NameValuePair[] {
264     // new NameValuePair("wpSection", ""),
265         // new NameValuePair("wpPreview", "Vorschau zeigen"),
266         // new NameValuePair("wpSave", "Artikel speichern"),
267         new NameValuePair("title", title), new NameValuePair("wpTextbox1", content.body),
268         new NameValuePair("wpEdittime", content.timestamp), new NameValuePair("wpSummary", summary),
269         new NameValuePair("wpSave", "yes"), new NameValuePair("action", "submit") };
270     method.addParameters(params);
271     if (minorEdit)
272       method.addParameter("wpMinoredit", "1");
273     if (watchThis)
274       method.addParameter("wpWatchthis", "1");
275
276     Stored result;
277     try {
278       int responseCode = client.executeMethod(method);
279       String responseBody = method.getResponseBodyAsString();
280       //                        log(method);
281
282       // since 11dec04 there is a single linefeed instead of an empty page.. trim() helps.
283       if (responseCode == 302 && responseBody.trim().length() == 0) {
284         //                              log("store successful, reloading");
285         Loaded loaded = load(actionUrl, config.getCharSet(), title);
286         result = new Stored(actionUrl, config.getCharSet(), loaded.title, loaded.content, false);
287       } else if (responseCode == 200) {
288         //        log("store not successful, conflict detected");
289         Parsed parsed = parseBody(config.getCharSet(), responseBody);
290         Content cont = new Content(parsed.timestamp, parsed.body);
291         result = new Stored(actionUrl, config.getCharSet(), parsed.title, cont, true);
292       } else {
293         throw new UnexpectedAnswerException("store not successful: expected 200 OK, got " + method.getStatusLine());
294       }
295     } catch (HttpException e) {
296       throw new MethodException("method failed", e);
297     } catch (IOException e) {
298       throw new MethodException("method failed", e);
299     } finally {
300       method.releaseConnection();
301     }
302     return result;
303   }
304
305   /**
306    * Get the text of a wikimedia article
307    *  
308    */
309   public static String getWikiRawText(String wikiname, String urlStr) {
310     // examples
311     // http://en.wikipedia.org/w/wiki.phtml?title=Main_Page&action=raw
312     // http://en.wikibooks.org/w/index.php?title=Programming:PHP:SQL_Injection&action=raw
313     // http://en.wikipedia.org/w/wiki.phtml?title=Talk:Division_by_zero&action=raw
314     HttpMethod method = null;
315     try {
316       if (urlStr == null) {
317         WikiEditorPlugin.getDefault().reportError("No Wikipedia URL configured", "URL-String == null");
318         //        urlStr = "http://en.wikipedia.org/w/wiki.phtml?title=" + wikiname + "&action=raw";
319       }
320       URI uri = new URI(urlStr.toCharArray());
321
322       String schema = uri.getScheme();
323       if ((schema == null) || (schema.equals(""))) {
324         schema = "http";
325       }
326       Protocol protocol = Protocol.getProtocol(schema);
327
328       HttpState state = new HttpState();
329
330       method = new GetMethod(uri.toString());
331       String host = uri.getHost();
332       int port = uri.getPort();
333
334       HttpConnection connection = new HttpConnection(host, port, protocol);
335       // timeout after 30 seconds
336       connection.setConnectionTimeout(30000);
337       connection.setProxyHost(System.getProperty("http.proxyHost"));
338       connection.setProxyPort(Integer.parseInt(System.getProperty("http.proxyPort", "80")));
339
340       if (System.getProperty("http.proxyUserName") != null) {
341         state.setProxyCredentials(null, null, new UsernamePasswordCredentials(System.getProperty("http.proxyUserName"), System
342             .getProperty("http.proxyPassword")));
343       }
344
345       if (connection.isProxied() && connection.isSecure()) {
346         method = new ConnectMethod(method);
347       }
348
349       method.execute(state, connection);
350
351       if (method.getStatusCode() == HttpStatus.SC_OK) {
352         // get the wiki text now:
353         String wikiText = method.getResponseBodyAsString();
354         return wikiText;
355         // wrong text not always complete
356         //        InputStream stream = method.getResponseBodyAsStream();
357         //        int byteLen = stream.available();
358         //        int count = 1;
359         //        byte[] buffer = new byte[byteLen];
360         //        int len = 0;
361         //        stream.read(buffer, 0, byteLen);
362         //        String wikiText = new String(buffer);
363         //        return wikiText;
364         //        System.out.println(wikiText);
365       }
366     } catch (Throwable e) {
367       WikiEditorPlugin.log(e);
368       WikiEditorPlugin.getDefault().reportError("Exception occured", e.getMessage() + "\nSee stacktrace in /.metadata/.log file.");
369     } finally {
370       if (method != null) {
371         method.releaseConnection();
372       }
373     }
374     return null; // no success in getting wiki text
375   }
376
377   public static String getWikiEditTextarea(String wikiname, String urlStr) {
378     // examples
379     // http://en.wikipedia.org/w/wiki.phtml?title=Main_Page&action=edit
380     // http://en.wikibooks.org/w/wiki.phtml?title=Programming:PHP:SQL_Injection&action=edit
381     // http://en.wikipedia.org/w/wiki.phtml?title=Talk:Division_by_zero&action=edit
382     HttpMethod method = null;
383     try {
384       if (urlStr == null) {
385         urlStr = "http://en.wikipedia.org/w/wiki.phtml?title=" + wikiname + "&action=edit";
386       }
387       //      else {
388       //        urlStr = urlStr + "?title=" + wikiname + "&action=edit";
389       //      }
390       URI uri = new URI(urlStr.toCharArray());
391
392       String schema = uri.getScheme();
393       if ((schema == null) || (schema.equals(""))) {
394         schema = "http";
395       }
396       Protocol protocol = Protocol.getProtocol(schema);
397
398       HttpState state = new HttpState();
399
400       method = new GetMethod(uri.toString());
401       String host = uri.getHost();
402       int port = uri.getPort();
403
404       HttpConnection connection = new HttpConnection(host, port, protocol);
405
406       connection.setProxyHost(System.getProperty("http.proxyHost"));
407       connection.setProxyPort(Integer.parseInt(System.getProperty("http.proxyPort", "80")));
408
409       if (System.getProperty("http.proxyUserName") != null) {
410         state.setProxyCredentials(null, null, new UsernamePasswordCredentials(System.getProperty("http.proxyUserName"), System
411             .getProperty("http.proxyPassword")));
412       }
413
414       if (connection.isProxied() && connection.isSecure()) {
415         method = new ConnectMethod(method);
416       }
417
418       method.execute(state, connection);
419
420       if (method.getStatusCode() == HttpStatus.SC_OK) {
421         // get the textareas wiki text now:
422         InputStream stream = method.getResponseBodyAsStream();
423         int byteLen = stream.available();
424         int count = 1;
425         byte[] buffer = new byte[byteLen];
426         stream.read(buffer, 0, byteLen);
427         String wikiText = new String(buffer);
428         //        String wikiText = method.getResponseBodyAsString();
429         int start = wikiText.indexOf("<textarea");
430         if (start != (-1)) {
431           start = wikiText.indexOf(">", start + 1);
432           if (start != (-1)) {
433             int end = wikiText.indexOf("</textarea>");
434             wikiText = wikiText.substring(start + 1, end);
435           }
436         }
437         return wikiText;
438         //        System.out.println(wikiText);
439
440       }
441     } catch (Exception e) {
442       e.printStackTrace();
443     } finally {
444       if (method != null) {
445         method.releaseConnection();
446       }
447     }
448     return null; // no success in getting wiki text
449   }
450 }
451