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.xmlrpc.XmlRpcException;
032
033import java.util.Calendar;
034import java.util.Date;
035import java.util.Hashtable;
036import java.util.Set;
037import java.util.Vector;
038import java.util.stream.Collectors;
039
040/**
041 *  Provides handlers for all RPC routines.  These routines are used by
042 *  the UTF-8 interface.
043 *
044 *  @since 1.6.13
045 */
046
047public class RPCHandlerUTF8 extends AbstractRPCHandler {
048
049    public String getApplicationName() {
050        checkPermission( PagePermission.VIEW );
051        return m_engine.getApplicationName();
052    }
053
054    public Vector< String > getAllPages() {
055        checkPermission( PagePermission.VIEW );
056
057        final Set< Page > pages = m_engine.getManager( PageManager.class ).getRecentChanges();
058
059        return pages.stream().filter(p -> !(p instanceof Attachment)).map(Page::getName).collect(Collectors.toCollection(Vector::new));
060    }
061
062    /**
063     *  Encodes a single wiki page info into a Hashtable.
064     */
065    @Override
066    protected Hashtable<String, Object> encodeWikiPage( final Page page ) {
067        final Hashtable<String, Object> ht = new Hashtable<>();
068        ht.put( "name", page.getName() );
069
070        final Date d = page.getLastModified();
071
072        //
073        //  Here we reset the DST and TIMEZONE offsets of the calendar.  Unfortunately, I haven't thought of a better
074        //  way to ensure that we're getting the proper date from the XML-RPC thingy, except to manually adjust the date.
075        //
076        final Calendar cal = Calendar.getInstance();
077        cal.setTime( d );
078        cal.add( Calendar.MILLISECOND,
079                 - (cal.get( Calendar.ZONE_OFFSET ) +
080                    (cal.getTimeZone().inDaylightTime( d ) ? cal.get( Calendar.DST_OFFSET ) : 0 )) );
081
082        ht.put( "lastModified", cal.getTime() );
083        ht.put( "version", page.getVersion() );
084
085        if( page.getAuthor() != null ) {
086            ht.put( "author", page.getAuthor() );
087        }
088
089        return ht;
090    }
091
092    @Override
093    public Vector< Hashtable< String, Object > > getRecentChanges( Date since ) {
094        checkPermission( PagePermission.VIEW );
095
096        final Set< Page > pages = m_engine.getManager( PageManager.class ).getRecentChanges();
097        final Vector< Hashtable< String, Object > > result = new Vector<>();
098
099        final Calendar cal = Calendar.getInstance();
100        cal.setTime( since );
101
102        //
103        //  Convert UTC to our time.
104        //
105        cal.add( Calendar.MILLISECOND,
106                 (cal.get( Calendar.ZONE_OFFSET ) +
107                  (cal.getTimeZone().inDaylightTime(since) ? cal.get( Calendar.DST_OFFSET ) : 0 ) ) );
108        since = cal.getTime();
109
110        for( final Page page : pages ) {
111            if( page.getLastModified().after( since ) && !( page instanceof Attachment ) ) {
112                result.add( encodeWikiPage( page ) );
113            }
114        }
115
116        return result;
117    }
118
119    /**
120     *  Simple helper method, turns the incoming page name into
121     *  normal Java string, then checks page condition.
122     *
123     *  @param pagename Page Name as an RPC string (URL-encoded UTF-8)
124     *  @return Real page name, as Java string.
125     *  @throws XmlRpcException, if there is something wrong with the page.
126     */
127    private String parsePageCheckCondition( final String pagename ) throws XmlRpcException {
128        if( !m_engine.getManager( PageManager.class ).wikiPageExists(pagename) ) {
129            throw new XmlRpcException( ERR_NOPAGE, "No such page '"+pagename+"' found, o master." );
130        }
131
132        final Page p = m_engine.getManager( PageManager.class ).getPage( pagename );
133
134        checkPermission( PermissionFactory.getPagePermission( p, PagePermission.VIEW_ACTION ) );
135        return pagename;
136    }
137
138    public Hashtable<String, Object> getPageInfo( final String pagename ) throws XmlRpcException {
139        return encodeWikiPage( m_engine.getManager( PageManager.class ).getPage( parsePageCheckCondition( pagename ) ) );
140    }
141
142    public Hashtable<String, Object> getPageInfoVersion( String pagename, final int version ) throws XmlRpcException {
143        pagename = parsePageCheckCondition( pagename );
144
145        return encodeWikiPage( m_engine.getManager( PageManager.class ).getPage( pagename, version ) );
146    }
147
148    public String getPage( final String pagename ) throws XmlRpcException {
149        return m_engine.getManager( PageManager.class ).getPureText( parsePageCheckCondition( pagename ), -1 );
150    }
151
152    public String getPageVersion( final String pagename, final int version ) throws XmlRpcException {
153        return m_engine.getManager( PageManager.class ).getPureText( parsePageCheckCondition( pagename ), version );
154    }
155
156    public String getPageHTML( final String pagename ) throws XmlRpcException  {
157        return m_engine.getManager( RenderingManager.class ).getHTML( parsePageCheckCondition( pagename ) );
158    }
159
160    public String getPageHTMLVersion( final String pagename, final int version ) throws XmlRpcException {
161        return m_engine.getManager( RenderingManager.class ).getHTML( parsePageCheckCondition( pagename ), version );
162    }
163
164    public Vector< Hashtable< String, String > > listLinks( String pagename ) throws XmlRpcException {
165        pagename = parsePageCheckCondition( pagename );
166
167        final Page page = m_engine.getManager( PageManager.class ).getPage( pagename );
168        final String pagedata = m_engine.getManager( PageManager.class ).getPureText( page );
169
170        final LinkCollector localCollector = new LinkCollector();
171        final LinkCollector extCollector   = new LinkCollector();
172        final LinkCollector attCollector   = new LinkCollector();
173
174        final Context context = Wiki.context().create( m_engine, page );
175        m_engine.getManager( RenderingManager.class ).textToHTML( context, pagedata, localCollector, extCollector, attCollector );
176
177        final Vector< Hashtable< String, String > > result = new Vector<>();
178
179        // FIXME: Contains far too much common with RPCHandler.  Refactor!
180
181        //
182        //  Add local links.
183        //
184        for( final String link : localCollector.getLinks() ) {
185            final Hashtable<String, String> ht = new Hashtable<>();
186            ht.put( "page", link );
187            ht.put( "type", LINK_LOCAL );
188
189            if( m_engine.getManager( PageManager.class ).wikiPageExists( link ) ) {
190                ht.put( "href", context.getViewURL( link ) );
191            } else {
192                ht.put( "href", context.getURL( ContextEnum.PAGE_EDIT.getRequestContext(), link ) );
193            }
194
195            result.add( ht );
196        }
197
198        //
199        // Add links to inline attachments
200        //
201        for( final String link : attCollector.getLinks() ) {
202            final Hashtable<String, String> ht = new Hashtable<>();
203            ht.put( "page", link );
204            ht.put( "type", LINK_LOCAL );
205            ht.put( "href", context.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), link ) );
206            result.add( ht );
207        }
208
209        //
210        // External links don't need to be changed into XML-RPC strings, simply because URLs are by definition ASCII.
211        //
212        for( final String link : extCollector.getLinks() )  {
213            final Hashtable<String, String> ht = new Hashtable<>();
214            ht.put( "page", link );
215            ht.put( "type", LINK_EXTERNAL );
216            ht.put( "href", link );
217            result.add( ht );
218        }
219
220        return result;
221    }
222
223}