package com.stanfy.enroscar.images;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.DisplayMetrics;
import android.util.Log;

import com.stanfy.enroscar.beans.BeansManager;
import com.stanfy.enroscar.io.FlushedInputStream;
import com.stanfy.enroscar.io.IoUtils;
import com.stanfy.enroscar.net.UrlConnectionBuilderFactory;
import com.stanfy.enroscar.net.cache.EnhancedResponseCache;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;

import static com.stanfy.enroscar.images.ImagesManager.TAG;

/**
 * Image loading request.
 * Contains information about max allowed size of an image.
 * It's the size an image will be scaled down to before storing on disk.
 */
public class ImageRequest {

  /** Mark for getting bounds info. */
  private static final int BOUNDS_INFO_MARK = 65536;
  /** Buffer size for image IO operations. */
  private static final int IMAGES_BUFFER_SIZE = IoUtils.BUFFER_SIZE_16K;

  /** Images manager. */
  private final ImagesManager manager;

  /** Image URL. */
  final String url;

  /** Maximum allowed height. */
  private final int maxAllowedWidth, maxAllowedHeight;

  /** Required size. */
  private int requiredWidth, requiredHeight;

  /** Skip scaling before memory cache. */
  private boolean skipScaleBeforeMemCache;

  /** Image format. */
  private Bitmap.Config format = Bitmap.Config.ARGB_8888;

  /**
   * @param manager images manager instance
   * @param url image URL
   * @param maxRelativeSize max allowed size of an image relatively to screen size,
   *                        negative value means allowed size will be ignored
   */
  ImageRequest(final ImagesManager manager, final String url, final float maxRelativeSize) {
    this.manager = manager;
    this.url = url;
    if (maxRelativeSize < 0) {
      maxAllowedWidth = -1;
      maxAllowedHeight = -1;
    } else {
      DisplayMetrics metrics = manager.getContext().getResources().getDisplayMetrics();
      maxAllowedWidth = (int) (metrics.widthPixels * maxRelativeSize);
      maxAllowedHeight = (int) (metrics.heightPixels * maxRelativeSize);
    }
  }

  public String getUrl() {
    return url;
  }

  public int getRequiredWidth() {
    return requiredWidth == 0 && hasAllowedSize() ? maxAllowedWidth : requiredWidth;
  }

  public void setRequiredWidth(final int requiredWidth) {
    this.requiredWidth = requiredWidth;
  }

  public int getRequiredHeight() {
    return requiredHeight == 0 && hasAllowedSize() ? maxAllowedHeight : requiredHeight;
  }

  public void setRequiredHeight(final int requiredHeight) {
    this.requiredHeight = requiredHeight;
  }

  public boolean isSkipScaleBeforeMemCache() {
    return skipScaleBeforeMemCache;
  }

  public void setSkipScaleBeforeMemCache(final boolean skipScaleBeforeMemCache) {
    this.skipScaleBeforeMemCache = skipScaleBeforeMemCache;
  }

  public void setFormat(final Bitmap.Config format) {
    this.format = format;
  }

  public String getKey() {
    return url + "!" + getRequiredHeight() + "x" + getRequiredHeight();
  }

  public String getCacheKey() {
    return url;
  }

  public boolean hasAllowedSize() {
    return maxAllowedWidth > 0 && maxAllowedHeight > 0;
  }

  /**
   * Store image to the disk cache.
   * If max allowed size is set image may be rescaled on disk.
   * @throws IOException if error happens
   */
  public void storeToDisk() throws IOException {
    if (manager.isPresentOnDisk(url)) {
      return;
    }

    if (!hasAllowedSize()) {
      IoUtils.consumeStream(getRemoteInputStream(), manager.getBuffersPool());
      return;
    }

    ImageResult result = decodeStream(getRemoteInputStream(), true);
    if (result.getType() == ImageSourceType.NETWORK && result.getBitmap() != null) {
      // image was scaled
      writeBitmapToDisk(result.getBitmap());
    }
  }

  /**
   * @return drawable with loaded image
   * @throws IOException if error happens
   */
  public ImageResult readImage() throws IOException {
    return decodeStream(getRemoteInputStream(), false);
  }

  private InputStream getRemoteInputStream() throws IOException {
    return newUrlConnection().getInputStream();
  }

  URLConnection newUrlConnection() throws IOException {
    UrlConnectionBuilderFactory builderFactory =
        BeansManager.get(manager.getContext())
            .getContainer()
            .getBean(ImagesManager.CONNECTION_BUILDER_FACTORY_NAME, UrlConnectionBuilderFactory.class);

    return builderFactory.newUrlConnectionBuilder()
        .setUrl(url)
        .setCacheManagerName(ImagesManager.CACHE_BEAN_NAME)
        .create();
  }

  private ImageResult decodeStream(final InputStream is, boolean onlyIfNeedsRescale) throws IOException {
    final BitmapFactory.Options options = createBitmapOptions();

    final InputStream src = prepareInputStream(is);

    try {

      ImageResult result = new ImageResult();
      result.setType(manager.isPresentOnDisk(url) ? ImageSourceType.DISK : ImageSourceType.NETWORK);

      // get scale factor
      options.inSampleSize = resolveSampleFactor(src, options);

      if (options.inSampleSize > 1 || !onlyIfNeedsRescale) {
        // actually decode
        result.setBitmap(doStreamDecode(src, options));
      } else {
        // consume input in order to cache it
        IoUtils.consumeStream(src, manager.getBuffersPool());
      }

      if (manager.debug) {
        Log.d(TAG, "Image decoded: " + result);
      }
      return result;

    } catch (final OutOfMemoryError e) {

      // wrap OOM in IO
      final IOException wrapped = new IOException("out of memory for " + getKey());
      wrapped.initCause(e);
      throw wrapped;

    } finally {

      recycle(options);
      src.close();

    }

  }

  Bitmap doStreamDecode(final InputStream input, final BitmapFactory.Options options) throws IOException {
    return BitmapFactory.decodeStream(input, null, options);
  }

  private BitmapFactory.Options createBitmapOptions() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inTempStorage = manager.getBuffersPool().get(IMAGES_BUFFER_SIZE);
    options.inPreferredConfig = format;
    return options;
  }

  private void recycle(final BitmapFactory.Options options) {
    manager.getBuffersPool().release(options.inTempStorage);
  }

  private InputStream prepareInputStream(final InputStream is) throws IOException {
    InputStream src = new FlushedInputStream(is);
    if (!src.markSupported()) {
      src = manager.getBuffersPool().bufferize(src, IMAGES_BUFFER_SIZE);
    }
    return src;
  }

  /**
   * @param is image input stream
   * @return sampling factor
   * @throws IOException if error happens
   */
  private int resolveSampleFactor(final InputStream is, final BitmapFactory.Options options) throws IOException {
    if (!is.markSupported()) { throw new IllegalStateException("Input stream does not support marks!"); }

    options.inJustDecodeBounds = true;
    int result = 1;
    try {

      MarkableInputStream markableStream = new MarkableInputStream(is); // Thanks to Square guys :)
      long mark = markableStream.savePosition(BOUNDS_INFO_MARK);
      doStreamDecode(markableStream, options);

      result = ImagesManager.calculateSampleFactor(options.outWidth, options.outHeight,
          getRequiredWidth(), getRequiredHeight());

      markableStream.reset(mark);

    } finally {
      options.inJustDecodeBounds = false;
    }
    return result;
  }

  void writeBitmapToDisk(final Bitmap bitmap) throws IOException {
    EnhancedResponseCache cache = (EnhancedResponseCache) manager.getImagesResponseCache();
    OutputStream output = new FileOutputStream(cache.getLocalPath(url));
    output = manager.getBuffersPool().bufferize(output, IMAGES_BUFFER_SIZE);
    try {
      final int quality = 100;
      bitmap.compress(Bitmap.CompressFormat.PNG, quality, output);
    } finally {
      IoUtils.closeQuietly(output);
    }
  }

}


package com.stanfy.enroscar.images;

import java.util.LinkedList;
import java.util.concurrent.Executor;

/** Image request builder. */
public class ImageRequestsBuilder {

  /** Images manager. */
  private final ImagesManager manager;

  /** Requests to process. */
  private final LinkedList<ImageRequest> requests = new LinkedList<ImageRequest>();

  /** Default allowed size. */
  private float defaultAllowedSize = -1;

  /** Executor instance. */
  private Executor executor;

  ImageRequestsBuilder(final ImagesManager manager) {
    this.manager = manager;
  }

  /**
   * Specify an executor to use for images loading. If it's null, loading will take place in the current thread.
   * @param executor executor instance
   * @return this for chaining
   */
  public ImageRequestsBuilder onExecutor(final Executor executor) {
    this.executor = executor;
    return this;
  }

  /**
   * Use default images manager executor to load the images.
   * @return this for chaining
   */
  public ImageRequestsBuilder onDefaultExecutor() {
    this.executor = manager.getImageTaskExecutor();
    return this;
  }

  /**
   * Store images of original size on disk.
   * @return this for chaining
   */
  public ImageRequestsBuilder storeOriginalImages() {
    defaultAllowedSize = -1;
    return this;
  }

  /**
   * Scale images to be maximum of screen size before storing on disk.
   * @return this for chaining
   */
  public ImageRequestsBuilder scaleToScreenSize() {
    defaultAllowedSize = 1;
    return this;
  }

  /**
   * Scale images to be maximum of screen size before storing on disk.
   * @param size relatively to screen size
   * @return this for chaining
   */
  public ImageRequestsBuilder scaleToScreenSize(final float size) {
    if (size <= 0) { throw new IllegalArgumentException("relative size must be bigger than 0"); }
    defaultAllowedSize = size;
    return this;
  }

  /**
   * Plan an image on loading.
   * @param url image URL
   * @return this for chaining
   */
  public ImageRequestsBuilder add(final String url) {
    requests.add(new ImageRequest(manager, url, defaultAllowedSize));
    return this;
  }

  /**
   * Plan an image on loading.
   * @param url image URL
   * @param relativeSize max allowed image size relatively to the screen size
   * @return this for chaining
   */
  public ImageRequestsBuilder add(final String url, final float relativeSize) {
    requests.add(new ImageRequest(manager, url, relativeSize));
    return this;
  }

  /**
   * Start images loading.
   */
  public void startLoading() {
    manager.ensureImages(requests, executor);
  }

}


