    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}