Package er.attachment

Source Code of er.attachment.ERAttachmentRequestHandler$Delegate

package er.attachment;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WODynamicURL;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WORequestHandler;
import com.webobjects.appserver.WOResponse;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOGlobalID;
import com.webobjects.eocontrol.EOKeyGlobalID;
import com.webobjects.foundation.NSLog;

import er.attachment.model.ERAttachment;
import er.attachment.processors.ERAttachmentProcessor;
import er.extensions.eof.ERXEC;
import er.extensions.eof.ERXEOGlobalIDUtilities;
import er.extensions.foundation.ERXStringUtilities;

/**
* ERAttachmentRequestHandler is the request handler that is used for loading
* any proxied attachment.  To control security, you can set the delegate of this
* request handler in your application constructor.  By default, all proxied
* attachments are visible.
*
* @author mschrag
*/
public class ERAttachmentRequestHandler extends WORequestHandler {
  public static final String REQUEST_HANDLER_KEY = "attachments";
  public static final Logger log = Logger.getLogger(ERAttachmentRequestHandler.class);

  /**
   * The delegate definition for this request handler.
   */
  public static interface Delegate {
    /**
     * Called prior to displaying a proxied attachment to a user and can be used to implement
     * security on top of attachments.
     *
     * @param attachment the attachment that was requested
     * @param request the current request
     * @param context the current context
     * @return true if the current user is allowed to view this attachment
     */
    public boolean attachmentVisible(ERAttachment attachment, WORequest request, WOContext context);
  }

  private ERAttachmentRequestHandler.Delegate _delegate;

  /**
   * Sets the delegate for this request handler.
   *
   * @param delegate the delegate for this request handler
   */
  public void setDelegate(ERAttachmentRequestHandler.Delegate delegate) {
    _delegate = delegate;
  }

  @Override
  public WOResponse handleRequest(WORequest request) {
    int bufferSize = 16384;

    WOApplication application = WOApplication.application();
    application.awake();
    try {
      WOContext context = application.createContextForRequest(request);
      WOResponse response = application.createResponseInContext(context);

      String sessionIdKey = application.sessionIdKey();
      String sessionId = (String) request.formValueForKey(sessionIdKey);
      if (sessionId == null) {
        sessionId = request.cookieValueForKey(sessionIdKey);
      }
      context._setRequestSessionID(sessionId);
      if (context._requestSessionID() != null) {
        application.restoreSessionWithID(sessionId, context);
      }

      try {
      final WODynamicURL url = request._uriDecomposed();
        final String requestPath = url.requestHandlerPath();
        final Matcher idMatcher = Pattern.compile("^id/(\\d+)/").matcher(requestPath);

        final Integer requestedAttachmentID;
        String requestedWebPath;

        final boolean requestedPathContainsAnAttachmentID = idMatcher.find();
    if (requestedPathContainsAnAttachmentID) {
          requestedAttachmentID = Integer.valueOf(idMatcher.group(1));
          requestedWebPath = idMatcher.replaceFirst("/");
        } else {
          // MS: This is kind of goofy because we lookup by path, your web path needs to
          // have a leading slash on it.
          requestedWebPath = "/" + requestPath;
          requestedAttachmentID = null;
        }


        try {
          InputStream attachmentInputStream;
          String mimeType;
          String fileName;
          long length;
          String queryString = url.queryString();
          boolean proxyAsAttachment = (queryString != null && queryString.contains("attachment=true"));

          EOEditingContext editingContext = ERXEC.newEditingContext();
          editingContext.lock();

          try {
            ERAttachment attachment = fetchAttachmentFor(editingContext, requestedAttachmentID, requestedWebPath);
           
            if (_delegate != null && !_delegate.attachmentVisible(attachment, request, context)) {
              throw new SecurityException("You are not allowed to view the requested attachment.");
            }
            mimeType = attachment.mimeType();
            length = attachment.size().longValue();
            fileName = attachment.originalFileName();
            ERAttachmentProcessor<ERAttachment> attachmentProcessor = ERAttachmentProcessor.processorForType(attachment);
            if (!proxyAsAttachment) {
              proxyAsAttachment = attachmentProcessor.proxyAsAttachment(attachment);
            }
            InputStream rawAttachmentInputStream = attachmentProcessor.attachmentInputStream(attachment);
            attachmentInputStream = new BufferedInputStream(rawAttachmentInputStream, bufferSize);
          } finally {
            editingContext.unlock();
          }
         
          response.setHeader(mimeType, "Content-Type");
          response.setHeader(String.valueOf(length), "Content-Length");

          if (proxyAsAttachment) {
            response.setHeader("attachment; filename=\"" + fileName + "\"", "Content-Disposition");
          }

          response.setStatus(200);
          response.setContentStream(attachmentInputStream, bufferSize, length);
        }
        catch (SecurityException e) {
          NSLog.out.appendln(e);
          response.setContent(e.getMessage());
          response.setStatus(403);
        }
        catch (NoSuchElementException e) {
          NSLog.out.appendln(e);
          response.setContent(e.getMessage());
          response.setStatus(404);
        }
        catch (FileNotFoundException e) {
          NSLog.out.appendln(e);
          response.setContent(e.getMessage());
          response.setStatus(404);
        }
        catch (IOException e) {
          NSLog.out.appendln(e);
          response.setContent(e.getMessage());
          response.setStatus(500);
        }

        return response;
      }
      finally {
        if (context._requestSessionID() != null) {
          WOApplication.application().saveSessionForContext(context);
        }
      }
    }
    finally {
      application.sleep();
    }
  }