package com.stanfy.enroscar.rest.request.net;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import com.stanfy.enroscar.beans.BeansManager;
import com.stanfy.enroscar.net.UrlConnectionBuilder;
import com.stanfy.enroscar.net.UrlConnectionBuilderFactory;
import com.stanfy.enroscar.net.UrlConnectionWrapper;
import com.stanfy.enroscar.rest.Utils;
import com.stanfy.enroscar.net.operation.OperationType;
import com.stanfy.enroscar.net.operation.Parameter;
import com.stanfy.enroscar.net.operation.ParameterValue;
import com.stanfy.enroscar.net.operation.RequestDescription;

import java.io.IOException;
import java.net.ContentHandler;
import java.net.HttpURLConnection;
import java.net.URLConnection;

/**
 * Converts request description to {@link URLConnection}.
 * @author Roman Mazur (Stanfy - http://stanfy.com)
 */
public abstract class BaseRequestDescriptionConverter {

  /** Bean name of UrlConnectionBuilderFactory. */
  public static final String CONNECTION_BUILDER_FACTORY_NAME = "enroscar.RequestConnectionBuilder";

  /** Logging tag. */
  protected static final String TAG = RequestDescription.TAG;

  /** Request description. */
  private final RequestDescription requestDescription;

  /** Context. */
  private final Context context;

  public BaseRequestDescriptionConverter(final RequestDescription requestDescription, final Context context) {
    this.requestDescription = requestDescription;
    this.context = context;
  }

  public abstract URLConnection prepareConnectionInstance() throws IOException;

  public abstract void sendRequest(final URLConnection connection) throws IOException;

  private static String opertionTypeToString(final int type) {
    switch (type) {
    case OperationType.SIMPLE_GET: return "GET";
    case OperationType.SIMPLE_POST: return "POST";
    case OperationType.UPLOAD_POST: return "POST, multipart";
    case OperationType.PAYLOAD_POST: return "POST, payload";
    default: return "unknown";
    }
  }

  public void connect(final URLConnection connection) throws IOException {
    if (Utils.isDebugRest(context)) {
      final String idPrefix = "(" + requestDescription.getId() + ") ";
      Log.d(TAG, idPrefix + "Connect to " + connection.getURL() + " <" + opertionTypeToString(requestDescription.getOperationType()) + ">");
      Log.d(TAG, idPrefix + "Headers: " + connection.getRequestProperties());
    }
    connection.connect();
  }

  /**
   * @return URL connection builder with set cache,
   */
  protected UrlConnectionBuilder prepareUrlConnectionBuilder() {
    UrlConnectionBuilderFactory factory = BeansManager.get(context).getContainer().getBean(CONNECTION_BUILDER_FACTORY_NAME, UrlConnectionBuilderFactory.class);
    if (factory == null) {
      throw new IllegalStateException("UrlConnectionBuilderFactory bean is not defined.");
    }
    final String contentHandlerName = requestDescription.getContentHandler();
    return factory.newUrlConnectionBuilder()
        .setCacheManagerName(requestDescription.getCacheName())
        .setContentHandler(BeansManager.get(context).getContainer()
            .getBean(contentHandlerName, ContentHandler.class))
        .setEntityTypeToken(requestDescription.getModelType());
  }

  /**
   * Composes URI builder using {@link RequestDescription#getUrl()} and {@link RequestDescription#getSimpleParameters()}.
   * @return request URI builder
   */
  protected Uri.Builder buildUri() {
    final Uri.Builder builder = Uri.parse(requestDescription.getUrl()).buildUpon();
    for (final Parameter p : requestDescription.getSimpleParameters().getChildren()) {
      if (p instanceof ParameterValue) {
        builder.appendQueryParameter(p.getName(), ((ParameterValue) p).getValue());
      }
    }
    return builder;
  }

  public RequestDescription getRequestDescription() {
    return requestDescription;
  }

  public Context getContext() {
    return context;
  }

  /**
   * @param connection potentially wrapped connection
   * @return unwrapped {@link HttpURLConnection} instance
   */
  protected static HttpURLConnection asHttp(final URLConnection connection) {
    return (HttpURLConnection)UrlConnectionWrapper.unwrap(connection);
  }

  /** Converter factory. */
  public interface ConverterFactory {
    /** @return converter instance */
    BaseRequestDescriptionConverter createConverter(final RequestDescription requestDescription, final Context context);
  }

}


package com.stanfy.enroscar.io;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

/**
 * Internal I/O utilities.
 * @author Roman Mazur (Stanfy - http://stanfy.com)
 */
public final class IoUtils {

  /** UTF-8 name. */
  public static final String UTF_8_NAME = "UTF-8";
  /** ASCII name. */
  public static final String US_ASCII_NAME = "US-ASCII";

  /** UTF-8 character set. */
  public static final Charset UTF_8 = Charset.forName(UTF_8_NAME);
  /** ASCII character set. */
  public static final Charset US_ASCII = Charset.forName(US_ASCII_NAME);

  /** 'gzip' string. */
  public static final String ENCODING_GZIP = "gzip";
  /** 'deflate' string. */
  public static final String ENCODING_DEFLATE = "deflate";

  /** 8K. */
  public static final int BUFFER_SIZE_8K = 8192;
  /** 16K. */
  public static final int BUFFER_SIZE_16K = 16 * 1024;

  /** End of file code. */
  private static final int EOF = -1;

  private IoUtils() { /* hidden */ }

  /**
   * Closes `closeable` ignoring any non-runtime exceptions.
   * @param closeable whatever that can be closed, may be null
   */
  public static void closeQuietly(final Closeable closeable) {
    if (closeable != null) {
      try {
        closeable.close();
      } catch (final RuntimeException rethrown) {
        throw rethrown;
      } catch (final Exception ignored) {
        // ignore
      }
    }
  }

  /**
   * Read all the bytes from input stream and convert them to a string.
   * Input stream is closed after this method invocation.
   * @param stream input stream
   * @param charset input characters set name
   * @param buffersPool buffers pool instance, may be null
   * @return string
   * @throws IOException if an error happens
   */
  public static String streamToString(final InputStream stream, final String charset, final BuffersPool buffersPool) throws IOException {
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    transfer(stream, output, buffersPool);
    return new String(output.toByteArray(), charset);
  }

  /**
   * Read all the bytes from input stream and convert them to a string using UTF-8 charset.
   * Input stream is closed after this method invocation.
   * @param stream input stream
   * @param buffersPool buffers pool instance, may be null
   * @return string
   * @throws IOException if an error happens
   */
  public static String streamToString(final InputStream stream, final BuffersPool buffersPool) throws IOException {
    return streamToString(stream, UTF_8_NAME, buffersPool);
  }

  /**
   * Transfers all the bytes from input to output stream.
   * If read/write operations are successful, output stream will be flushed.
   * Input stream is always closed after this method invocation.
   *
   * @param input input stream
   * @param output output stream
   * @param buffersPool buffers pool used to obtain a temporal bytes buffer, may be null
   * @throws IOException if an error happens
   */
  public static void transfer(final InputStream input, final OutputStream output, final BuffersPool buffersPool) throws IOException {
    final InputStream in = buffersPool == null
        ? new BufferedInputStream(input, BUFFER_SIZE_8K)
        : new PoolableBufferedInputStream(input, BUFFER_SIZE_8K, buffersPool);

    final byte[] buffer = buffersPool == null
        ? new byte[BUFFER_SIZE_8K]
        : buffersPool.get(BUFFER_SIZE_8K);

    try {
      int cnt;
      while ((cnt = in.read(buffer)) != EOF) {
        output.write(buffer, 0, cnt);
      }
      output.flush();
    } finally {
      closeQuietly(in);

      if (buffersPool != null) {
        buffersPool.release(buffer);
      }
    }
  }

  /**
   * Consume the stream and close it.
   * This implementation calls {@link InputStream#read(byte[])} method and ignores any read bytes.
   *
   * @param input input stream
   * @throws IOException if I/O error happens
   */
  public static void consumeStream(final InputStream input, final BuffersPool buffersPool) throws IOException {
    // do not use skip, just use a buffer and read it all
    final byte[] buffer = buffersPool == null
        ? new byte[BUFFER_SIZE_8K]
        : buffersPool.get(BUFFER_SIZE_8K);

    try {
      //noinspection StatementWithEmptyBody
      while (input.read(buffer) != EOF);
    } finally {
      closeQuietly(input);

      if (buffersPool != null) {
        buffersPool.release(buffer);
      }
    }

  }

  /**
   * Gets stream of uncompressed bytes for the {@link URLConnection} wrapping its input stream
   * according to what is defined in its content encoding.
   * Supported encodings: {@link #ENCODING_GZIP}, {@link #ENCODING_DEFLATE}.
   *
   * @param connection given URL connection
   * @return an uncompressed {@link InputStream} for the given {@link URLConnection}.
   * @see #getUncompressedInputStream(String, java.io.InputStream)
   */
  public static InputStream getUncompressedInputStream(final URLConnection connection) throws IOException {
    final InputStream source = connection.getInputStream();
    final String encoding = connection.getContentEncoding();
    return getUncompressedInputStream(encoding, source);
  }

  /**
   * Wraps the supplied stream into either {@link GZIPInputStream} or {@link InflaterInputStream}
   * depending on {@code encoding} parameter value.
   *
   * @param encoding content encoding, supported values: {@link #ENCODING_GZIP}, {@link #ENCODING_DEFLATE}
   * @param source source stream
   * @return the uncompressed {@link InputStream}
   * @see #getUncompressedInputStream(java.net.URLConnection)
   */
  public static InputStream getUncompressedInputStream(final String encoding, final InputStream source) throws IOException {
    if (ENCODING_GZIP.equalsIgnoreCase(encoding)) {
      return new GZIPInputStream(source);
    }
    if (ENCODING_DEFLATE.equalsIgnoreCase(encoding)) {
      final Inflater inflater = new Inflater(/*no header*/ true);
      return new InflaterInputStream(source, inflater);
    }
    return source;
  }

}


package com.stanfy.enroscar.rest;

import com.stanfy.enroscar.beans.BeansContainer;
import com.stanfy.enroscar.beans.BeansManager;
import com.stanfy.enroscar.beans.EnroscarBean;
import com.stanfy.enroscar.beans.InitializingBean;
import com.stanfy.enroscar.beans.ManagerAwareBean;
import com.stanfy.enroscar.net.operation.RequestDescription;

