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}