content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.chemistry.opencmis.server.impl.browser;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.math.BigInteger;
  import java.net.URLDecoder;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  
  import javax.servlet.http.HttpServletRequest;
  
  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
  import org.apache.chemistry.opencmis.commons.impl.Constants;
  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
  import org.apache.chemistry.opencmis.server.shared.HttpUtils;
  import org.apache.chemistry.opencmis.server.shared.QueryStringHttpServletRequestWrapper;
  import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
  
  public final class POSTHttpServletRequestWrapper extends QueryStringHttpServletRequestWrapper {
  
      public static final String FORM_URLENCODED = "application/x-www-form-urlencoded";
      private static final int MAX_CONTENT_BYTES = 10 * 1024 * 1024;
      private static final int BUFFER_SIZE = 64 * 1024;
      private static final String CHARSET_FIELD = "_charset_";
  
      private String filename;
      private String contentType;
      private BigInteger size;
      private InputStream stream;
  
      public POSTHttpServletRequestWrapper(HttpServletRequest request, TempStoreOutputStreamFactory streamFactory)
              throws IOException {
          super(request);
  
          if (MultipartParser.isMultipartContent(request)) {
              // multipart processing
              MultipartParser parser = new MultipartParser(request, streamFactory);
              parser.parse();
  
              if (parser.hasContent()) {
                  filename = parser.getFilename();
                  contentType = parser.getContentType();
                  size = parser.getSize();
                  stream = parser.getStream();
              }
  
              for (Map.Entry<String, String[]> e : parser.getFields().entrySet()) {
                  addParameter(e.getKey(), e.getValue());
              }
  
              String filenameControl = HttpUtils.getStringParameter(this, Constants.CONTROL_FILENAME);
              if (filenameControl != null && filenameControl.trim().length() > 0) {
                  filename = filenameControl;
              }
  
              String contentTypeControl = HttpUtils.getStringParameter(this, Constants.CONTROL_CONTENT_TYPE);
              if (contentTypeControl != null && contentTypeControl.trim().length() > 0) {
                  contentType = contentTypeControl;
              }
          } else if (isFormUrlencodedContent(request)) {
              // form data processing
              if (!parseFormUrlEncodedData(request)) {
                  parameters.putAll(request.getParameterMap());
              }
          } else {
              // spec incompliant form encoding
              throw new CmisInvalidArgumentException("Invalid form encoding!");
          }
      }
  
      public String getFilename() {
          return filename;
      }
  
      @Override
      public String getContentType() {
          return contentType;
      }
  
      public BigInteger getSize() {
          return size;
      }
  
      public InputStream getStream() {
          return stream;
      }
  
      /**
       * Parses a form data request
       * 
       * @param request
       *            the request
       * @return {@code true} if the body contained data, {@code false} otherwise
       */
      protected boolean parseFormUrlEncodedData(HttpServletRequest request) throws IOException {
          byte[] data = new byte[BUFFER_SIZE];
          int dataPos = 0;
  
          InputStream stream = request.getInputStream();
          int b;
          byte[] buffer = new byte[BUFFER_SIZE];
  
          // read stream
          while ((b = stream.read(buffer)) != -1) {
              if (dataPos + b > MAX_CONTENT_BYTES) {
                  throw new CmisInvalidArgumentException("Limit exceeded!");
              }
  
              if (data.length - dataPos < b) {
                  // expand buffer
                  int newSize = ((data.length + b) * 2 < MAX_CONTENT_BYTES ? (data.length + b * 2) : MAX_CONTENT_BYTES);
                  byte[] newbuf = new byte[newSize];
                  System.arraycopy(data, 0, newbuf, 0, dataPos);
                  data = newbuf;
              }
  
              System.arraycopy(buffer, 0, data, dataPos, b);
              dataPos += b;
          }
  
          if (dataPos == 0) {
              // empty stream
              return false;
          }
  
          // parse parameters
          boolean parseName = true;
          boolean parseCharset = false;
          int startPos = 0;
  
          List<String[]> rawParameters = new ArrayList<String[]>();
          String rawName = null;
          String rawValue = null;
  
          String charset = null;
  
          for (int i = 0; i < dataPos; i++) {
              switch (data[i]) {
              case '=':
                  if (startPos < i) {
                      rawName = new String(data, startPos, i - startPos, IOUtils.ISO_8859_1);
                      if (CHARSET_FIELD.equalsIgnoreCase(rawName)) {
                          parseCharset = true;
                      }
                  }
  
                  parseName = false;
                  startPos = i + 1;
                  break;
  
              case '&':
                  if (parseName) {
                      if (startPos < i) {
                          rawName = new String(data, startPos, i - startPos, IOUtils.ISO_8859_1);
                          rawParameters.add(new String[] { rawName, null });
                      }
                  } else {
                      if (rawName != null) {
                          rawValue = new String(data, startPos, i - startPos, IOUtils.ISO_8859_1);
                          rawParameters.add(new String[] { rawName, rawValue });
                          if (parseCharset) {
                              charset = rawValue;
                          }
                      }
                  }
  
                  rawName = null;
                  rawValue = null;
  
                  parseName = true;
                  parseCharset = false;
                  startPos = i + 1;
                  break;
  
              default:
                  break;
              }
          }
  
          if (startPos < dataPos) {
              // there is a final parameter after the last '&'
              if (parseName) {
                  rawName = new String(data, startPos, dataPos - startPos, IOUtils.ISO_8859_1);
                  rawParameters.add(new String[] { rawName, null });
              } else {
                  if (rawName != null) {
                      rawValue = new String(data, startPos, dataPos - startPos, IOUtils.ISO_8859_1);
                      rawParameters.add(new String[] { rawName, rawValue });
                      if (parseCharset) {
                          charset = rawValue;
                      }
                  }
              }
          } else if (!parseName) {
              // the stream ended with '='
              rawParameters.add(new String[] { rawName, "" });
          }
  
          data = null;
  
          // find charset
          if (charset == null) {
              // check charset in content type
              String contentType = request.getContentType();
              if (contentType != null) {
                  String[] parts = contentType.split(";");
                  for (int i = 1; i < parts.length; i++) {
                      String part = parts[i].trim().toLowerCase(Locale.ENGLISH);
                      if (part.startsWith("charset")) {
                          int x = part.indexOf('=');
                          charset = part.substring(x + 1).trim();
                          break;
                      }
                  }
              }
          }
  
          if (charset == null) {
              // set default charset
              charset = IOUtils.UTF8;
          }
  
          // decode parameters
          for (String[] rawParameter : rawParameters) {
              String name = URLDecoder.decode(rawParameter[0], charset);
  
              String value = null;
              if (rawParameter[1] != null) {
                  value = URLDecoder.decode(rawParameter[1], charset);
              }
  
              addParameter(name, value);
          }
  
          return true;
      }
  
      /**
       * Returns if the request is a form-urlencoded request.
       */
      public static final boolean isFormUrlencodedContent(HttpServletRequest request) {
          String contentType = request.getContentType();
  
          if (contentType != null && contentType.toLowerCase(Locale.ENGLISH).startsWith(FORM_URLENCODED)) {
              return true;
          }
  
          return false;
      }
  }
  