/**
 * Configures remote server API access classes.
 * @author Roman Mazur (Stanfy - http://stanfy.com)
 */
@EnroscarBean(RemoteServerApiConfiguration.BEAN_NAME)
public class RemoteServerApiConfiguration implements InitializingBean, ManagerAwareBean {

  /** Bean name. */
  public static final String BEAN_NAME = "enroscar.RemoteServerApiConfiguration";

  /** Bean manager instance. */
  private BeansManager beansManager;

  /** Request method. */
  private RequestMethod defaultRequestMethod;

  /** Default content handler name. */
  private String defaultContentHandlerName;

  /** Default cache bean name. */
  private String defaultCacheBeanName;

  /** Debug REST calls. */
  private boolean debugRest;

  /** Debug REST response. */
  private boolean debugRestResponse;

  public void setDefaultRequestMethod(final RequestMethod requestMethod) {
    this.defaultRequestMethod = requestMethod;
  }

  public RequestMethod getRequestMethod(final RequestDescription requestDescription) { return defaultRequestMethod; }

  public String getDefaultContentHandlerName() { return defaultContentHandlerName; }
  public void setDefaultContentHandlerName(final String defaultContentHandlerName) {
    checkBeanExists(defaultContentHandlerName);
    this.defaultContentHandlerName = defaultContentHandlerName;
  }

  public String getDefaultCacheBeanName() { return defaultCacheBeanName; }
  public void setDefaultCacheBeanName(final String defaultCacheBeanName) {
    checkBeanExists(defaultCacheBeanName);
    this.defaultCacheBeanName = defaultCacheBeanName;
  }

  public void setDebugRest(final boolean debugRest) { this.debugRest = debugRest; }
  public boolean isDebugRestResponse() { return debugRestResponse; }

  public void setDebugRestResponse(final boolean debugRestResponse) { this.debugRestResponse = debugRestResponse; }
  public boolean isDebugRest() { return debugRest; }

  /**
   * Throw {@link IllegalArgumentException} if the specified bean does not exist in the beans container.
   * @param name bean name
   */
  protected void checkBeanExists(final String name) {
    if (name == null) { throw new NullPointerException("Bean name cannot be null"); }
    if (!beansManager.getContainer().containsBean(name)) {
      throw new IllegalArgumentException("Bean " + name + " does not exist");
    }
  }

  public RequestDescription createRequestDescription() { return new RequestDescription(); }

  @Override
  public void onInitializationFinished(final BeansContainer beansContainer) {
    if (this.defaultRequestMethod == null) {
      this.defaultRequestMethod = new RequestMethod();
    }
  }

  @Override
  public void setBeansManager(final BeansManager beansManager) {
    this.beansManager = beansManager;
  }

}


package com.stanfy.enroscar.rest.request.net.multipart;

import java.io.IOException;
import java.io.OutputStream;

import org.apache.http.util.EncodingUtils;

import android.util.Log;

import com.stanfy.enroscar.rest.Utils;

/**
 * Simple string parameter for a multipart post.
 *
 * @author Matthew Albright
 * @author Jeff Dever
 * @author Mike Bowler
 * @author Oleg Kalnichevski
 *
 * @since 2.0
 */
public class StringPart extends PartBase {

  /** Logging tag. */
  private static final String TAG = "StringPart";
  /** Debug flag. */
  private static final boolean DEBUG = Utils.DEBUG_IO;

  /** Default content encoding of string parameters. */
  public static final String DEFAULT_CONTENT_TYPE = "text/plain";

  /** Default charset of string parameters. */
  public static final String DEFAULT_CHARSET = "US-ASCII";

  /** Default transfer encoding of string parameters. */
  public static final String DEFAULT_TRANSFER_ENCODING = "8bit";

  /** Contents of this StringPart. */
  private byte[] content;

  /** The String value of this part. */
  private String value;

  /**
   * Constructor.
   *
   * @param name The name of the part
   * @param value the string to post
   * @param charset the charset to be used to encode the string, if null
   * the {@link #DEFAULT_CHARSET default} is used
   */
  public StringPart(final String name, final String value, final String charset) {

    super(
        name,
        DEFAULT_CONTENT_TYPE,
        charset == null ? DEFAULT_CHARSET : charset,
            DEFAULT_TRANSFER_ENCODING
    );
    if (value == null) {
      throw new IllegalArgumentException("Value may not be null");
    }
    if (value.indexOf(0) != -1) {
      // See RFC 2048, 2.8. "8bit Data"
      throw new IllegalArgumentException("NULs may not be present in string parts");
    }
    this.value = value;
  }

  /**
   * Constructor.
   *
   * @param name The name of the part
   * @param value the string to post
   */
  public StringPart(final String name, final String value) {
    this(name, value, null);
  }

  /**
   * Gets the content in bytes.  Bytes are lazily created to allow the charset to be changed
   * after the part is created.
   *
   * @return the content in bytes
   */
  private byte[] getContent() {
    if (content == null) {
      content = EncodingUtils.getBytes(value, getCharSet());
    }
    return content;
  }

  /**
   * Writes the data to the given OutputStream.
   * @param out the OutputStream to write to
   * @throws IOException if there is a write error
   */
  @Override
  protected void sendData(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendData(OutputStream)"); }
    out.write(getContent());
  }

  /**
   * Return the length of the data.
   * @return The length of the data.
   * @see Part#lengthOfData()
   */
  @Override
  protected long lengthOfData() {
    if (DEBUG) { Log.v(TAG, "enter lengthOfData()"); }
    return getContent().length;
  }

  /* (non-Javadoc)
   * @see org.apache.commons.httpclient.methods.multipart.BasePart#setCharSet(java.lang.String)
   */
  @Override
  public void setCharSet(final String charSet) {
    super.setCharSet(charSet);
    this.content = null;
  }

  /**
   * @return part value
   */
  public String getValue() {
    return value;
  }

}


