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