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