package com.stanfy.enroscar.io;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Source is based on BufferedInputStream.
 * @see BuffersPool
 * @author Roman Mazur - Stanfy (http://www.stanfy.com)
 */
class PoolableBufferedInputStream extends FilterInputStream {

  /** Buffers pool. */
  private final BuffersPool pool;

  /** The buffer containing the current bytes read from the target InputStream. */
  private volatile byte[] buf;

  /** The total number of bytes inside the byte array {@code buf}. */
  private int count;

  /** The current limit, which when passed, invalidates the current mark. */
  private int marklimit;

  /**
   * The currently marked position. -1 indicates no mark has been set or the
   * mark has been invalidated.
   */
  private int markpos = -1;

  /**
   * The current position within the byte array {@code buf}.
   */
  private int pos;

  /** Call stack. */
  private Throwable stack;

  /**
   * Constructs a new {@code PoolableBufferedInputStream} on the {@link InputStream}
   * {@code in}. The buffer size is specified by the parameter {@code size}
   * and all reads are now filtered through this stream.
   *
   * @param in the input stream the buffer reads from.
   * @param size the size of buffer to allocate.
   * @param pool buffers pool
   * @throws IllegalArgumentException if {@code size < 0}.
   */
  public PoolableBufferedInputStream(final InputStream in, final int size, final BuffersPool pool) {
    super(in);
    if (size <= 0) { throw new IllegalArgumentException("Size must be greater than zero"); }
    this.pool = pool;
    buf = pool.get(size);
    if (buf == null) {
      buf = new byte[size];
    }
    this.stack = new Throwable();
  }

  /**
   * Returns the number of bytes that are available before this stream will
   * block. This method returns the number of bytes available in the buffer
   * plus those available in the source stream.
   *
   * @return the number of bytes available before blocking.
   * @throws IOException
   *             if this stream is closed.
   */
  @Override
  public synchronized int available() throws IOException {
    final InputStream localIn = in; // 'in' could be invalidated by close()
    if (buf == null || localIn == null) { throw new IOException("Stream is closed"); }
    return count - pos + localIn.available();
  }

  /**
   * Closes this stream. The source stream is closed and any resources
   * associated with it are released.
   *
   * @throws IOException
   *             if an error occurs while closing this stream.
   */
  @Override
  public void close() throws IOException {
    final byte[] localBuf = buf;
    buf = null;
    final InputStream localIn = in;
    in = null;
    pool.release(localBuf);
    if (localIn != null) { localIn.close(); }
  }

  private int fillbuf(final InputStream localIn, final byte[] buf) throws IOException {
    byte[] localBuf = buf;
    if (markpos == -1 || (pos - markpos >= marklimit)) {
      /* Mark position not set or exceeded readlimit */
      final int result = localIn.read(localBuf);
      if (result > 0) {
        markpos = -1;
        pos = 0;
        count = result == -1 ? 0 : result;
      }
      return result;
    }
    if (markpos == 0 && marklimit > localBuf.length) {
      /* Increase buffer size to accommodate the readlimit */
      int newLength = localBuf.length * 2;
      if (newLength > marklimit) {
        newLength = marklimit;
      }
      final byte[] newbuf = new byte[newLength];
      System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);
      // Reassign buf, which will invalidate any local references
      // FIXME: what if buf was null?
      localBuf = newbuf;
      this.buf = newbuf;
    } else if (markpos > 0) {
      System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length
          - markpos);
    }
    /* Set the new position and mark position */
    pos -= markpos;
    count = 0;
    markpos = 0;
    final int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);
    count = bytesread <= 0 ? pos : pos + bytesread;
    return bytesread;
  }

  /**
   * Sets a mark position in this stream. The parameter {@code readlimit}
   * indicates how many bytes can be read before a mark is invalidated.
   * Calling {@code reset()} will reposition the stream back to the marked
   * position if {@code readlimit} has not been surpassed. The underlying
   * buffer may be increased in size to allow {@code readlimit} number of
   * bytes to be supported.
   *
   * @param readlimit
   *            the number of bytes that can be read before the mark is
   *            invalidated.
   * @see #reset()
   */
  @Override
  public synchronized void mark(final int readlimit) {
    marklimit = readlimit;
    markpos = pos;
  }

  /**
   * Indicates whether {@code BufferedInputStream} supports the {@code mark()}
   * and {@code reset()} methods.
   *
   * @return {@code true} for BufferedInputStreams.
   * @see #mark(int)
   * @see #reset()
   */
  @Override
  public boolean markSupported() {
    return true;
  }

  /**
   * Reads a single byte from this stream and returns it as an integer in the
   * range from 0 to 255. Returns -1 if the end of the source string has been
   * reached. If the internal buffer does not contain any available bytes then
   * it is filled from the source stream and the first byte is returned.
   *
   * @return the byte read or -1 if the end of the source stream has been
   *         reached.
   * @throws IOException
   *             if this stream is closed or another IOException occurs.
   */
  @Override
  public synchronized int read() throws IOException {
    // Use local refs since buf and in may be invalidated by an
    // unsynchronized close()
    byte[] localBuf = buf;
    final InputStream localIn = in;
    if (localBuf == null || localIn == null) { throw new IOException("Stream is closed"); }

    /* Are there buffered bytes available? */
    if (pos >= count && fillbuf(localIn, localBuf) == -1) {
      return -1; /* no, fill buffer */
    }
    // localBuf may have been invalidated by fillbuf
    if (localBuf != buf) {
      localBuf = buf;
      if (localBuf == null) { throw new IOException("Stream is closed"); }
    }

    /* Did filling the buffer fail with -1 (EOF)? */
    final int mask = 0xFF;
    if (count - pos > 0) {
      return localBuf[pos++] & mask;
    }
    return -1;
  }

  /**
   * Reads at most {@code length} bytes from this stream and stores them in
   * byte array {@code buffer} starting at offset {@code offset}. Returns the
   * number of bytes actually read or -1 if no bytes were read and the end of
   * the stream was encountered. If all the buffered bytes have been used, a
   * mark has not been set and the requested number of bytes is larger than
   * the receiver's buffer size, this implementation bypasses the buffer and
   * simply places the results directly into {@code buffer}.
   *
   * @param buffer
   *            the byte array in which to store the bytes read.
   * @param start
   *            the initial position in {@code buffer} to store the bytes read
   *            from this stream.
   * @param length
   *            the maximum number of bytes to store in {@code buffer}.
   * @return the number of bytes actually read or -1 if end of stream.
   * @throws IndexOutOfBoundsException
   *             if {@code offset < 0} or {@code length < 0}, or if
   *             {@code offset + length} is greater than the size of
   *             {@code buffer}.
   * @throws IOException
   *             if the stream is already closed or another IOException
   *             occurs.
   */
  @Override
  public synchronized int read(final byte[] buffer, final int start, final int length) throws IOException {
    int offset = start;
    // Use local ref since buf may be invalidated by an unsynchronized
    // close()
    byte[] localBuf = buf;
    if (localBuf == null) {
      throw new IOException("Stream is closed");
    }
    // avoid int overflow
    // BEGIN android-changed
    // Exception priorities (in case of multiple errors) differ from
    // RI, but are spec-compliant.
    // made implicit null check explicit, used (offset | length) < 0
    // instead of (offset < 0) || (length < 0) to safe one operation
    if (buffer == null) {
      throw new NullPointerException("Buffer is null");
    }
    if ((offset | length) < 0 || offset > buffer.length - length) {
      throw new IndexOutOfBoundsException("Bad offsets");
    }
    // END android-changed
    if (length == 0) {
      return 0;
    }
    final InputStream localIn = in;
    if (localIn == null) {
      throw new IOException("Stream is closed");
    }

    int required;
    if (pos < count) {
      /* There are bytes available in the buffer. */
      final int copylength = count - pos >= length ? length : count - pos;
      System.arraycopy(localBuf, pos, buffer, offset, copylength);
      pos += copylength;
      if (copylength == length || localIn.available() == 0) {
        return copylength;
      }
      offset += copylength;
      required = length - copylength;
    } else {
      required = length;
    }

    while (true) {
      int read;
      /*
       * If we're not marked and the required size is greater than the
       * buffer, simply read the bytes directly bypassing the buffer.
       */
      if (markpos == -1 && required >= localBuf.length) {
        read = localIn.read(buffer, offset, required);
        if (read == -1) {
          return required == length ? -1 : length - required;
        }
      } else {
        if (fillbuf(localIn, localBuf) == -1) {
          return required == length ? -1 : length - required;
        }
        // localBuf may have been invalidated by fillbuf
        if (localBuf != buf) {
          localBuf = buf;
          if (localBuf == null) {
            throw new IOException("Stream is closed");
          }
        }

        read = count - pos >= required ? required : count - pos;
        System.arraycopy(localBuf, pos, buffer, offset, read);
        pos += read;
      }
      required -= read;
      if (required == 0) {
        return length;
      }
      if (localIn.available() == 0) {
        return length - required;
      }
      offset += read;
    }
  }

  /**
   * Resets this stream to the last marked location.
   *
   * @throws IOException
   *             if this stream is closed, no mark has been set or the mark is
   *             no longer valid because more than {@code readlimit} bytes
   *             have been read since setting the mark.
   * @see #mark(int)
   */
  @Override
  public synchronized void reset() throws IOException {
    // BEGIN android-changed
    /*
     * These exceptions get thrown in some "normalish" circumstances,
     * so it is preferable to avoid loading up the whole big set of
     * messages just for these cases.
     */
    if (buf == null) {
      throw new IOException("Stream is closed");
    }
    if (-1 == markpos) {
      throw new IOException("Mark has been invalidated.");
    }
    // END android-changed
    pos = markpos;
  }

  /**
   * Skips {@code amount} number of bytes in this stream. Subsequent
   * {@code read()}'s will not return these bytes unless {@code reset()} is
   * used.
   *
   * @param amount
   *            the number of bytes to skip. {@code skip} does nothing and
   *            returns 0 if {@code amount} is less than zero.
   * @return the number of bytes actually skipped.
   * @throws IOException
   *             if this stream is closed or another IOException occurs.
   */
  @Override
  public synchronized long skip(final long amount) throws IOException {
    // Use local refs since buf and in may be invalidated by an
    // unsynchronized close()
    final byte[] localBuf = buf;
    final InputStream localIn = in;
    if (localBuf == null) {
      throw new IOException("Stream is closed");
    }
    if (amount < 1) {
      return 0;
    }
    if (localIn == null) {
      throw new IOException("Stream is closed");
    }

    if (count - pos >= amount) {
      pos += amount;
      return amount;
    }
    long read = count - pos;
    pos = count;

    if (markpos != -1 && amount <= marklimit) {
      if (fillbuf(localIn, localBuf) == -1) {
        return read;
      }
      if (count - pos >= amount - read) {
        pos += amount - read;
        return amount;
      }
      // Couldn't get all the bytes, skip what we read
      read += (count - pos);
      pos = count;
      return read;
    }
    return read + localIn.skip(amount - read);
  }

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    if (pool.strictMode && buf != null) {
      System.err.println("WARNING: " + this + " was not closed, buffer was not released");
      stack.printStackTrace();
    }
  }

}


package com.stanfy.enroscar.rest.request.net.multipart;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.http.util.EncodingUtils;

import android.util.Log;

import com.stanfy.enroscar.rest.Utils;

/**
 * Abstract class for one Part of a multipart post object.
 *
 * @author Matthew Albright
 * @author Jeff Dever
 * @author Adrian Sutton
 * @author Mike Bowler
 * @author Oleg Kalnichevski
 *
 * @since 2.0
 */
public abstract class Part {

  /** Logging tag. */
  private static final String TAG = "Part";
  /** Debug flag. */
  private static final boolean DEBUG = Utils.DEBUG_IO;

  /** Carriage return/linefeed. */
  protected static final String CRLF = "\r\n";

  /** Carriage return/linefeed as a byte array. */
  protected static final byte[] CRLF_BYTES = EncodingUtils.getAsciiBytes(CRLF);

  /** Content dispostion characters. */
  protected static final String QUOTE = "\"";

  /** Content dispostion as a byte array. */
  protected static final byte[] QUOTE_BYTES =
    EncodingUtils.getAsciiBytes(QUOTE);

  /** Extra characters. */
  protected static final String EXTRA = "--";

  /** Extra characters as a byte array. */
  protected static final byte[] EXTRA_BYTES =
    EncodingUtils.getAsciiBytes(EXTRA);

  /** Content dispostion characters. */
  protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";

  /** Content dispostion as a byte array. */
  protected static final byte[] CONTENT_DISPOSITION_BYTES =
    EncodingUtils.getAsciiBytes(CONTENT_DISPOSITION);

  /** Content type header. */
  protected static final String CONTENT_TYPE = "Content-Type: ";

  /** Content type header as a byte array. */
  protected static final byte[] CONTENT_TYPE_BYTES =
    EncodingUtils.getAsciiBytes(CONTENT_TYPE);

  /** Content charset. */
  protected static final String CHARSET = "; charset=";

  /** Content charset as a byte array. */
  protected static final byte[] CHARSET_BYTES =
    EncodingUtils.getAsciiBytes(CHARSET);

  /** Content type header. */
  protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";

  /** Content type header as a byte array. */
  protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES =
    EncodingUtils.getAsciiBytes(CONTENT_TRANSFER_ENCODING);

  /**
   * The ASCII bytes to use as the multipart boundary.
   */
  private byte[] boundaryBytes;

  /**
   * Return the name of this part.
   * @return The name.
   */
  public abstract String getName();

  /**
   * Returns the content type of this part.
   * @return the content type, or null to exclude the content type header
   */
  public abstract String getContentType();

  /**
   * Return the character encoding of this part.
   * @return the character encoding, or null to exclude the character
   * encoding header
   */
  public abstract String getCharSet();

  /**
   * Return the transfer encoding of this part.
   * @return the transfer encoding, or null to exclude the transfer encoding header
   */
  public abstract String getTransferEncoding();

  /**
   * Gets the part boundary to be used.
   * @return the part boundary as an array of bytes.
   *
   * @since 3.0
   */
  protected byte[] getPartBoundary() {
    return boundaryBytes;
  }

  /**
   * Sets the part boundary.  Only meant to be used by
   * {@link Part#sendParts(OutputStream, Part[], byte[])}
   * and {@link Part#getLengthOfParts(Part[], byte[])}
   * @param boundaryBytes An array of ASCII bytes.
   * @since 3.0
   */
  public void setPartBoundary(final byte[] boundaryBytes) {
    this.boundaryBytes = boundaryBytes;
  }

