001 /* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019 package org.apache.wiki.xmlrpc; 020 021 import java.io.UnsupportedEncodingException; 022 import java.util.Calendar; 023 import java.util.Collection; 024 import java.util.Date; 025 import java.util.Hashtable; 026 import java.util.Iterator; 027 import java.util.Vector; 028 029 import org.apache.log4j.Logger; 030 import org.apache.wiki.LinkCollector; 031 import org.apache.wiki.WikiContext; 032 import org.apache.wiki.WikiEngine; 033 import org.apache.wiki.WikiPage; 034 import org.apache.wiki.attachment.Attachment; 035 import org.apache.wiki.auth.permissions.PagePermission; 036 import org.apache.wiki.auth.permissions.PermissionFactory; 037 import org.apache.wiki.util.TextUtil; 038 import org.apache.xmlrpc.XmlRpcException; 039 040 /** 041 * Provides handlers for all RPC routines. 042 * 043 * @since 1.6.6 044 */ 045 // We could use WikiEngine directly, but because of introspection it would 046 // show just too many methods to be safe. 047 public class RPCHandler 048 extends AbstractRPCHandler 049 { 050 private static Logger log = Logger.getLogger( RPCHandler.class ); 051 052 /** 053 * {@inheritDoc} 054 */ 055 public void initialize( WikiContext ctx ) 056 { 057 super.initialize( ctx ); 058 } 059 060 /** 061 * Converts Java string into RPC string. 062 */ 063 private String toRPCString( String src ) 064 { 065 return TextUtil.urlEncodeUTF8( src ); 066 } 067 068 /** 069 * Converts RPC string (UTF-8, url encoded) into Java string. 070 */ 071 private String fromRPCString( String src ) 072 { 073 return TextUtil.urlDecodeUTF8( src ); 074 } 075 076 /** 077 * Transforms a Java string into UTF-8. 078 */ 079 private byte[] toRPCBase64( String src ) 080 { 081 try 082 { 083 return src.getBytes("UTF-8"); 084 } 085 catch( UnsupportedEncodingException e ) 086 { 087 // 088 // You shouldn't be running JSPWiki on a platform that does not 089 // use UTF-8. We revert to platform default, so that the other 090 // end might have a chance of getting something. 091 // 092 log.fatal("Platform does not support UTF-8, reverting to platform default"); 093 return src.getBytes(); 094 } 095 } 096 097 public String getApplicationName() 098 { 099 checkPermission( PagePermission.VIEW ); 100 return toRPCString(m_engine.getApplicationName()); 101 } 102 103 public Vector getAllPages() 104 { 105 checkPermission( PagePermission.VIEW ); 106 Collection pages = m_engine.getRecentChanges(); 107 Vector<String> result = new Vector<String>(); 108 109 for( Iterator i = pages.iterator(); i.hasNext(); ) 110 { 111 WikiPage p = (WikiPage) i.next(); 112 if( !(p instanceof Attachment) ) 113 { 114 result.add( toRPCString(p.getName()) ); 115 } 116 } 117 118 return result; 119 } 120 121 /** 122 * Encodes a single wiki page info into a Hashtable. 123 */ 124 protected Hashtable<String,Object> encodeWikiPage( WikiPage page ) 125 { 126 Hashtable<String, Object> ht = new Hashtable<String, Object>(); 127 128 ht.put( "name", toRPCString(page.getName()) ); 129 130 Date d = page.getLastModified(); 131 132 // 133 // Here we reset the DST and TIMEZONE offsets of the 134 // calendar. Unfortunately, I haven't thought of a better 135 // way to ensure that we're getting the proper date 136 // from the XML-RPC thingy, except to manually adjust the date. 137 // 138 139 Calendar cal = Calendar.getInstance(); 140 cal.setTime( d ); 141 cal.add( Calendar.MILLISECOND, 142 - (cal.get( Calendar.ZONE_OFFSET ) + 143 (cal.getTimeZone().inDaylightTime( d ) ? cal.get( Calendar.DST_OFFSET ) : 0 )) ); 144 145 ht.put( "lastModified", cal.getTime() ); 146 ht.put( "version", page.getVersion() ); 147 148 if( page.getAuthor() != null ) 149 { 150 ht.put( "author", toRPCString(page.getAuthor()) ); 151 } 152 153 return ht; 154 } 155 156 public Vector getRecentChanges( Date since ) 157 { 158 checkPermission( PagePermission.VIEW ); 159 Collection pages = m_engine.getRecentChanges(); 160 Vector<Hashtable<String, Object>> result = new Vector<Hashtable<String, Object>>(); 161 162 Calendar cal = Calendar.getInstance(); 163 cal.setTime( since ); 164 165 // 166 // Convert UTC to our time. 167 // 168 cal.add( Calendar.MILLISECOND, 169 (cal.get( Calendar.ZONE_OFFSET ) + 170 (cal.getTimeZone().inDaylightTime(since) ? cal.get( Calendar.DST_OFFSET ) : 0 ) ) ); 171 since = cal.getTime(); 172 173 for( Iterator i = pages.iterator(); i.hasNext(); ) 174 { 175 WikiPage page = (WikiPage)i.next(); 176 177 if( page.getLastModified().after( since ) && !(page instanceof Attachment) ) 178 { 179 result.add( encodeWikiPage( page ) ); 180 } 181 } 182 183 return result; 184 } 185 186 /** 187 * Simple helper method, turns the incoming page name into 188 * normal Java string, then checks page condition. 189 * 190 * @param pagename Page Name as an RPC string (URL-encoded UTF-8) 191 * @return Real page name, as Java string. 192 * @throws XmlRpcException, if there is something wrong with the page. 193 */ 194 private String parsePageCheckCondition( String pagename ) 195 throws XmlRpcException 196 { 197 pagename = fromRPCString( pagename ); 198 199 if( !m_engine.pageExists(pagename) ) 200 { 201 throw new XmlRpcException( ERR_NOPAGE, "No such page '"+pagename+"' found, o master." ); 202 } 203 204 WikiPage p = m_engine.getPage( pagename ); 205 206 checkPermission( PermissionFactory.getPagePermission( p, PagePermission.VIEW_ACTION ) ); 207 208 return pagename; 209 } 210 211 public Hashtable getPageInfo( String pagename ) 212 throws XmlRpcException 213 { 214 pagename = parsePageCheckCondition( pagename ); 215 return encodeWikiPage( m_engine.getPage(pagename) ); 216 } 217 218 public Hashtable getPageInfoVersion( String pagename, int version ) 219 throws XmlRpcException 220 { 221 pagename = parsePageCheckCondition( pagename ); 222 223 return encodeWikiPage( m_engine.getPage( pagename, version ) ); 224 } 225 226 public byte[] getPage( String pagename ) 227 throws XmlRpcException 228 { 229 pagename = parsePageCheckCondition( pagename ); 230 231 String text = m_engine.getPureText( pagename, -1 ); 232 233 return toRPCBase64( text ); 234 } 235 236 public byte[] getPageVersion( String pagename, int version ) 237 throws XmlRpcException 238 { 239 pagename = parsePageCheckCondition( pagename ); 240 241 return toRPCBase64( m_engine.getPureText( pagename, version ) ); 242 } 243 244 public byte[] getPageHTML( String pagename ) 245 throws XmlRpcException 246 { 247 pagename = parsePageCheckCondition( pagename ); 248 249 return toRPCBase64( m_engine.getHTML( pagename ) ); 250 } 251 252 public byte[] getPageHTMLVersion( String pagename, int version ) 253 throws XmlRpcException 254 { 255 pagename = parsePageCheckCondition( pagename ); 256 257 return toRPCBase64( m_engine.getHTML( pagename, version ) ); 258 } 259 260 public Vector listLinks( String pagename ) 261 throws XmlRpcException 262 { 263 pagename = parsePageCheckCondition( pagename ); 264 265 WikiPage page = m_engine.getPage( pagename ); 266 String pagedata = m_engine.getPureText( page ); 267 268 LinkCollector localCollector = new LinkCollector(); 269 LinkCollector extCollector = new LinkCollector(); 270 LinkCollector attCollector = new LinkCollector(); 271 272 WikiContext context = new WikiContext( m_engine, page ); 273 context.setVariable( WikiEngine.PROP_REFSTYLE, "absolute" ); 274 275 m_engine.textToHTML( context, 276 pagedata, 277 localCollector, 278 extCollector, 279 attCollector ); 280 281 Vector<Hashtable<String, String>> result = new Vector<Hashtable<String, String>>(); 282 283 // 284 // Add local links. 285 // 286 for( Iterator< String > i = localCollector.getLinks().iterator(); i.hasNext(); ) 287 { 288 String link = i.next(); 289 Hashtable< String, String > ht = new Hashtable<String, String>(); 290 ht.put( "page", toRPCString( link ) ); 291 ht.put( "type", LINK_LOCAL ); 292 293 // 294 // FIXME: This is a kludge. The link format should really be queried 295 // from the TranslatorReader itself. Also, the link format should probably 296 // have information on whether the page exists or not. 297 // 298 299 // 300 // FIXME: The current link collector interface is not very good, since 301 // it causes this. 302 // 303 304 if( m_engine.pageExists(link) ) 305 { 306 ht.put( "href", context.getURL(WikiContext.VIEW,link) ); 307 } 308 else 309 { 310 ht.put( "href", context.getURL(WikiContext.EDIT,link) ); 311 } 312 313 result.add( ht ); 314 } 315 316 // 317 // Add links to inline attachments 318 // 319 for( Iterator< String > i = attCollector.getLinks().iterator(); i.hasNext(); ) 320 { 321 String link = i.next(); 322 323 Hashtable< String, String > ht = new Hashtable< String, String >(); 324 325 ht.put( "page", toRPCString( link ) ); 326 ht.put( "type", LINK_LOCAL ); 327 ht.put( "href", context.getURL( WikiContext.ATTACH, link ) ); 328 329 result.add( ht ); 330 } 331 332 // 333 // External links don't need to be changed into XML-RPC strings, 334 // simply because URLs are by definition ASCII. 335 // 336 337 for( Iterator< String > i = extCollector.getLinks().iterator(); i.hasNext(); ) 338 { 339 String link = i.next(); 340 341 Hashtable< String, String > ht = new Hashtable< String, String >(); 342 343 ht.put( "page", link ); 344 ht.put( "type", LINK_EXTERNAL ); 345 ht.put( "href", link ); 346 347 result.add( ht ); 348 } 349 350 return result; 351 } 352 }