Quick-n-Dirty: A simple isRecycled() method

Update: Please read the second article – there you will find a better version.

You will find that the domino java API does not have a suitable method to test if a domino object was already recycled or not. And because of the internal caching of some method calls it is not reliable to check for example for a specific property with a try/catch block.

But domino objects have a private property isdeleted which is transient and gives us the required information. With Java Reflection it is possible to access this property:

package ch.hasselba.domino;

import java.lang.reflect.Field;

import lotus.domino.Base;
import lotus.domino.local.NotesBase;

/**
 * DominoUtil a library containing usefull Tools / Methods for domino
 * 
 * @author Sven Hasselbach
 * @category Domino
 * @category Tools
 * @version 1.2
 */
public class DominoUtil {

    private static Field isDeleted;    

    /**
     * checks if a domino object is already recycled
     * 
     * @param lotus.domino.local.NotesBase
     *           obj to check
     * @author Sven Hasselbach
     * @category Domino
     * @category Tools
     * @version 1.1
     */
    public static boolean isRecycled(NotesBase obj) {

        if( isDeleted == null )
            initIsDeleted();

        try {
            return isDeleted.getBoolean(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * checks if a domino object is already recycled
     * 
     * @param lotus.domino.Base
     *           obj to check
     * @author Sven Hasselbach
     * @category Domino
     * @category Tools
     * @version 1.1
     */
    public static boolean isRecycled(Base obj) {
        return isRecycled( (NotesBase) obj );
    }

    /**
     * inits Reflection of isDeleted field
     */
    private static void initIsDeleted(){
        try {
            isDeleted = lotus.domino.local.NotesBase.class
                    .getDeclaredField("isdeleted");
            isDeleted.setAccessible(true);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Here is a short demonstration of the usage in an agent (it works in XPages / SSJS too):

import lotus.domino.*;
import ch.hasselba.domino.DominoUtil;

public class JavaAgent extends AgentBase {

    public void NotesMain() {

      try {
          Session session = getSession();
          AgentContext agentContext = session.getAgentContext();
          Database db = session.getCurrentDatabase();

          View v1 =  db.getView( "AllDocuments" );
          View v2 =  db.getView( "AllDocuments" );

          System.out.println( "Recycled: " + 
             DominoUtil.isRecycled( v1 ) );

          v2.recycle();

          System.out.println( "Recycled: " + 
            DominoUtil.isRecycled( v1 ) );

      } catch(Exception e) {
          e.printStackTrace();
       }
   }
}
Veröffentlicht unter Java, ServerSide JavaScript | Verschlagwortet mit , , , | 7 Kommentare

Teamstudio Unplugged: SSJS & Error Messages

Today I had to fight with a mysterious error message in a XPage application which is running on Teamstudio Unplugged:

SyntaxError: missing ; before statement

This IS the message. No more information. No stack trace. No library name. Nothing!

It was a hard piece of work to find the needle in the haystack: I used the synchronized keyword in a SSJS library. Hope in future Teamstudio will provide better error informations…

Veröffentlicht unter Errorhandling, Mobile, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | Ein Kommentar

XPages: Capture Signatures with the jQuery-Plugin ‚jSignature‘

In one of my current projects it is one of the goals that the members of the field staff have the possibility to sign a report directly on their iPad. After some research I found the very cool jQuery plugin jSignature, which easily allows to add a signature capture field to a XPage.

The plugin is very easy to use: Just add a <div> to your XPage and initialize the plugin with the .jSignature() constructor and that’s it! The API is very simple, but provides everything needed: The captured signatures can be stored in different formats like PNG or SVG or as native Vector. Additionally they can be encoded as BASE64 or BASE30. The data can restored as easy as they can be saved: Just one API call and it’s done.

To save the signatures to a Notes document, the resulting data can be copied to a hidden input field. In the provied example above I additionally added the format of the signature. For a better handling of the generated data I decided to store the data in the SVG format, because this allows to save it directly in standard notes text field (without having problems because of the 32 K limit). This works well and the data can be displayed in standard browsers without any problems. Only in XPiNC this will not work, because the SVG format is not supported. PDFs doesn’t support SVG too, that’s why I created a converted agent using the Apache Batik framework.

I will add a demo database asap. A description of parameters to customize the plugin can be found here.

P.S. The compressed Version is not running on teamstudio Unplugged. You have to use the uncompressed Version. The example uses the x$ function of Mark Roden.

 XPage example

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:this.resources>
        <xp:script src="/x$.js" clientSide="true"></xp:script>
        <xp:script src="/jSignatureHandler.js" clientSide="true" />
        <xp:styleSheet href="/jSignature.css"></xp:styleSheet>
    </xp:this.resources>
    <xp:this.data>
        <xp:dominoDocument var="documentSig" 
           formName="frmSignature" />
    </xp:this.data>

    <script src="js/jquery-1.8.1.min.js" />
    <script src="js/jSignature/jSignature.min.js" />

    <xp:div id="jSignature" style="width:400.0px"></xp:div>
    <xp:scriptBlock id="scriptBlockSignature">
        <xp:this.value><![CDATA[
        $(document).ready(function() { 
            sigHandler.init( "#{id:jSignature}", 
               "#{id:inputHiddenSignatureData}", 
               "#{id:inputHiddenSignatureFormat}" );
        }
        )]]></xp:this.value>
    </xp:scriptBlock>

    <xp:button id="button1" value="Reset">
        <xp:eventHandler event="onclick" submit="false">
            <xp:this.script><![CDATA[
                sigHandler.reset();
            ]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:button id="button2" value="Save">
        <xp:eventHandler event="onclick" submit="true"
           refreshMode="complete">
            <xp:this.script><![CDATA[ 
                return sigHandler.save();
            ]]></xp:this.script>
            <xp:this.action>
                <xp:saveDocument var="documentSig" />
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

    <xp:inputHidden id="inputHiddenSignatureData" 
       value="#{documentSignature.SignatureData}" />
    <xp:inputHidden id="inputHiddenSignatureFormat"
       value="#{documentSignature.SignatureFormat}" />

</xp:view>

CSJS library „jSignatureHandler.js“

/**
 * signatureHandler
 * JS Object for handling the signature data
 * Requires jSignature jQuery plugin & special 
 * function "x$" from Mark Roden
 * 
 * @author Sven Hasselbach
 * @category JavaScript
 * @category jQuery
 * @category UI
 * @version 1.0
 */
var signatureHandler = function() {
    _module = "signatureHandler";

    _idJSignature: null; // DOM id of JSignatue DIV
    _idItemData: null; // DOM id of INPUT for signature data
    _idItemFormat: null; // DOM id of INPUT for signature format
    _objDOMJSignature: null; // handle to DOM object
    _objDOMItemData: null; // handle to DOM object
    _objDOMItemFormat: null; // handle to DOM object
    _maxSize = 32000; // max characters to store (32K limit!)
    _format = "svg"; // format used

    /**
     * set DOM id of JSignature DIV
     * @param String id
     */
    this.setJSignatureId = function( id ){
        this._idJSignature = id;
    }
    /**
     * get DOM id of JSignature DIV
     * @return String id
     */
    this.getJSignatureId = function(){
        return this._idJSignature;
    }
    /**
     * set DOM id of data item
     * @param String id
     */
    this.setItemDataId = function( id ){
        this._idItemData = id;
    } 
    /**
     * get DOM id of data item
     * @return String id
     */
    this.getItemDataId = function(){
        return this._idItemData;
    }
    /**
     * set DOM id of format item
     * @param String id
     */
    this.setItemFormatId = function( id ){
        this._idItemFormat = id;
    } 
    /**
     * get DOM id of format item
     * @return String id
     */
    this.getItemFormatId = function(){
        return this._idItemFormat;
    }

    /**
     * get handle to DOM object of JSignature DIV
     * @return Object
     */
    this.getJSignatureDOMObj = function(){
        return this._objDOMJSignature;
    }
    /**
     * set handle to DOM object of JSignature DIV
     * @param Object
     */
    this.setJSignatureDOMObj = function( obj ){
        this._objDOMSignature = obj;
    }
    /**
     * get handle to DOM object of data item
     * @return Object
     */
    this.getItemDataDOMObj = function(){
        return this._objDOMItemData;
    }
    /**
     * set handle to DOM object of data item
     * @param Object
     */
    this.setItemDataDOMObj = function( obj ){
        this._objDOMItemData = obj;
    }
    /**
     * get handle to DOM object of format item
     * @return Object
     */
    this.getItemFormatDOMObj = function(){
        return this._objDOMItemFormat;
    }
    /**
     * set handle to DOM object of format item
     * @param Object
     */
    this.setItemFormatDOMObj = function( obj ){
        this._objDOMItemFormat = obj;
    }

    /**
     * initialize object
     * 
     * @param String id of jSignature DIV
     * @param String id of data item INPUT
     * @param String id of format item INPUT
     * 
     */
    this.init = function( idJSig, idItemData, idItemFormat ){
        try{

            // init jSignature
            this._idJSignature = idJSig;
            this._objDOMSignature = x$( this._idJSignature ) ;
            this._objDOMSignature.jSignature();
            // init data item
            this._idItemData = idItemData;
            this._objDOMItemData = x$( this._idItemData );

            // init format item
            this._idItemFormat = idItemFormat;
            this._objDOMItemFormat = x$( this._idItemFormat );

            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }

    /**
     * reset jSignature
     */
    this.reset = function(){
        try{
            this._objDOMSignature.jSignature("reset");
            return true;
        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
    /**
     * saves the data from jSignature
     * 
     */
    this.save = function(){
        try{
            var datapair =  this._objDOMSignature.jSignature( "getData", _format );
            var format = "data:" + datapair[0];
            var data = datapair[1];
            // check max size!
            if( data.length >  _maxSize){
                alert( "The size of the signature is too large. Please retry!" );
                return false;
            }

            this._objDOMItemData.val( data );

            this._objDOMItemFormat.val( format )
            return true;

        }catch(e){
            var errMsg = _module + "::" + arguments.callee.name + "\n";
            for( p in e ){
                errMsg += p + ": '"  + e[p] + "'\n";
            }
            console.error( "Error!\n\n" + errMsg );
            return false;
        }
    }
}

// init JS instance
sigHandler = new signatureHandler();

EDIT:
Ursus created a demo database. You can find it here.

Veröffentlicht unter Java, Java Script, Mobile, Web, XPages | Verschlagwortet mit , , , , , , , , , | 10 Kommentare

Quick-n-Dirty: Development Helper for Unplugged

I started today testing/evaluating Teamstudio Unplugged for a customer project. The first thing I missed during developement was a button to sync the current page directly to see my changes on the fly. That’s why I created this small custom control:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <table border="1" width="100%" cellpadding="2">
      <tr>
         <td colspan="3" align="middle">
            <b>Unplugged Development Helper</b>
         </td>
     </tr>
     <tr>
        <td align="middle">
           <input type="button"
              onclick="window.location='/unpws.unp/'" 
              name="Workspace" value="Workspace" />
         </td>
         <td>&#160;</td>
         <td align="middle">
            <input type="button"
              onclick="$.get('/unpws.unp/ajaxreplicate.xsp', function(data){ location.reload(); });"
              name="Sync" value="Sync!" />
         </td>
      </tr>
   </table>   
</xp:view>

The Workspace Button brings you to the workspace, the Sync button starts the replication and reloads the current page.

Veröffentlicht unter HTML, Java Script, Mobile | Verschlagwortet mit , , , , | Schreib einen Kommentar

XPages: High Performance Applications

During the last months I worked on a high performance XPages application used by a lot of end users.  To get a better data throughput, I decided to use a reverse proxy for load balancing, caching of ressources, SSL connections etc.

For static resources I am using some „special“ domains: This means that the browser is allowed to do more HTTP requests at once to the same NSF. If you have for example some images in your database which are reachable from outside via http://www.example.com/mydb.nsf/image.gif, this can be changed to http://static.example.com/mydb.nsf/image.gif (Here you can find a list of best practices).

I solved the problem of multiple execution during the JSF lifecycle  by checking if the request has already actual data (details can be found here – German only), but there was still a problem: Everytime a XPage wants some data to display, a query is sent to the domino server. This is nice if your application requires data in real-time, but not for a normal application – it kills the user experience.

This is why I searched a way which easily allows to implement memory cached database queries for domino databases.  The main idea is that the database query is no longer send directly to the backend . Instead, the request is made against a JSON wrapper, and the request to this wrapper (an agent residing in the same NSF) is done via a proxy. This allows a full control of the requested data.

The browser sends the HTTP request to the XPage and receives the response. The XPage queries the MemCache for data; if the data is not in the cache, the proxy queries the data storage (the domino database) and caches the result. The XPage has to parse the JSON data only, and this boosts the performance in my test environment for about 250-300%.

By „stealing“ the session cookie, the database query to the backend database will be done in the context of the user; the security for domino databases is not influenced.

In my solution, I am using Apache 2.2 as reverse proxy. The following modules are additionally enabled for this solution:

  • cache_module
  • deflate_module
  • expires_module
  • headers_module
  • mem_cache_module
  • proxy_module
  • proxy_http_module
  • rewrite_module
  • setenvif_module

The virtual host configuration looks like this:

<VirtualHost *:8080>
 ServerName localhost

 # Enable reverseproxy
 ProxyRequests Off
 ProxyPreserveHost On
 <Proxy *>
  AddDefaultCharset off
  Order allow,deny
  Allow from all
 </Proxy>

 # Proxy config for Domino server
 # 
 ProxyPass / http://localhost:80/
 ProxyPassReverse / http://localhost:80/

  # prevent max-age calculation from Last-Modified
  # prevent If-Modified-Since requests
  # reduces the number of requests that hit the server
 <LocationMatch "/.*$">
  Header unset Last-Modified
  Header unset ETag
  Header unset HTTP_CACHE_CONTROL
 </LocationMatch>

 # MemCache Config
 # 
 CacheEnable mem /
 CacheEnable mem http://

 # Cache-Size 80 MB
 MCacheSize 81920
 MCacheMaxObjectCount 8192

 # Min Obj. Size 1 Byte
 MCacheMinObjectSize 1

 # Max Obj. Size 1 MB
 MCacheMaxObjectSize 1000000

 # cache for 60 seconds by default
 CacheDefaultExpire 60

 # FORCE caching for all documents (without Cache-Control: no-cache)
 CacheIgnoreNoLastMod On

 # force caching for all requests
 # ignore client side Cache-Control header
 CacheIgnoreCacheControl On
 # don't add Set-Cookie header to cache
 CacheIgnoreHeaders Set-Cookie

 # Add expires headers for images, css & js files
 # reduces the number of requests that hit the server
 ExpiresActive On
 ExpiresByType domino/json A600

</VirtualHost>

As you can see, the proxy runs on port 8080, and I have added a special content type „domino/json„. This makes it easier to identify the relevant data.

This is the XPage the user accesses:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
  xmlns:xc="http://www.ibm.com/xsp/custom">

    <xp:pager layout="Previous Group Next" partialRefresh="true"
        id="pager1" for="repeat1">
    </xp:pager>

    <xp:inputText id="inputSearch" value="#{sessionScope.searchFor}" />

    <xp:button value="Label" id="button1">
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete">
        </xp:eventHandler>
    </xp:button>

    <xp:repeat id="repeat1" rows="30" var="rowData">
        <xp:this.value><![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util );

            var sessionId = null;
            try{
                 sessionId = cookie.get("DomAuthSessId").getValue();
            }catch(e){}

            var url = "http://localhost:8080/Data.nsf/DoSearch?OpenAgent";
            url += "&sessionId=" + sessionId;

            if( sessionScope.get("searchFor") !== null ){
                if( sessionScope.get("searchFor") !== "" )
                    url += "&search="; 
                    url += java.net.URLEncoder.encode(sessionScope.get("searchFor"),"UTF-8");
            }

            var data = ch.hasselba.xpages.util.URLReader.read( url, sessionId );
            var parsed = null;
            try{
                 parsed = fromJson(data).data;
            }catch(e){}
            parsed
            }]]>
        </xp:this.value>
        <xp:text escape="true" id="computedField1" value="#{javascript:rowData.LastName}">
        </xp:text>
        &#160;
        <xp:text escape="true" id="computedField2" value="#{javascript:rowData.FirstName}">
        </xp:text>
        <xp:br />
    </xp:repeat>

</xp:view>

The Java class used is really simple, I know there are better ways to do a Http request, but this is a proof of concept.

package ch.hasselba.xpages.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

/**
 * URLReader
 * performs a HTTP request
 * 
 * @author Sven Hasselbach
 * @category URL
 * @category Proxy
 * @version 0.2
 */
public class URLReader {

    // Constants
    private final static String PROPERTY_COOKIE_NAME = "Cookie";
    private final static String PROPERTY_DOMAUTHSESSID_VALUE = "DomAuthSessId=";

    /**
     * reads data from a given URL
     * 
     * @param pURL URL to load data from
     * @param pSessionId session data for doing a request in the current user context
     * @return String containg the result of the http request
     * @author Sven Hasselbach
     * @category URL
     * @category Proxy
     * @version 0.2
     */
    public static String read( final String pURL, final String pSessionId ){
        String data = null;

        try{
            // init the URL connection
            URL url = new URL( pURL );
            URLConnection uc = url.openConnection();

            // "steal" the original user session cookie
            if( !("".equals(pSessionId)))
                    uc.setRequestProperty ( PROPERTY_COOKIE_NAME ,
                       PROPERTY_DOMAUTHSESSID_VALUE + pSessionId);

            // do the HTTP request
            BufferedReader in = new BufferedReader( 
               new InputStreamReader( uc.getInputStream() ));

            // process the data returned 
            StringBuffer strBuf = new StringBuffer();
            String tmpStr = "";
            while((tmpStr = in.readLine()) != null ){
                strBuf.append( tmpStr );
            }
            data = strBuf.toString();

        }catch(Exception e){
            e.printStackTrace();
        }

        return data;
    }
}

And here comes the JSON handler, a simple Lotus Script agent:

Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim isFirst As Boolean
    Dim contextDoc As NotesDocument
    Dim hlp
    Set contextDoc = session.Documentcontext
    Set db = session.Currentdatabase

    ' get the search string from the URL or use the default search
    hlp = Split( contextDoc.QUERY_STRING_DECODED(0), "search=" )
    If UBound( hlp ) = 0 Then
        Set dc = db.Ftsearch("[FirstNAME] CONTAINS AARON", 0)
    Else
        Set dc = db.Ftsearch(hlp(1), 0)
    End If

    ' create the JSON output    
    isFirst = true
    Set doc = dc.Getfirstdocument()

    ' special content type domino/json
    Print |Content-type: domino/json|
    Print
    Print |{"data":[|
    While Not doc Is Nothing
        If Not isFirst Then Print ","
        Print |{"LastName":"| & doc.LastName(0) & _
        |","FirstName":"| & doc.FirstName(0) & |"}|
        isFirst = False
        Set doc = dc.Getnextdocument( doc )
    Wend

    Print |]}|

End Sub

In the next days I will provide a sample database and add more details. A database with 300k test datasets will be added too.

Veröffentlicht unter Java, Java Script, Lotus Script, Performance, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , , | 8 Kommentare

XPages: The Outputstream and binary data

As wiritten in the comments, Verne is correct. I just missed the „facesContext.responseComplete()“ in the beforeRenderResponse event.

If you want to get control over the outputstream of a XPage, you can use the response object from the ExternalContext:

var response = facesContext.getExternalContext().getResponse()

This will give you access to an object of type com.ibm.xsp.webapp.XspHttpServletResponse which allows some basic operations, but the response will always be encoded to UTF-8. You can not return any binary data directly.

But if you access the underlying LCDAdapterHttpServletResponse directly, it is possible to get the full control for the outputstream.

var exCon = facesContext.getExternalContext();
var response = exCon.getResponse().getDelegate();

This allows you to send any data you want to the browser. Here is an example for a JPEG rendered directly in the XPage:

1. The XPage

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="true">

   <xp:this.afterRenderResponse>
      <![CDATA[#{javascript:
         importPackage( ch.hasselba.xpages.demo );
         var exCon = facesContext.getExternalContext();
         var response = exCon.getResponse().getDelegate();
         ch.hasselba.xpages.demo.JPEGGenerator.generate( response );
      }]]>
   </xp:this.afterRenderResponse>

</xp:view>

 

2. The Java object to generate a JPEG

package ch.hasselba.xpages.demo;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import com.sun.image.codec.jpeg.ImageFormatException;
import com.sun.image.codec.jpeg.JPEGCodec;
import java.awt.Color;
import java.awt.Font;

/**
 * JPEG Generator for a simple demonstration of controlling the
 * HttpServletResponse output
 * 
 * @author Sven Hasselbach
 * @category Demo
 * @category Servlet
 * @category Image
 * @version 1.0
 */

public class JPEGGenerator {

    private static final long serialVersionUID = 1L;
    private static final int WIDTH = 800;
    private static final int HEIGHT = 200;
    private static final Color BACKGROUND_COLOR = new Color(224, 224, 224);
    private static final Color COLOR = new Color(0, 0, 0);
    private static final Font FONT = new Font("Times New Roman", Font.BOLD, 46);

    /**
     * generates a JPEG image and sends the result to the outputstream of the
     * HttpServlet
     * 
     * @param response
     *            HttpServletResponse
     * @author Sven Hasselbach
     * @version 1.0
     */
    public static void generate(final HttpServletResponse response) {
        ServletOutputStream out = null;
        try {

            // set the content type for JPEG
            response.setContentType("image/jpg");

            // get the ouput stream
            out = response.getOutputStream();

            // generate a image to convert
            BufferedImage img = createImage();

            // convert image to jpeg and send to output
            JPEGCodec.createJPEGEncoder(out).encode(img);

        } catch (ImageFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * creates a simple "Hello World" image
     * 
     * @return BufferedImage
     * @author Sven Hasselbach
     * @version 1.0
     */
    public static BufferedImage createImage() {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
                BufferedImage.TYPE_BYTE_INDEXED);
        Graphics graphics = image.getGraphics();
        graphics.setColor(BACKGROUND_COLOR);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
        graphics.setColor(COLOR);
        graphics.setFont(FONT);
        graphics.drawString("Hello World!", 10, HEIGHT / 2);

        return image;
    }

}

There is no need for splitting this example in a separated Java class. You can directly access the output in SSJS and send your binary data directly via write method of the ServletOutputStream.

Veröffentlicht unter Java, Server, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , | 7 Kommentare

Security: Another XSS Vulnerability in Domino

Stephan Wissel wrote about a XSS vulnerabilty for Domino servers (< 8.5.4) and in his post you will get an advise how to protect your domino server against this attack. Thanks for this! Works great!

But there is still a problem with another URL pattern:

*/xsp/.ibmmodres/*

This resolves resources from databases, that’s why it only works in a database URL. But normally domcgf.nsf is reachable from outside.

Update:

The blog post was updated on wissel.net. Please update your server configuration!

Veröffentlicht unter Infrastruktur, Java Script, Security, Server, Web, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

SSJS: Execute remote SSJS Code

I have created a small helper class to run SSJS code from a remote server. The basic idea behind this class is a question on stackoverflow: http://stackoverflow.com/questions/12054733/include-jss-file-from-notes-document-as-resource

As far as I know there is no way to add a SSJS resource via the src attribute, this won’t work:

<xp:this.resources>
   <xp:script src="http://localhost:8080/test.jss"
      clientSide="false" />
</xp:this.resources>

It will always fail, even if the file is available, has the correct file extension etc.

That’s why I wrote the code, it’s only a proof of concept. There are no security features to protect against manipulations, no caching for better performance and whatever.

Here is a demo XPage to demonstrate how to use it:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util.ssjs );
            SSJSUtil.executeSSJSFromURL("http://localhost:8080/test.jss");
            test();
         }]]>
      </xp:this.value>
   </xp:label>
</xp:view>

The method executeSSJSFromURL loads a text file from the given URL, creates a method binding with the content and invokes it. Then, the SSJS code is executed directly – all functions, objects and variables defined in the remote code are ready to use from now on. As you can see above, the method test() is called which is defined in the remote SSJS file.

And here is the Java code:

package ch.hasselba.xpages.util.ssjs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.ibm.xsp.util.StreamUtil;
import com.ibm.xsp.page.compiled.ExpressionEvaluatorImpl;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

/**
 * SSJSUtil
 * 
 * helper class for SSJS operations
 * 
 * @author Sven Hasselbach
 * @version 1.0.2
 * @category SSJS
 * @category Utility
 */

public class SSJSUtil {

    private static final String NEWLINE = "\n";
    private static final String SSJS_EXPRESSION_BEGIN = "#{javascript:";
    private static final String SSJS_EXPRESSION_END = "}";

    /**
     * Loads SSJS code from a given URL and executes it
     * Declared methods and objects are reachable for other SSJS code
     * 
     * @param url of the SSJS code
     * @return Object resulting object from SSJS execution
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static Object executeSSJSFromURL( final String url ){
        return execute( loadFromURL( url ) );
    }

    /**
     * loads a URL stream and converts it to a string
     * @param url of the resource
     * @return String containing the data loaded from given url
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static String loadFromURL( final String url ){
        String ret = null;
        try{
            FacesContext fc = FacesContext.getCurrentInstance();
            InputStream in = StreamUtil.getInputStream(fc, url);
            ret = inputStreamToString( in );
        }catch(Exception e){
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * executes given SSJS code and returns the result (if any)
     * functions / libraries are added to runtime 
     * 
     * @param ssjsCode code to execute
     * @return resulting object
     * @author Sven Hasselbach
     * @version 1.0.2
     * @category SSJS
     */
    public static Object execute( final String ssjsCode ){
        Object ret = null;

        try{
            String valueExpr = SSJS_EXPRESSION_BEGIN + ssjsCode + SSJS_EXPRESSION_END;
            FacesContext fc = FacesContext.getCurrentInstance();
            ExpressionEvaluatorImpl evaluator = new ExpressionEvaluatorImpl( fc );
            ValueBinding vb = evaluator.createValueBinding( fc.getViewRoot(), valueExpr, null, null);
            ret = vb.getValue(fc);
        }catch(Exception e){
            e.printStackTrace();
        }
        return ret;
    }

    /**
     * converts the data from a given inputstream to a string
     * 
     * @param in InputStream to convert
     * @return String containing data from input stream
     * @throws IOException
     * @author Sven Hasselbach
     * @version 1.0.1
     * @category Utility
     */
    public static String inputStreamToString(final InputStream inStream) throws IOException {
        BufferedReader bufReader = new BufferedReader( new InputStreamReader(inStream) );
        StringBuilder strBuilder = new StringBuilder();
        String line = null;

        while ((line = bufReader.readLine()) != null) {
            strBuilder.append(line);
            strBuilder.append( NEWLINE );
        }
        bufReader.close();

        return strBuilder.toString();
     }
}

By the way: You cannot use the import method in the remote code.

Veröffentlicht unter Java, Java Script, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , | 3 Kommentare

XPages: Access a datasource from one custom control in another one

On stackoverflow.com, a very interesting question was asked: How can you access a document datasource from one custom control in another custom control. And here comes my solution in a small SSJS function.

First you have to add an id to the custom control which contains the datasource. This gives you a handle for other custom controls:

<xc:ccDS1 id="idDSComponent"></xc:ccDS1>

Now it is possible to get the custom control by using the SSJS function getComponent(). The datasources are stored in the data attribute of the custom control. You just have to iterate through them and check every entry for the name you are looking for.

And here is the code snippet to access the datasource in another custom control:

/***
 * getDatasource
 * Resolves a datasource from a custom control
 * 
 * @param componentId    id of the component containing the datasource
 * @param dsName    name of the datasource in the component
 * @author Sven Hasselbach
 */
 function getDatasource( componentId:String, dataSourceName:String ):com.ibm.xsp.model.domino.DominoDocumentData {
    try{
       var data:java.util.ArrayList = getComponent( componentId ).getAttributes().get("data");
       if( data == null )
          return null;
                    
       var it:java.util.Iterator = data.iterator();
       var obj = null;
       while( it.hasNext() ){
          obj = it.next();
          if( obj.getVar() == dataSourceName )
             return obj;
       }
    }catch(e){
       print( e );
    }
}

To use the function you have to use it like this:

getDatasource( "idDSComponent", "document1" )

Here is the link to the original question: How to get document datasource in another Custom Control?

Veröffentlicht unter Java Script, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , | 2 Kommentare

XPages: Access resources and their content (2)

By using the same code as described in the previous article you can access the complied java classes. To get the correct path you have to do the following:

1. Select the java element you want to access

2. Have a look inside the „$ClassIndexItem“ field

3. This is the path of the compiled Java class:

4. Change the variable url to the specific path

var url = "WEB-INF/classes/ch/hasselba/jsf/servlet/MyServletFactory.class";

The first 30 Bytes of the resulting output are for internal use only and have to be skipped. They contain header informations like the length of the java class. The compiled java class starts from &HCAFEBABE.

It is even possible to access the generated XPages code. In this case there are multiple classes in a design document. For the XPage „Demo.xsp“ the two generated classes are:

var url = "WEB-INF/classes/xsp/Demo.class"

and

var url = "WEB-INF/classes/xsp/Demo$DemoPage.class"
Veröffentlicht unter Java, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | Schreib einen Kommentar