  /**
   * Tests if this part can be sent more than once.
   * @return true if {@link #sendData(OutputStream)} can be successfully called
   * more than once.
   * @since 3.0
   */
  public boolean isRepeatable() {
    return true;
  }

  /**
   * Write the start to the specified output stream.
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected void sendStart(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendStart(OutputStream out)"); }
    out.write(EXTRA_BYTES);
    out.write(getPartBoundary());
    out.write(CRLF_BYTES);
  }

  /**
   * Write the content disposition header to the specified output stream.
   *
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected void sendDispositionHeader(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendDispositionHeader(OutputStream out)"); }
    out.write(CONTENT_DISPOSITION_BYTES);
    out.write(QUOTE_BYTES);
    out.write(EncodingUtils.getAsciiBytes(getName()));
    out.write(QUOTE_BYTES);
  }

  /**
   * Write the content type header to the specified output stream.
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected void sendContentTypeHeader(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendContentTypeHeader(OutputStream out)"); }
    final String contentType = getContentType();
    if (contentType != null) {
      out.write(CRLF_BYTES);
      out.write(CONTENT_TYPE_BYTES);
      out.write(EncodingUtils.getAsciiBytes(contentType));
      final String charSet = getCharSet();
      if (charSet != null) {
        out.write(CHARSET_BYTES);
        out.write(EncodingUtils.getAsciiBytes(charSet));
      }
    }
  }

  /**
   * Write the content transfer encoding header to the specified
   * output stream.
   *
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected void sendTransferEncodingHeader(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendTransferEncodingHeader(OutputStream out)"); }
    final String transferEncoding = getTransferEncoding();
    if (transferEncoding != null) {
      out.write(CRLF_BYTES);
      out.write(CONTENT_TRANSFER_ENCODING_BYTES);
      out.write(EncodingUtils.getAsciiBytes(transferEncoding));
    }
  }

  /**
   * Write the end of the header to the output stream.
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected void sendEndOfHeader(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendEndOfHeader(OutputStream out)"); }
    out.write(CRLF_BYTES);
    out.write(CRLF_BYTES);
  }

  /**
   * Write the data to the specified output stream.
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected abstract void sendData(OutputStream out) throws IOException;

  /**
   * Return the length of the main content.
   *
   * @return long The length.
   * @throws IOException If an IO problem occurs
   */
  protected abstract long lengthOfData() throws IOException;

  /**
   * Write the end data to the output stream.
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  protected void sendEnd(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendEnd(OutputStream out)"); }
    out.write(CRLF_BYTES);
  }

  /**
   * Write all the data to the output stream.
   * If you override this method make sure to override
   * #length() as well
   *
   * @param out The output stream
   * @throws IOException If an IO problem occurs.
   */
  public void send(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter send(OutputStream out)"); }
    sendStart(out);
    sendDispositionHeader(out);
    sendContentTypeHeader(out);
    sendTransferEncodingHeader(out);
    sendEndOfHeader(out);
    sendData(out);
    sendEnd(out);
  }

  /**
   * Return the full length of all the data.
   * If you override this method make sure to override
   * #send(OutputStream) as well
   *
   * @return long The length.
   * @throws IOException If an IO problem occurs
   */
  public long length() throws IOException {
    if (DEBUG) { Log.v(TAG, "enter length()"); }
    if (lengthOfData() < 0) {
      return -1;
    }
    final ByteArrayOutputStream overhead = new ByteArrayOutputStream();
    sendStart(overhead);
    sendDispositionHeader(overhead);
    sendContentTypeHeader(overhead);
    sendTransferEncodingHeader(overhead);
    sendEndOfHeader(overhead);
    sendEnd(overhead);
    return overhead.size() + lengthOfData();
  }

  /**
   * Return a string representation of this object.
   * @return A string representation of this object.
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return this.getName();
  }

  /**
   * Write all parts and the last boundary to the specified output stream.
   *
   * @param out The stream to write to.
   * @param parts The parts to write.
   * @param partBoundary The ASCII bytes to use as the part boundary.
   *
   * @throws IOException If an I/O error occurs while writing the parts.
   *
   * @since 3.0
   */
  public static void sendParts(final OutputStream out, final Part[] parts, final byte[] partBoundary)
  throws IOException {

    if (parts == null) {
      throw new IllegalArgumentException("Parts may not be null");
    }
    if (partBoundary == null || partBoundary.length == 0) {
      throw new IllegalArgumentException("partBoundary may not be empty");
    }
    for (int i = 0; i < parts.length; i++) {
      // set the part boundary before the part is sent
      parts[i].setPartBoundary(partBoundary);
      parts[i].send(out);
    }
    out.write(EXTRA_BYTES);
    out.write(partBoundary);
    out.write(EXTRA_BYTES);
    out.write(CRLF_BYTES);
  }

  /**
   * Gets the length of the multipart message including the given parts.
   *
   * @param parts The parts.
   * @param partBoundary The ASCII bytes to use as the part boundary.
   * @return The total length
   *
   * @throws IOException If an I/O error occurs while writing the parts.
   *
   * @since 3.0
   */
  public static long getLengthOfParts(final Part[] parts, final byte[] partBoundary) throws IOException {
    if (DEBUG) { Log.v(TAG, "getLengthOfParts(Parts[])"); }
    if (parts == null) {
      throw new IllegalArgumentException("Parts may not be null");
    }
    long total = 0;
    for (int i = 0; i < parts.length; i++) {
      // set the part boundary before we calculate the part's length
      parts[i].setPartBoundary(partBoundary);
      final long l = parts[i].length();
      if (l < 0) {
        return -1;
      }
      total += l;
    }
    total += EXTRA_BYTES.length;
    total += partBoundary.length;
    total += EXTRA_BYTES.length;
    total += CRLF_BYTES.length;
    return total;
  }
}


package com.netiq.websockify;

import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static org.jboss.netty.handler.codec.http.HttpHeaders.setContentLength;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static org.jboss.netty.handler.codec.http.HttpMethod.GET;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.TEMPORARY_REDIRECT;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Logger;

import javax.activation.MimetypesFileTypeMap;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelFutureProgressListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.DefaultFileRegion;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.FileRegion;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
import org.jboss.netty.handler.codec.base64.Base64;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.handler.stream.ChunkedFile;
import org.jboss.netty.util.CharsetUtil;

public class WebsockifyProxyHandler extends SimpleChannelUpstreamHandler {

    private static final String URL_PARAMETER = "url";
	public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
    public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
    public static final int HTTP_CACHE_SECONDS = 60;
    public static final String REDIRECT_PATH = "/redirect";

    private final ClientSocketChannelFactory cf;
    private final IProxyTargetResolver resolver;

    private WebSocketServerHandshaker handshaker = null;
    private String webDirectory;

    // This lock guards against the race condition that overrides the
    // OP_READ flag incorrectly.
    // See the related discussion: http://markmail.org/message/x7jc6mqx6ripynqf
    final Object trafficLock = new Object();

    private volatile Channel outboundChannel;

    public WebsockifyProxyHandler(ClientSocketChannelFactory cf, IProxyTargetResolver resolver, String webDirectory) {
        this.cf = cf;
        this.resolver = resolver;
        this.outboundChannel = null;
        this.webDirectory = webDirectory;
    }

