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}