  /**
   *
   *
   * @param editingContext
   *        the {@link EOEditingContext} that the result will be inserted into
   * @param attachmentPrimaryKey
   *        the primaryKey value of an existing ERAttachment in the database
   * @param requestedWebPath
   *        a URL-encoded portion of the requested ERAttachment path including the file name of the attachment
   * @return an attachment that matches either both the {@code attachmentPrimaryKey} and the {@code requestedWebPath},
   *         or just the {@code reqestedWebPath}. If it is null then we throw a SecurityException.
   *
   * @author davendasora
   * @since Apr 25, 2014
   */
  public static ERAttachment fetchAttachmentFor(final EOEditingContext editingContext, final Integer attachmentPrimaryKey, final String requestedWebPath) {
    ERAttachment attachment;
    if (attachmentPrimaryKey != null) {
      final EOGlobalID gid = EOKeyGlobalID.globalIDWithEntityName(ERAttachment.ENTITY_NAME, new Object[] {(attachmentPrimaryKey)});
      attachment = (ERAttachment) ERXEOGlobalIDUtilities.fetchObjectWithGlobalID(editingContext, gid);

      /*
       * Ensure the attachment request is a legitimate one by comparing the attachment's webPath to the
       * requestedWebPath.
       */
      final boolean requestedWebPathIsInvalid = !ERAttachmentRequestHandler.requestedWebPathIsForAttachment(requestedWebPath, attachment);
      if (requestedWebPathIsInvalid) {
        throw new SecurityException("You are not allowed to view the requested attachment.");
      }
    } else {
      /*
       * Aaron Rosenzweig April 25, 2014
       * WARNING: This is partially broken on an edge case and no easy fix is available. Any true fix would break
       * current WOnder users of ERAttachment. Short version: We cannot URLDecode the column in the database efficiently.
       * See details below:
       *
       * If the webPath value that is stored in the database (actualWebPath) contains "%20" (or any other %nn
       * code) the following fetch will not find it because the requestedWebPath needs to be decoded, which will
       * remove any occurrences of "%20" from it, causing it to no longer match the actualWebPath value.
       */
      String decodedRequestedWebPath;
      try {
        decodedRequestedWebPath = new URI(requestedWebPath).getPath();
        attachment = ERAttachment.fetchRequiredAttachmentWithWebPath(editingContext, decodedRequestedWebPath);
      } catch (URISyntaxException exception) {
        attachment = null;
        exception.printStackTrace();
      }
     
      if (attachment == null) {
        throw new SecurityException("You are not allowed to view the requested attachment.");
      }
    }
    return attachment;
  }


  /**
   * Takes into account potential URL encoding differences between the {@code requestedWebPath} and the
   * {@code attachment}'s {@link ERAttachment#webPath() webPath()} attribute. e.g., "/the/web/path/My Attachment.jpg"
   * will match "/the/web/path/My%20Attachment.jpg"
   *
   * @param requestedWebPath
   *        a String to compare to the {@code attachment} parameter's
   * @param attachment
   *        an ERAttachment
   * @return {@code true} if the {@code requestedWebPath} matches {@code attachment.webPath()}. {@code false} if not.
   *
   * @author davendasora
   * @since Apr 25, 2014
   */
  public static boolean requestedWebPathIsForAttachment(final String requestedWebPath, final ERAttachment attachment) {
    final String actualWebPath = attachment.webPath();
    /*
     * We are using the form-data decoder (URLDecoder.decode(String)) instead of the more appropriate URI#getPath()
     * because we only need to ensure that both webPath values decode identically. We can't use URI.getPath() here
     * because the webPath value stored in the database (actualWebPath) may contain illegal values (spaces, etc.)
     */
    final String decodedActualWebPath = ERXStringUtilities.urlDecode(actualWebPath);
    final String decodedRequestedWebPath = ERXStringUtilities.urlDecode(requestedWebPath);

    /*
     * Aaron Rosenzweig - April 24, 2014 - Because the attachment may have been originally uploaded with "%20" (or
     * another %nn value) already in the file name, we need to compare both the stored decoded (actualWebPath)
     * against the decoded requested value (requestedWebPath) otherwise we could incorrectly throw a SecurityException.
     */
    final boolean requestedWebPathMatchesTheAttachmentWebPath = decodedRequestedWebPath.equals(decodedActualWebPath);
    return requestedWebPathMatchesTheAttachmentWebPath;
  }
}
TOP

Related Classes of er.attachment.ERAttachmentRequestHandler$Delegate

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.