    private void ensureTargetConnection(ChannelEvent e, boolean websocket, final Object sendMsg)
            throws Exception {
    	if(outboundChannel == null) {
	        // Suspend incoming traffic until connected to the remote host.
	        final Channel inboundChannel = e.getChannel();
	        inboundChannel.setReadable(false);
			Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Inbound proxy connection from " + inboundChannel.getRemoteAddress() + ".");

	        // resolve the target
	        final InetSocketAddress target = resolver.resolveTarget(inboundChannel);
	        if ( target == null )
	        {
				Logger.getLogger(WebsockifyProxyHandler.class.getName()).severe("Connection from " + inboundChannel.getRemoteAddress() + " failed to resolve target.");
	        	// there is no target
	        	inboundChannel.close();
	        	return;
	        }

	        // Start the connection attempt.
	        ClientBootstrap cb = new ClientBootstrap(cf);
	        if ( websocket ) {
	        	cb.getPipeline().addLast("handler", new OutboundWebsocketHandler(e.getChannel(), trafficLock));
	        }
	        else {
	        	cb.getPipeline().addLast("handler", new OutboundHandler(e.getChannel(), trafficLock));
	        }
	        ChannelFuture f = cb.connect(target);

	        outboundChannel = f.getChannel();
	        if ( sendMsg != null ) outboundChannel.write(sendMsg);
	        f.addListener(new ChannelFutureListener() {
	            public void operationComplete(ChannelFuture future) throws Exception {
	                if (future.isSuccess()) {
	                    // Connection attempt succeeded:
	                    // Begin to accept incoming traffic.
	    				Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Created outbound connection to " + target + ".");
	                    inboundChannel.setReadable(true);
	                } else {
	    				Logger.getLogger(WebsockifyProxyHandler.class.getName()).severe("Failed to create outbound connection to " + target + ".");
	                    // Close the connection if the connection attempt has failed.
	                    inboundChannel.close();
	                }
	            }
	        });
    	} else {
	        if ( sendMsg != null ) outboundChannel.write(sendMsg);
    	}
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e)
            throws Exception {
        Object msg = e.getMessage();
        // An HttpRequest means either an initial websocket connection
        // or a web server request
        if (msg instanceof HttpRequest) {
            handleHttpRequest(ctx, (HttpRequest) msg, e);
        // A WebSocketFrame means a continuation of an established websocket connection
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg, e);
            // A channel buffer we treat as a VNC protocol request
        } else if (msg instanceof ChannelBuffer) {
            handleVncDirect(ctx, (ChannelBuffer) msg, e);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req, final MessageEvent e) throws Exception {
        // Allow only GET methods.
        if (req.getMethod() != GET) {
            sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
            return;
        }

        String upgradeHeader = req.getHeader("Upgrade");
        if(upgradeHeader != null && upgradeHeader.toUpperCase().equals("WEBSOCKET")){
			Logger.getLogger(WebsockifyProxyHandler.class.getName()).fine("Websocket request from " + e.getRemoteAddress() + ".");
	        // Handshake
	        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
	                this.getWebSocketLocation(req), "base64", false);
	        this.handshaker = wsFactory.newHandshaker(req);
	        if (this.handshaker == null) {
	            wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
	        } else {
	        	// deal with a bug in the flash websocket emulation
	        	// it specifies WebSocket-Protocol when it seems it should specify Sec-WebSocket-Protocol
	        	String protocol = req.getHeader("WebSocket-Protocol");
	        	String secProtocol = req.getHeader("Sec-WebSocket-Protocol");
	        	if(protocol != null && secProtocol == null )
	        	{
	        		req.addHeader("Sec-WebSocket-Protocol", protocol);
	        	}
	            this.handshaker.handshake(ctx.getChannel(), req);
	        }
	    	ensureTargetConnection (e, true, null);
        }
        else {
            HttpRequest request = (HttpRequest) e.getMessage();
            String redirectUrl = isRedirect(request.getUri());
		    if ( redirectUrl != null) {
				Logger.getLogger(WebsockifyProxyHandler.class.getName()).fine("Redirecting to " + redirectUrl + ".");
		        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, TEMPORARY_REDIRECT);
		        response.setHeader(HttpHeaders.Names.LOCATION, redirectUrl);
	            sendHttpResponse(ctx, req, response);
	            return;
		    }
		    else if ( webDirectory != null )/* not a websocket connection attempt */{
		    	handleWebRequest ( ctx, e );
		    }
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame, final MessageEvent e) {

        // Check for closing frame
        if (frame instanceof CloseWebSocketFrame) {
            this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
            return;
        } else if (frame instanceof PingWebSocketFrame) {
            ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
            return;
        } else if (!(frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
                    .getName()));
        }

        ChannelBuffer msg = ((TextWebSocketFrame) frame).getBinaryData();
        ChannelBuffer decodedMsg = Base64.decode(msg);
        synchronized (trafficLock) {
            outboundChannel.write(decodedMsg);
            // If outboundChannel is saturated, do not read until notified in
            // OutboundHandler.channelInterestChanged().
            if (!outboundChannel.isWritable()) {
                e.getChannel().setReadable(false);
            }
        }
    }

    private void handleVncDirect(ChannelHandlerContext ctx, ChannelBuffer buffer, final MessageEvent e) throws Exception {
    	// ensure the target connection is open and send the data
    	ensureTargetConnection(e, false, buffer);
    }

    private void handleWebRequest(ChannelHandlerContext ctx, final MessageEvent e) throws Exception {

        HttpRequest request = (HttpRequest) e.getMessage();
        if (request.getMethod() != GET) {
            sendError(ctx, METHOD_NOT_ALLOWED);
            return;
        }

		Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Web request from " + e.getRemoteAddress() + " for " + request.getUri() + ".");

        final String path = sanitizeUri(request.getUri());
        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }

        File file = new File(path);
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }

        // Cache Validation
        String ifModifiedSince = request.getHeader(HttpHeaders.Names.IF_MODIFIED_SINCE);
        if (ifModifiedSince != null && !ifModifiedSince.equals("")) {
            SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
            Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);

            // Only compare up to the second because the datetime format we send to the client does not have milliseconds
            long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
            long fileLastModifiedSeconds = file.lastModified() / 1000;
            if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
                sendNotModified(ctx);
                return;
            }
        }

        RandomAccessFile raf;
        try {
            raf = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException fnfe) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        long fileLength = raf.length();

        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        setContentLength(response, fileLength);
        setContentTypeHeader(response, file);
        setDateAndCacheHeaders(response, file);

        Channel ch = e.getChannel();

        // Write the initial line and the header.
        ch.write(response);

        // Write the content.
        ChannelFuture writeFuture;
        if (ch.getPipeline().get(SslHandler.class) != null) {
            // Cannot use zero-copy with HTTPS.
            writeFuture = ch.write(new ChunkedFile(raf, 0, fileLength, 8192));
        } else {
            // No encryption - use zero-copy.
            final FileRegion region =
                new DefaultFileRegion(raf.getChannel(), 0, fileLength);
            writeFuture = ch.write(region);
            writeFuture.addListener(new ChannelFutureProgressListener() {
                public void operationComplete(ChannelFuture future) {
                    region.releaseExternalResources();
                }

                public void operationProgressed(
                        ChannelFuture future, long amount, long current, long total) {
                    System.out.printf("%s: %d / %d (+%d)%n", path, current, total, amount);
                }
            });
        }

        // Decide whether to close the connection or not.
        if (!isKeepAlive(request)) {
            // Close the connection when the whole content is written out.
            writeFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private static Map<String, String> getQueryMap(String query)
    {
        String[] params = query.split("&");
        Map<String, String> map = new HashMap<String, String>();
        for (String param : params)
        {
            String name = param.split("=")[0];
            String value = param.split("=")[1];
            map.put(name, value);
        }
        return map;
    }

    // checks to see if the uri is a redirect request
    // if it is, it returns the url parameter
    private String isRedirect(String uri) throws URISyntaxException, MalformedURLException {
        // Decode the path.
        URI url = new URI (uri);

        if ( REDIRECT_PATH.equals(url.getPath()) ) {
        	String query = url.getRawQuery();
        	Map<String, String> params = getQueryMap(query);

        	String urlParam = params.get(URL_PARAMETER);
        	if ( urlParam == null ) return null;

        	try {
				return URLDecoder.decode(urlParam, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				Logger.getLogger(WebsockifyProxyHandler.class.getName()).severe(e.getMessage());
			}
        }

        return null;
    }

    private String sanitizeUri(String uri) throws URISyntaxException {
        // Decode the path.
        URI url = new URI (uri);
        uri = url.getPath();

        // Convert file separators.
        uri = uri.replace('/', File.separatorChar);

        // Simplistic dumb security check.
        // You will have to do something serious in the production environment.
        if (uri.contains(File.separator + ".") ||
            uri.contains("." + File.separator) ||
            uri.startsWith(".") || uri.endsWith(".")) {
            return null;
        }

        // Convert to absolute path.
        return webDirectory + File.separator + uri;
    }

    private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
        // Generate an error page if response status code is not OK (200).
        if (res.getStatus().getCode() != 200) {
            res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
            setContentLength(res, res.getContent().readableBytes());
        }

        // Send the response and close the connection if necessary.
        ChannelFuture f = ctx.getChannel().write(res);
        if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
        response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.setContent(ChannelBuffers.copiedBuffer(
                "Failure: " + status.toString() + "\r\n",
                CharsetUtil.UTF_8));

        // Close the connection as soon as the error message is sent.
        ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
     *
     * @param ctx
     *            Context
     */
    private void sendNotModified(ChannelHandlerContext ctx) {
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_MODIFIED);
        setDateHeader(response);

        // Close the connection as soon as the error message is sent.
        ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * Sets the Date header for the HTTP response
     *
     * @param response
     *            HTTP response
     */
    private void setDateHeader(HttpResponse response) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

        Calendar time = new GregorianCalendar();
        response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));
    }

    /**
     * Sets the Date and Cache headers for the HTTP Response
     *
     * @param response
     *            HTTP response
     * @param fileToCache
     *            file to extract content type
     */
    private void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

        // Date header
        Calendar time = new GregorianCalendar();
        response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));

        // Add cache headers
        time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
        response.setHeader(HttpHeaders.Names.EXPIRES, dateFormatter.format(time.getTime()));
        response.setHeader(HttpHeaders.Names.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
        response.setHeader(HttpHeaders.Names.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
    }

    /**
     * Sets the content type header for the HTTP Response
     *
     * @param response
     *            HTTP response
     * @param file
     *            file to extract content type
     */
    private void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
        response.setHeader(HttpHeaders.Names.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
    }

    private String getWebSocketLocation(HttpRequest req) {
        String prefix = "ws";
        String origin = req.getHeader(HttpHeaders.Names.ORIGIN).toLowerCase();
        if(origin.contains("https")){
            prefix = "wss";
        }
        return prefix + "://" + req.getHeader(HttpHeaders.Names.HOST) + req.getUri();
    }

    @Override
    public void channelInterestChanged(ChannelHandlerContext ctx,
            ChannelStateEvent e) throws Exception {
        // If inboundChannel is not saturated anymore, continue accepting
        // the incoming traffic from the outboundChannel.
        synchronized (trafficLock) {
            if (e.getChannel().isWritable() && outboundChannel != null) {
                outboundChannel.setReadable(true);
            }
        }
    }

    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
            throws Exception {

		Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Inbound proxy connection from " + ctx.getChannel().getRemoteAddress() + " closed.");
        if (outboundChannel != null) {
            closeOnFlush(outboundChannel);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
            throws Exception {
        e.getCause().printStackTrace();
		Logger.getLogger(WebsockifyProxyHandler.class.getName()).severe("Exception on inbound proxy connection from " + e.getChannel().getRemoteAddress() + ": " + e.getCause().getMessage());
        closeOnFlush(e.getChannel());
    }

    /**
     * Closes the specified channel after all queued write requests are flushed.
     */
    static void closeOnFlush(Channel ch) {
        if (ch.isConnected()) {
            ch.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }
}


package com.stanfy.enroscar.rest.request.net.multipart;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.http.util.EncodingUtils;

import android.util.Log;

import com.stanfy.enroscar.rest.Utils;

/**
 * This class implements a part of a Multipart post object that
 * consists of a file.
 *
 * @author Matthew Albright
 * @author Jeff Dever
 * @author Adrian Sutton
 * @author Michael Becke
 * @author Mark Diggory
 * @author Mike Bowler
 * @author Oleg Kalnichevski
 *
 * @since 2.0
 *
 */
public class FilePart extends PartBase {

  /** Logging tag. */
  private static final String TAG = "FilePart";
  /** Debug flag. */
  private static final boolean DEBUG = Utils.DEBUG_IO;

  /** Default content encoding of file attachments. */
  public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

  /** Default charset of file attachments. */
  public static final String DEFAULT_CHARSET = "ISO-8859-1";

  /** Default transfer encoding of file attachments. */
  public static final String DEFAULT_TRANSFER_ENCODING = "binary";

  /** Attachment's file name. */
  protected static final String FILE_NAME = "; filename=";

  /** Attachment's file name as a byte array. */
  private static final byte[] FILE_NAME_BYTES = EncodingUtils.getAsciiBytes(FILE_NAME);

  /** Source of the file part. */
  private PartSource source;

  /**
   * FilePart Constructor.
   *
   * @param name the name for this part
   * @param partSource the source for this part
   * @param contentType the content type for this part, if null the
   * {@link #DEFAULT_CONTENT_TYPE default} is used
   * @param charset the charset encoding for this part, if null the
   * {@link #DEFAULT_CHARSET default} is used
   */
  public FilePart(final String name, final PartSource partSource, final String contentType, final String charset) {

    super(
        name,
        contentType == null ? DEFAULT_CONTENT_TYPE : contentType,
            charset == null ? "ISO-8859-1" : charset,
                DEFAULT_TRANSFER_ENCODING
    );

    if (partSource == null) {
      throw new IllegalArgumentException("Source may not be null");
    }
    this.source = partSource;
  }

  /**
   * FilePart Constructor.
   *
   * @param name the name for this part
   * @param partSource the source for this part
   */
  public FilePart(final String name, final PartSource partSource) {
    this(name, partSource, null, null);
  }

  /**
   * FilePart Constructor.
   *
   * @param name the name of the file part
   * @param file the file to post
   *
   * @throws FileNotFoundException if the file is not a normal
   * file or if it is not readable.
   */
  public FilePart(final String name, final File file)
  throws FileNotFoundException {
    this(name, new FilePartSource(file), null, null);
  }

  /**
   * FilePart Constructor.
   *
   * @param name the name of the file part
   * @param file the file to post
   * @param contentType the content type for this part, if null the
   * {@link #DEFAULT_CONTENT_TYPE default} is used
   * @param charset the charset encoding for this part, if null the
   * {@link #DEFAULT_CHARSET default} is used
   *
   * @throws FileNotFoundException if the file is not a normal
   * file or if it is not readable.
   */
  public FilePart(final String name, final File file, final String contentType, final String charset)
  throws FileNotFoundException {
    this(name, new FilePartSource(file), contentType, charset);
  }

  /**
   * FilePart Constructor.
   *
   * @param name the name of the file part
   * @param fileName the file name
   * @param file the file to post
   *
   * @throws FileNotFoundException if the file is not a normal
   * file or if it is not readable.
   */
  public FilePart(final String name, final String fileName, final File file)
  throws FileNotFoundException {
    this(name, new FilePartSource(fileName, file), null, null);
  }

  /**
   * FilePart Constructor.
   *
   * @param name the name of the file part
   * @param fileName the file name
   * @param file the file to post
   * @param contentType the content type for this part, if null the
   * {@link #DEFAULT_CONTENT_TYPE default} is used
   * @param charset the charset encoding for this part, if null the
   * {@link #DEFAULT_CHARSET default} is used
   *
   * @throws FileNotFoundException if the file is not a normal
   * file or if it is not readable.
   */
  public FilePart(final String name, final String fileName, final File file, final String contentType, final String charset)
  throws FileNotFoundException {
    this(name, new FilePartSource(fileName, file), contentType, charset);
  }

  /**
   * Write the disposition header to the output stream.
   * @param out The output stream
   * @throws IOException If an IO problem occurs
   * @see Part#sendDispositionHeader(OutputStream)
   */
  @Override
  protected void sendDispositionHeader(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v("FilePart", "enter sendDispositionHeader(OutputStream out)"); }
    super.sendDispositionHeader(out);
    final String filename = this.source.getFileName();
    if (filename != null) {
      out.write(FILE_NAME_BYTES);
      out.write(QUOTE_BYTES);
      out.write(EncodingUtils.getAsciiBytes(filename));
      out.write(QUOTE_BYTES);
    }
  }

  /**
   * Write the data in "source" to the specified stream.
   * @param out The output stream.
   * @throws IOException if an IO problem occurs.
   * @see Part#sendData(OutputStream)
   */
  @Override
  protected void sendData(final OutputStream out) throws IOException {
    if (DEBUG) { Log.v(TAG, "enter sendData(OutputStream out)"); }
    if (lengthOfData() == 0) {

      // this file contains no data, so there is nothing to send.
      // we don't want to create a zero length buffer as this will
      // cause an infinite loop when reading.
      if (DEBUG) { Log.d(TAG, "No data to send."); }
      return;
    }

    final int size = 4096;
    final byte[] tmp = new byte[size];
    final InputStream instream = source.createInputStream();
    try {
      int len;
      while ((len = instream.read(tmp)) >= 0) {
        out.write(tmp, 0, len);
      }
    } finally {
      // we're done with the stream, close it
      instream.close();
    }
  }

  /**
   * Returns the source of the file part.
   *
   * @return The source.
   */
  protected PartSource getSource() {
    if (DEBUG) { Log.v(TAG, "enter getSource()"); }
    return this.source;
  }

  /**
   * Return the length of the data.
   * @return The length.
   * @see Part#lengthOfData()
   */
  @Override
  protected long lengthOfData() {
    if (DEBUG) { Log.v(TAG, "enter lengthOfData()"); }
    return source.getLength();
  }

}


package com.stanfy.enroscar.net.cache;

import android.os.AsyncTask;
import android.util.Log;

import com.jakewharton.disklrucache.DiskLruCache;
import com.stanfy.enroscar.beans.BeansContainer;
import com.stanfy.enroscar.beans.DestroyingBean;
import com.stanfy.enroscar.beans.InitializingBean;
import com.stanfy.enroscar.io.BuffersPool;
import com.stanfy.enroscar.io.IoUtils;
import com.stanfy.enroscar.net.UrlConnectionWrapper;
import com.stanfy.enroscar.net.cache.CacheEntry.CacheEntryListener;
import com.stanfy.enroscar.net.cache.CacheEntry.CacheEntryRequest;

import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Base class for cache implementations based on file system.
 * @author Roman Mazur (Stanfy - http://stanfy.com)
 */
public abstract class BaseFileResponseCache extends BaseSizeRestrictedCache
    implements EnhancedResponseCache, CacheEntryListener, DestroyingBean, InitializingBean {

  /** Cache entry index. */
  private static final int ENTRY_BODY = 0, ENTRY_METADATA = 1;
  /** Count of cache entries. */
  private static final int ENTRIES_COUNT = 2;

  /** Logging tag. */
  protected static final String TAG = "FileCache";
  /** Debug flag. */
  protected static final boolean DEBUG = DebugFlags.DEBUG_NET_CACHE;

  /** Application version. */
  private static final int VERSION = 20120718;

  /** Disk cache instance. */
  private DiskLruCache diskCache;

  /** Buffers pool. */
  private BuffersPool buffersPool;

  /** Sync point for cache installation. */
  private final CountDownLatch initSync = new CountDownLatch(1);

  /** Statistics. */
  private final AtomicInteger writeSuccessCount = new AtomicInteger(0),
                              writeAbortCount = new AtomicInteger(0),
                              hitCount = new AtomicInteger(0);

  /**
   * Setup cache. This operation causes disk reads.
   * @param version cache version
   * @throws IOException if error happens
   */
  protected void install(final int version) throws IOException {
    if (buffersPool == null) {
      throw new IllegalStateException("Buffers pool is not resolved");
    }

    diskCache = DiskLruCache.open(ensureWorkingDirectory(), version, ENTRIES_COUNT, getMaxSize());
    onCacheInstalled();
  }

  // this method is synchronized in order to avoid concurrent calls to mkdir
  private synchronized File ensureWorkingDirectory() throws IOException {
    File directory = getWorkingDirectory();
    if (!directory.exists()) {
      if (!directory.mkdirs()) {
        throw new IOException("Working directory " + directory + " cannot be created");
      }
    } else {
      if (!directory.isDirectory()) {
        if (!directory.delete()) {
          throw new IOException(directory + " is not a directory and cannot be deleted");
        }
        if (!directory.mkdirs()) {
          throw new IOException("Working directory " + directory + " cannot be recreated");
        }
      }
    }
    return directory;
  }

  /**
   * Called when cache is initialized before any reads or writes to this cache.
   * Called from a working thread.
   */
  protected void onCacheInstalled() {
    // nothing
  }

  public void delete() throws IOException {
    if (DEBUG) { Log.d(TAG, "Delete cache workingDirectory=" + diskCache.getDirectory()); }
    diskCache.delete();
  }

  public DiskLruCache getDiskCache() { return diskCache; }

  private DiskLruCache.Snapshot readCacheInfo(final CacheEntry requestInfo, final CacheEntry entry) {
    if (!checkDiskCache()) { return null; }

    final String key = requestInfo.getCacheKey();

    DiskLruCache.Snapshot snapshot;
    InputStream bufferedStream = null;
    try {
      snapshot = diskCache.get(key);
      if (snapshot == null) {
        return null;
      }
      bufferedStream = buffersPool.bufferize(snapshot.getInputStream(ENTRY_METADATA));
      entry.readFrom(bufferedStream);
    } catch (final IOException e) {
      IoUtils.closeQuietly(bufferedStream);
      // Give up because the cache cannot be read.
      return null;
    }
    return snapshot;
  }

  /**
   * Read cache for the specified cache entry.
   * @param requestInfo request info (cache key)
   * @return cache response instance
   */
  protected CacheResponse get(final CacheEntry requestInfo) {
    if (!checkDiskCache()) { return null; }
    final CacheEntry entry = newCacheEntry();
    final DiskLruCache.Snapshot snapshot = readCacheInfo(requestInfo, entry);
    if (snapshot == null) { return null; }

    if (!entry.matches(requestInfo) || !entry.canBeUsed()) {
      snapshot.close();
      return null;
    }

    hitCount.incrementAndGet();

    final InputStream body = newBodyInputStream(snapshot);
    return entry.newCacheResponse(body);
  }

  @Override
  public final CacheResponse get(final URI uri, final URLConnection connection) throws IOException {
    final CacheEntry requestInfo = newCacheEntry();
    requestInfo.setFrom(connection);
    return get(requestInfo);
  }

  @Override
  public final CacheResponse get(final URI uri, final String requestMethod, final Map<String, List<String>> requestHeaders) {
    final CacheEntry requestInfo = newCacheEntry();
    requestInfo.set(uri, requestMethod, requestHeaders);
    return get(requestInfo);
  }

  /**
   * Override this method in order to provide custom {@link CacheEntry} implementation.
   * But use {@link #newCacheEntry()} method for creating ones.
   * @return new cache entry instance
   */
  protected abstract CacheEntry createCacheEntry();

  /**
   * Create and setup new cache entry.
   * @return new cache entry instance
   */
  protected final CacheEntry newCacheEntry() {
    final CacheEntry result = createCacheEntry();
    result.setListener(this);
    return result;
  }

  /**
   * Returns an input stream that reads the body of a snapshot, closing the
   * snapshot when the stream is closed.
   */
  private InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
    return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
      @Override public void close() throws IOException {
        snapshot.close();
        super.close();
      }
    };
  }

  @Override
  public CacheRequest put(final URI uri, final URLConnection connection) throws IOException {
    if (!checkDiskCache()) { return null; }
    final URLConnection urlConnection = UrlConnectionWrapper.unwrap(connection);

    final CacheEntry cacheEntry = newCacheEntry();
    cacheEntry.setFrom(urlConnection);
    cacheEntry.setResponseData(urlConnection);

    if (!cacheEntry.canBeCached()) { return null; }

    final String key = cacheEntry.getCacheKey();

    DiskLruCache.Editor editor = null;
    OutputStream metaOut = null;
    try {
      editor = diskCache.edit(key);
      if (editor == null) {
        return null;
      }
      metaOut = buffersPool.bufferize(editor.newOutputStream(ENTRY_METADATA));
      cacheEntry.writeTo(metaOut);
      return cacheEntry.newCacheRequest(buffersPool.bufferize(editor.newOutputStream(ENTRY_BODY)), editor);
    } catch (final IOException e) {
      Log.w(TAG, "Cannot write cache entry", e);
      // Give up because the cache cannot be written.
      try {
        if (editor != null) {
          editor.abort();
        }
      } catch (final IOException ignored) {
        Log.w(TAG, "Cannot abort editor", e);
      }
      return null;
    } finally {
      IoUtils.closeQuietly(metaOut);
    }

  }

  private boolean checkDiskCache() {
    try {
      initSync.await();
    } catch (InterruptedException e) {
      Log.i(TAG, "Init sync waiting was interrupted for cache " + this);
    }

    if (diskCache == null || diskCache.isClosed()) {
      Log.e(TAG, "File cache is being used but not properly installed, diskCache = " + diskCache);
      return false;
    }
    return true;
  }

  private CacheEntry createGetEntry(final String url) {
    try {
      if (url == null) { throw new URISyntaxException("<null uri>", "URI is null"); }
      final CacheEntry cacheEntry = newCacheEntry();
      cacheEntry.set(new URI(url), "GET", Collections.<String, List<String>>emptyMap());
      return cacheEntry;
    } catch (final URISyntaxException e) {
      Log.e(TAG, "Bad url " + url + ", cannot deleteGetEntry", e);
      return null;
    }
  }

  @Override
  public boolean deleteGetEntry(final String url) throws IOException {
    if (!checkDiskCache()) { return false; }
    final CacheEntry cacheEntry = createGetEntry(url);
    if (cacheEntry == null) { return false; }
    return diskCache.remove(cacheEntry.getCacheKey());
  }

  @Override
  public boolean contains(final String url) {
    final CacheEntry requestInfo = createGetEntry(url);
    if (requestInfo == null) { return false; }

    final CacheEntry entry = newCacheEntry();
    final DiskLruCache.Snapshot snapshot = readCacheInfo(requestInfo, entry);
    if (snapshot == null) { return false; }
    IoUtils.closeQuietly(snapshot);

    return entry.matches(requestInfo);
  }

  @Override
  public String getLocalPath(final String url) {
    final CacheEntry requestInfo = createGetEntry(url);
    if (requestInfo == null) { return null; }
    File f = new File(getWorkingDirectory(), requestInfo.getCacheKey() + "." + ENTRY_BODY);
    return f.getAbsolutePath();
  }

  @Override
  public void onCacheEntryWriteAbort(final CacheEntryRequest request) {
    writeAbortCount.incrementAndGet();
  }
  @Override
  public void onCacheEntryWriteSuccess(final CacheEntryRequest request) {
    writeSuccessCount.incrementAndGet();
    try {
      diskCache.flush();
    } catch (final IOException e) {
      Log.w(TAG, "Cannot flush disk cache", e);
    }
  }

  public int getWriteSuccessCount() { return writeSuccessCount.get(); }
  public int getWriteAbortCount() { return writeAbortCount.get(); }
  public int getHitCount() { return hitCount.get(); }

  @Override
  public void onInitializationFinished(final BeansContainer beansContainer) {
    this.buffersPool = beansContainer.getBean(BuffersPool.class.getName(), BuffersPool.class);
    if (buffersPool == null) {
      throw new IllegalStateException("Buffers pool must be initialized before the response cache");
    }

    new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(final Void... params) {
        try {
          if (DEBUG) {
            Log.i(TAG, "Install new file cache workingDirectory=" + getWorkingDirectory() + ", version=" + VERSION + ", maxSize=" + getMaxSize());
          }
          install(VERSION);
        } catch (final IOException e) {
          // We do not throw fatal exception: it's a cache app should be able to work without it
          Log.e(TAG, "Cannot install file cache " + BaseFileResponseCache.this + ". It must be configuration error.", e);
        } finally {
          initSync.countDown();
        }
        return null;
      }
    }
    .execute();
  }

  @Override
  public void onDestroy(final BeansContainer beansContainer) {
    try {
      if (DEBUG) {
        Log.i(TAG, "Close file cache workingDirectory=" + getWorkingDirectory());
      }
      diskCache.close();
    } catch (final IOException e) {
      Log.e(TAG, "Cannot close file cache", e);
    }
  }

  @Override
  public String toString() {
    return getClass().getName() + "@" + hashCode() + "[dir=" + getWorkingDirectory() + ", maxSize=" + getMaxSize() + "]";
  }

}


