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 org.apache.wiki.LinkCollector; 022import org.apache.wiki.api.core.Attachment; 023import org.apache.wiki.api.core.Context; 024import org.apache.wiki.api.core.ContextEnum; 025import org.apache.wiki.api.core.Page; 026import org.apache.wiki.api.spi.Wiki; 027import org.apache.wiki.auth.permissions.PagePermission; 028import org.apache.wiki.auth.permissions.PermissionFactory; 029import org.apache.wiki.pages.PageManager; 030import org.apache.wiki.render.RenderingManager; 031import org.apache.wiki.util.TextUtil; 032import org.apache.xmlrpc.XmlRpcException; 033 034import java.nio.charset.StandardCharsets; 035import java.util.Calendar; 036import java.util.Collection; 037import java.util.Date; 038import java.util.Hashtable; 039import java.util.Set; 040import java.util.Vector; 041 042/** 043 * Provides handlers for all RPC routines. 044 * 045 * @since 1.6.6 046 */ 047// We could use Engine directly, but because of introspection it would show just too many methods to be safe. 048public class RPCHandler extends AbstractRPCHandler { 049 050 /** 051 * Converts Java string into RPC string. 052 */ 053 private String toRPCString( final String src ) 054 { 055 return TextUtil.urlEncodeUTF8( src ); 056 } 057 058 /** 059 * Converts RPC string (UTF-8, url encoded) into Java string. 060 */ 061 private String fromRPCString( final String src ) 062 { 063 return TextUtil.urlDecodeUTF8( src ); 064 } 065 066 /** 067 * Transforms a Java string into UTF-8. 068 */ 069 private byte[] toRPCBase64( final String src ) 070 { 071 return src.getBytes( StandardCharsets.UTF_8 ); 072 } 073 074 public String getApplicationName() { 075 checkPermission( PagePermission.VIEW ); 076 return toRPCString(m_engine.getApplicationName()); 077 } 078 079 public Vector< String > getAllPages() { 080 checkPermission( PagePermission.VIEW ); 081 final Collection< Page > pages = m_engine.getManager( PageManager.class ).getRecentChanges(); 082 final Vector< String > result = new Vector<>(); 083 084 for( final Page p : pages ) { 085 if( !( p instanceof Attachment ) ) { 086 result.add( toRPCString( p.getName() ) ); 087 } 088 } 089 090 return result; 091 } 092 093 /** 094 * Encodes a single wiki page info into a Hashtable. 095 */ 096 @Override 097 protected Hashtable<String,Object> encodeWikiPage( final Page page ) { 098 final Hashtable<String, Object> ht = new Hashtable<>(); 099 ht.put( "name", toRPCString(page.getName()) ); 100 101 final Date d = page.getLastModified(); 102 103 // 104 // Here we reset the DST and TIMEZONE offsets of the 105 // calendar. Unfortunately, I haven't thought of a better 106 // way to ensure that we're getting the proper date 107 // from the XML-RPC thingy, except to manually adjust the date. 108 // 109 110 final Calendar cal = Calendar.getInstance(); 111 cal.setTime( d ); 112 cal.add( Calendar.MILLISECOND, 113 - (cal.get( Calendar.ZONE_OFFSET ) + 114 (cal.getTimeZone().inDaylightTime( d ) ? cal.get( Calendar.DST_OFFSET ) : 0 ) ) ); 115 116 ht.put( "lastModified", cal.getTime() ); 117 ht.put( "version", page.getVersion() ); 118 119 if( page.getAuthor() != null ) { 120 ht.put( "author", toRPCString( page.getAuthor() ) ); 121 } 122 123 return ht; 124 } 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override 130 public Vector< Hashtable< String, Object > > getRecentChanges( Date since ) { 131 checkPermission( PagePermission.VIEW ); 132 final Set< Page > pages = m_engine.getManager( PageManager.class ).getRecentChanges(); 133 final Vector< Hashtable< String, Object > > result = new Vector<>(); 134 135 final Calendar cal = Calendar.getInstance(); 136 cal.setTime( since ); 137 138 // 139 // Convert UTC to our time. 140 // 141 cal.add( Calendar.MILLISECOND, 142 (cal.get( Calendar.ZONE_OFFSET ) + 143 (cal.getTimeZone().inDaylightTime(since) ? cal.get( Calendar.DST_OFFSET ) : 0 ) ) ); 144 since = cal.getTime(); 145 146 for( final Page page : pages ) { 147 if( page.getLastModified().after( since ) && !(page instanceof Attachment) ) { 148 result.add( encodeWikiPage( page ) ); 149 } 150 } 151 152 return result; 153 } 154 155 /** 156 * Simple helper method, turns the incoming page name into 157 * normal Java string, then checks page condition. 158 * 159 * @param pagename Page Name as an RPC string (URL-encoded UTF-8) 160 * @return Real page name, as Java string. 161 * @throws XmlRpcException, if there is something wrong with the page. 162 */ 163 private String parsePageCheckCondition( String pagename ) throws XmlRpcException { 164 pagename = fromRPCString( pagename ); 165 166 if( !m_engine.getManager( PageManager.class ).wikiPageExists(pagename) ) { 167 throw new XmlRpcException( ERR_NOPAGE, "No such page '"+pagename+"' found, o master." ); 168 } 169 170 final Page p = m_engine.getManager( PageManager.class ).getPage( pagename ); 171 172 checkPermission( PermissionFactory.getPagePermission( p, PagePermission.VIEW_ACTION ) ); 173 174 return pagename; 175 } 176 177 public Hashtable getPageInfo( String pagename ) throws XmlRpcException { 178 pagename = parsePageCheckCondition( pagename ); 179 return encodeWikiPage( m_engine.getManager( PageManager.class ).getPage(pagename) ); 180 } 181 182 public Hashtable getPageInfoVersion( String pagename, final int version ) throws XmlRpcException { 183 pagename = parsePageCheckCondition( pagename ); 184 185 return encodeWikiPage( m_engine.getManager( PageManager.class ).getPage( pagename, version ) ); 186 } 187 188 public byte[] getPage( final String pagename ) throws XmlRpcException { 189 final String text = m_engine.getManager( PageManager.class ).getPureText( parsePageCheckCondition( pagename ), -1 ); 190 return toRPCBase64( text ); 191 } 192 193 public byte[] getPageVersion( String pagename, final int version ) throws XmlRpcException { 194 pagename = parsePageCheckCondition( pagename ); 195 196 return toRPCBase64( m_engine.getManager( PageManager.class ).getPureText( pagename, version ) ); 197 } 198 199 public byte[] getPageHTML( String pagename ) throws XmlRpcException { 200 pagename = parsePageCheckCondition( pagename ); 201 202 return toRPCBase64( m_engine.getManager( RenderingManager.class ).getHTML( pagename ) ); 203 } 204 205 public byte[] getPageHTMLVersion( String pagename, final int version ) throws XmlRpcException { 206 pagename = parsePageCheckCondition( pagename ); 207 208 return toRPCBase64( m_engine.getManager( RenderingManager.class ).getHTML( pagename, version ) ); 209 } 210 211 public Vector< Hashtable< String, String > > listLinks( String pagename ) throws XmlRpcException { 212 pagename = parsePageCheckCondition( pagename ); 213 214 final Page page = m_engine.getManager( PageManager.class ).getPage( pagename ); 215 final String pagedata = m_engine.getManager( PageManager.class ).getPureText( page ); 216 217 final LinkCollector localCollector = new LinkCollector(); 218 final LinkCollector extCollector = new LinkCollector(); 219 final LinkCollector attCollector = new LinkCollector(); 220 221 final Context context = Wiki.context().create( m_engine, page ); 222 m_engine.getManager( RenderingManager.class ).textToHTML( context, pagedata, localCollector, extCollector, attCollector ); 223 224 final Vector< Hashtable< String, String > > result = new Vector<>(); 225 226 // 227 // Add local links. 228 // 229 for( final String link : localCollector.getLinks() ) { 230 final Hashtable< String, String > ht = new Hashtable<>(); 231 ht.put( "page", toRPCString( link ) ); 232 ht.put( "type", LINK_LOCAL ); 233 234 // 235 // FIXME: This is a kludge. The link format should really be queried 236 // from the TranslatorReader itself. Also, the link format should probably 237 // have information on whether the page exists or not. 238 // 239 240 // 241 // FIXME: The current link collector interface is not very good, since it causes this. 242 // 243 244 if( m_engine.getManager( PageManager.class ).wikiPageExists( link ) ) { 245 ht.put( "href", context.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), link ) ); 246 } else { 247 ht.put( "href", context.getURL( ContextEnum.PAGE_EDIT.getRequestContext(), link ) ); 248 } 249 250 result.add( ht ); 251 } 252 253 // 254 // Add links to inline attachments 255 // 256 for( final String link : attCollector.getLinks() ) { 257 final Hashtable<String, String> ht = new Hashtable<>(); 258 ht.put( "page", toRPCString( link ) ); 259 ht.put( "type", LINK_LOCAL ); 260 ht.put( "href", context.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), link ) ); 261 result.add( ht ); 262 } 263 264 // 265 // External links don't need to be changed into XML-RPC strings, simply because URLs are by definition ASCII. 266 // 267 for( final String link : extCollector.getLinks() ) { 268 final Hashtable<String, String> ht = new Hashtable<>(); 269 ht.put( "page", link ); 270 ht.put( "type", LINK_EXTERNAL ); 271 ht.put( "href", link ); 272 result.add( ht ); 273 } 274 275 return result; 276 } 277 278}