package com.stanfy.enroscar.views.decorator;

import android.graphics.Bitmap;
import android.graphics.Canvas;

/**
 * Decorator that requires a buffer. It caches the bitmap whenever it's possible.
 * @author Roman Mazur (Stanfy - http://www.stanfy.com)
 */
public abstract class BufferBitmapDecorator extends ImageDecoratorAdapter {

  /** Buffer bitmap. */
  Bitmap bitmap;
  /** Buffer canvas. */
  Canvas bitmapCanvas;

  /**
   * Prepare for new width and hight.
   * @param width new width
   * @param height new height
   */
  protected void reset(final int width, final int height) {
    Bitmap bitmap = this.bitmap;
    // recycle old buffer
    if (bitmap != null) { bitmap.recycle(); }
    bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // high quality
    this.bitmap = bitmap;
    this.bitmapCanvas = new Canvas(bitmap);
  }

  @Override
  public void setup(final int width, final int height, final int[] state, final int level, final int sourceWidth, final int sourceHeight) {
    if (width <= 0 || height <= 0) { this.bitmap = null; return; }
    final Bitmap bitmap = this.bitmap;

    // need a new buffer?
    if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
      reset(width, height);
    }
  }

  /** @return the bitmap */
  protected Bitmap getBitmap() { return bitmap; }
  /** @return the bitmapCanvas */
  protected Canvas getBitmapCanvas() { return bitmapCanvas; }

}


package com.netiq.websockify;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.logging.Logger;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;

public class WebsockifySslContext {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebsockifySslContext.class);
    private static final String PROTOCOL = "TLS";
    private SSLContext _serverContext;

    /**
     * Returns the singleton instance for this class
     */
    public static WebsockifySslContext getInstance(String keystore, String password, String keyPassword) {
    	WebsockifySslContext context = SingletonHolder.INSTANCE_MAP.get(keystore);
    	if ( context == null )
    	{
    		context = new WebsockifySslContext ( keystore, password, keyPassword );
    		SingletonHolder.INSTANCE_MAP.put(keystore, context);
    	}
    	return context;
    }

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
     * SingletonHolder.INSTANCE, not before.
     *
     * See http://en.wikipedia.org/wiki/Singleton_pattern
     */
    private static class SingletonHolder {
        public static final HashMap<String, WebsockifySslContext> INSTANCE_MAP = new HashMap<String, WebsockifySslContext>();
    }
    /**
     * Constructor for singleton
     */
    private WebsockifySslContext(String keystore, String password) {
        this ( keystore, password, password );
    }

    /**
     * Constructor for singleton
     */
    private WebsockifySslContext(String keystore, String password, String keyPassword) {
        try {
            SSLContext serverContext = null;
            try {
            	serverContext = getSSLContext(keystore, password, keyPassword);
            } catch (Exception e) {
        		Logger.getLogger(WebsockifySslContext.class.getName()).severe("Error creating SSL context for keystore " + keystore + ": " + e.getMessage());
                throw new Error("Failed to initialize the server-side SSLContext", e);
            }
            _serverContext = serverContext;
        } catch (Exception ex) {
            logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex);
            System.exit(1);

        }
    }

    /**
     * Returns the server context with server side key store
     */
    public SSLContext getServerContext() {
        return _serverContext;
    }

    private static SSLContext getSSLContext ( String keyStoreFilePath, String password, String keyPassword )
    	throws KeyStoreException, FileNotFoundException, CertificateException, NoSuchAlgorithmException,
    	IOException, UnrecoverableKeyException, KeyManagementException
    {
        // Key store (Server side certificate)
        String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
        if (algorithm == null) {
            algorithm = "SunX509";
        }

        KeyStore ks = KeyStore.getInstance("JKS");
        FileInputStream fin = new FileInputStream(keyStoreFilePath);
        ks.load(fin, password.toCharArray());

        // Set up key manager factory to use our key store
        // Assume key password is the same as the key store file
        // password
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
        kmf.init(ks, keyPassword.toCharArray());

        // Initialise the SSLContext to work with our key managers.
        SSLContext context = SSLContext.getInstance(PROTOCOL);
        context.init(kmf.getKeyManagers(), null, null);
        return context;
    }

	/**
	 * Validates that a keystore with the given parameters exists and can be used for an SSL context.
	 * @param keystore - path to the keystore file
	 * @param password - password to the keystore file
	 * @param keyPassword - password to the private key in the keystore file
	 * @return null if valid, otherwise a string describing the error.
	 */
	public static void validateKeystore ( String keystore, String password, String keyPassword )
		throws KeyManagementException, UnrecoverableKeyException, IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException
	{

        getSSLContext(keystore, password, keyPassword);
	}
}