Android Open Source - wifikeyboard Http Connection






From Project

Back to project page wifikeyboard.

License

The source code is released under:

GNU General Public License

If you think the Android project wifikeyboard listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * WiFi Keyboard - Remote Keyboard for Android.
 * Copyright (C) 2011 Ivan Volosyuk/*from   w w w.  j a  v a2s .  c  o m*/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.volosyukivan;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Arrays;

import android.util.Log;

public abstract class HttpConnection {
  // Selector data
  private static final int BUFSIZE = 1024;
  SelectionKey key;
  private SocketChannel ch;
  
  // Stream connection fields
  ByteBuffer outputBuffer;
  private ByteBuffer in;
  {
    in = ByteBuffer.allocate(BUFSIZE);
    in.limit(0);
  }
  
  enum ConnectionState {
    SELECTOR_WAIT_FOR_NEW_INPUT,
    SELECTOR_WAIT_FOR_OUTPUT_BUFFER,
    SELECTOR_NO_WAIT
  }
  
  class ConnectionFailureException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public ConnectionFailureException(String msg) {
      super(msg);
    }
  };
  
  public HttpConnection(SocketChannel ch) {
    this.ch = ch;
    Log.d("wifikeyboard", "new connection");
  }

  public SocketChannel getClient() {
    return ch;
  }

  /// Transport layer functions
  
  /**
   * New data available for the connection.
   * @param ch
   * @return SELECTOR_* new state of connection.
   */
  public final ConnectionState newInput() throws IOException {
    if (in.hasRemaining()) {
      throw new RuntimeException("data remaining in input");
    }
    in.clear();
    int r = ch.read(in);
    if (r == -1) throw new IOException("connection closed on client side");
    if (r == 0) return ConnectionState.SELECTOR_WAIT_FOR_NEW_INPUT;
    in.flip();
    return newRequestChunk();
  }

  public final ConnectionState newOutputBuffer() throws IOException {
    ch.write(outputBuffer);
    if (outputBuffer.hasRemaining()) {
      return ConnectionState.SELECTOR_WAIT_FOR_OUTPUT_BUFFER;
    }
    outputBuffer = null;
    return newRequestChunk();
  }
  
  /// HTTP layer functions
  
  enum HttpConnectionState {
    READ_REQUEST,
    LOOK_REQUEST_HEADERS,
    SKIP_HEADERS,
    READ_HEADER,
    CONSIDER_FORM_DATA,
    READ_FORM_DATA,
    EXECUTE_REQUEST
  };
  
  // Http connection fields
  
  // Buffer references
  private byte[] data;
  private int offset;
  private int limit;
  
  private HttpConnectionState httpConnectionState = HttpConnectionState.READ_REQUEST;
  private boolean isPost;
  
  private ConnectionState newRequestChunk() throws IOException {
    ConnectionState connectionState = ConnectionState.SELECTOR_WAIT_FOR_NEW_INPUT;
    data = in.array();
    offset = in.position();
    limit = in.limit();
    exit:
    while (true) {
      switch (httpConnectionState) {
        case READ_REQUEST:
          if (offset == limit) break exit;
          httpConnectionState = readRequest();
          break;
        case LOOK_REQUEST_HEADERS:
          isPost = request[0] == LETTER_P;
          headerMatcher = lookupRequestHandler();
          if (headerMatcher == null)
            httpConnectionState = HttpConnectionState.SKIP_HEADERS;
          else {
            httpConnectionState = HttpConnectionState.READ_HEADER;
            int numHeaders = headerMatcher.patterns.length;
            for (int i = 0; i < numHeaders; i++) {
              headerLen[i] = 0;
            }
          }
          break;
        case READ_HEADER:
          if (offset == limit) break exit;
          httpConnectionState = readHeader();
          break;
        case SKIP_HEADERS:
          if (offset == limit) break exit;
          httpConnectionState = skipHeaders();
          break;
        case CONSIDER_FORM_DATA:
          httpConnectionState = considerFormData();
          break;
        case READ_FORM_DATA:
          if (offset == limit) break exit;
          httpConnectionState = readFormData();
          break;
        case EXECUTE_REQUEST:
          httpConnectionState = HttpConnectionState.READ_REQUEST;
          in.position(offset);
          connectionState = executeRequest();
          if (connectionState != ConnectionState.SELECTOR_WAIT_FOR_NEW_INPUT) break exit;
      }
      
    }
    in.position(offset);
    return connectionState;
  }
  
  private static final byte LETTER_P = "P".getBytes()[0];
  private static final byte LETTER_COLUMN = ":".getBytes()[0];
  private static final byte LETTER_CR = "\n".getBytes()[0];
  
  protected byte[] request = new byte[BUFSIZE];
  protected int requestLength = 0;
  
  private HttpConnectionState readRequest() {
    int limit = this.limit;
    int requestLength = this.requestLength;
    byte[] data = this.data;
    
    for (int i = offset; i < limit; i++) {
      byte b = data[i];
      if (b == LETTER_CR) {
        // request end
        this.requestLength = requestLength;
        this.offset = i + 1;
        return HttpConnectionState.LOOK_REQUEST_HEADERS;
      } else {
        try {
          request[requestLength++] = b;
        } catch (ArrayIndexOutOfBoundsException e) {
          throw new ConnectionFailureException("request is too large");
        }
      }
    }
    this.offset = limit;
    this.requestLength = requestLength;
    return HttpConnectionState.READ_REQUEST;
  }
  
  private HeaderMatcher headerMatcher;
  private int matchedChars = 0;
  private int matchedMatcher = 0;
  
  enum HeaderState {
    MATCH_HEADER_NAME,
    SKIP_HEADER_NAME,
    STORE_HEADER_DATA,
    SKIP_HEADER_DATA,
    NO_MORE_HEADERS
  }

  private HeaderState headerState = HeaderState.MATCH_HEADER_NAME;
  protected byte[][] header = new byte[2][256];
  protected int[] headerLen = new int[2];
  private byte[] currentHeader;
  private int currentHeaderLen;

  private HttpConnectionState readHeader() {
    while (offset != limit) {
      switch (headerState) {
      case MATCH_HEADER_NAME:
        headerState = matchHeaderName();
        break;
      case SKIP_HEADER_NAME:
        headerState = skipHeaderName();
        if (headerState == HeaderState.NO_MORE_HEADERS) {
          headerState = HeaderState.MATCH_HEADER_NAME;
          return HttpConnectionState.CONSIDER_FORM_DATA;
        }
        break;
      case SKIP_HEADER_DATA:
        headerState = skipHeaderData();
        break;
      case STORE_HEADER_DATA:
        headerState = storeHeaderData();
        break;
      }
    }
    return HttpConnectionState.READ_HEADER;
  }
  
  // match bbde
  // abc   [ matched = 0]
  // bbce  [ matched=2 skip = 0]
  // ...
  // e, skip=0   (failed) 
  // bbcd, skip=3 (match not possible, skip to next)
  // bbx, skip=2  (try match)
  private HeaderState matchHeaderName() {
    byte[] data = this.data;
    int limit = this.limit;
    int matchedChars = this.matchedChars;
    int matchedMatcher = this.matchedMatcher;
    
    HeaderMatcher matcher = headerMatcher;
    byte[][] patterns = matcher.patterns;
    int npatterns = patterns.length;
    byte[] pattern = patterns[matchedMatcher];
    int i;
    
    outer:
    for (i = offset; i < limit; ) {
      byte b = data[i];
      while (true) {
        if (pattern[matchedChars] == b) {
          matchedChars++;
          i++;
          if (b == LETTER_COLUMN) {
            // fully matched
            this.offset = i + 1;
            this.matchedChars = 0;
            this.currentHeader = this.header[this.matchedMatcher];
            this.currentHeaderLen = 0;
            return HeaderState.STORE_HEADER_DATA;
          }
          continue outer;
        }
        
        inner:
        while (true) {
          matchedMatcher++;
          if (matchedMatcher >= npatterns)
            return HeaderState.SKIP_HEADER_NAME;
          int similarity = headerMatcher.similarity[matchedMatcher];
          if (similarity < matchedChars) {
            this.matchedChars = matchedChars;
            return HeaderState.SKIP_HEADER_NAME;
          } else if (similarity > matchedChars) {
            // will not match, skip this pattern
            continue inner;
          } else {
            // try match this pattern
            pattern = patterns[matchedMatcher];
            continue outer;
          }
        }
      }
    }
    offset = i;
    this.matchedChars = matchedChars;
    this.matchedMatcher = matchedMatcher;
    return HeaderState.MATCH_HEADER_NAME;
  }
  
  private HeaderState skipHeaderName() {
    byte[] data = this.data;
    for (int i = offset; i < limit; i++,matchedChars++) {
      byte b = data[i];
      if (b == LETTER_COLUMN) {
        this.offset = i + 1;
        return HeaderState.SKIP_HEADER_DATA;
      } else if (b == LETTER_CR) {
        if (matchedChars > 1) {
          throw new ConnectionFailureException("header format error");
        }
        this.offset = i + 1;
        return HeaderState.NO_MORE_HEADERS;        
      }
    }
    this.matchedChars = 0;
    this.matchedMatcher = 0;
    return HeaderState.MATCH_HEADER_NAME;
  }
  
  private HeaderState skipHeaderData() {
    byte[] data = this.data;
    int limit = this.limit;
    for (int i = offset; i < limit; i++) {
      byte b = data[i];
      if (b == LETTER_CR) {
        this.offset = i + 1;
        this.matchedChars = 0;
        this.matchedMatcher = 0;
        return HeaderState.MATCH_HEADER_NAME;
      }
    }
    this.offset = limit;
    return HeaderState.SKIP_HEADER_DATA;
  }
  
  private HeaderState storeHeaderData() {
    byte[] data = this.data;
    int limit = this.limit;
    int currentHeaderLen = this.currentHeaderLen;
    byte[] currentHeader = this.currentHeader;
    for (int i = offset; i < limit; i++) {
      byte b = data[i];
      if (b == LETTER_CR) {
//        Log.d("wifikeyboard", "header value = " + new String(currentHeader, 0, currentHeaderLen));
        this.header[this.matchedMatcher] = currentHeader;
        this.headerLen[this.matchedMatcher] = currentHeaderLen;
        this.offset = i + 1;
        this.matchedChars = 0;
        this.matchedMatcher = 0;
        return HeaderState.MATCH_HEADER_NAME;
      } else {
        currentHeader[currentHeaderLen++] = b;
      }
    }
    this.offset = limit;
    this.currentHeaderLen = currentHeaderLen;
    return HeaderState.STORE_HEADER_DATA;
  }

  
  private int skipLineLen = 0;
  private HttpConnectionState skipHeaders() {
    byte[] data = this.data;
    int skipLineLen = this.skipLineLen;
    int limit = this.limit;
    for (int i = offset; i < limit; i++) {
      if (data[i] == LETTER_CR) {
        if (skipLineLen < 2) {
          // no more headers
          offset = i + 1;
          this.skipLineLen = 0;
          return HttpConnectionState.CONSIDER_FORM_DATA;
        } else {
          skipLineLen = 0;
        }
      } else {
        skipLineLen++;
      }
    }
    this.offset = limit;
    this.skipLineLen = skipLineLen;
    return HttpConnectionState.SKIP_HEADERS;
  }
  
  
  
//  = new RequestHandler(new HeaderMatcher("Accept-Language","Accept-Encoding")) {
//    @Override
//    public ByteBuffer processQuery(byte[] query, int offset, int limit) {
//      return null;
//    }
//  };
  
  public abstract HeaderMatcher lookupRequestHandler();
  
  // FIXME: dummy form data implementation
  byte[] formData = new byte[BUFSIZE];
  int formDataLength = 0;
  private int formDataExpectedLength = 0;

  private HttpConnectionState readFormData() {
    byte[] data = this.data;
    int limit = this.limit;
    int formDataLength = this.formDataLength;
    int formDataExpectedLength = this.formDataExpectedLength;
    
    for (int i = offset; i < limit; i++) {
      byte b = data[i];
      try {
        formData[formDataLength++] = b;
      } catch (ArrayIndexOutOfBoundsException e) {
        throw new ConnectionFailureException("request is too large");
      }
      if (formDataLength == formDataExpectedLength) {
        // request end
        this.formDataLength = formDataLength;
        this.offset = i + 1;
//        Log.d("wifikeyboard", "Form data: " + new String(formData, 0, formDataLength));
        return HttpConnectionState.EXECUTE_REQUEST;
      } else {
      }
    }
    this.offset = limit;
    this.formDataLength = formDataLength;
    return HttpConnectionState.READ_FORM_DATA;
  }
  
  private static final byte[] ACCEPTED_CONTENT_TYPE =
    "application/x-www-form-urlencoded".getBytes();
  private static byte LETTER_ZERO = "0".getBytes()[0];
  
  private HttpConnectionState considerFormData() {
    if (!isPost) {
      return HttpConnectionState.EXECUTE_REQUEST;
    }
    
    byte[] contentType = header[0]; 
//    if (ACCEPTED_CONTENT_TYPE.length != headerLen[0]) {
//      throw new ConnectionFailureException("unsupported content type");
//    }
//    int len = ACCEPTED_CONTENT_TYPE.length;
//    for (int i = 0; i < len; i++) {
//      if (ACCEPTED_CONTENT_TYPE[i] != contentType[i])
//        throw new ConnectionFailureException("unsupported content type");
//    }
    
    int contentLen = 0;
    int len = headerLen[0] - 1;
    byte[] contentLenHeader = header[0];
    for (int i = 0; i < len; i++) {
      contentLen = (contentLen * 10) + contentLenHeader[i] - LETTER_ZERO;
    }
    
    this.formDataExpectedLength = contentLen;
    
    if (contentLen == 0) {
      return HttpConnectionState.EXECUTE_REQUEST;
    }
    if (formData.length < contentLen) {
      formData = new byte[contentLen];
    }
    return HttpConnectionState.READ_FORM_DATA;
  }
  
  protected abstract ByteBuffer requestHandler();
  
  private ConnectionState executeRequest() throws IOException {
    ByteBuffer output = requestHandler();
    // Cleanup
    requestLength = 0;
    formDataLength = 0;
    
    if (output == null) {
      return ConnectionState.SELECTOR_NO_WAIT;
    }
    
    // FIXME: check that this doesn't block
    ch.write(output);
    
    if (output.hasRemaining()) {
      outputBuffer = output;
      return ConnectionState.SELECTOR_WAIT_FOR_OUTPUT_BUFFER;
    }
    
    return ConnectionState.SELECTOR_WAIT_FOR_NEW_INPUT;
  }
  
  static class HeaderMatcher {
    byte[][] patterns;
    int[] similarity;
    public HeaderMatcher(String...strings) {
      patterns = new byte[strings.length][];
      similarity = new int[strings.length];
      String prevString = null;
      int nstr = 0;
      
      for (String s : strings) {
        int match = 0;
        if (prevString != null) {
          int len = Math.min(s.length(), prevString.length());
          for (int i = 0; i < len; i++) {
            if (s.charAt(i) != prevString.charAt(i)) break;
            match++;
          }
        }
        similarity[nstr] = match;
        patterns[nstr++] = (s + ":").getBytes();
        prevString = s;
      }
    }
  };
  
//  @Deprecated
//  private String parseRequest(String line) {
//    String[] parts = line.split(" ");
//    if (parts.length != 3) {
//      throw new ConnectionFailureException("malformed req: " + line);
//    }
//    String url = parts[1];
//    String[] urlParts = url.split("/", -2);
//    return urlParts[urlParts.length - 1];
//  }

//  /**
//   * Obtain response for the request.
//   * @param url
//   * @return
//   */
//  @Deprecated
//  protected abstract ByteBuffer processRequest(String url);

  public void setKey(SelectionKey clientkey) {
    this.key = clientkey;
  }
}




Java Source Code List

com.volosyukivan.Debug.java
com.volosyukivan.HttpConnection.java
com.volosyukivan.HttpServer.java
com.volosyukivan.HttpService.java
com.volosyukivan.KeyboardHttpConnection.java
com.volosyukivan.KeyboardHttpServer.java
com.volosyukivan.KeycodeConvertor.java
com.volosyukivan.WiFiInputMethod.java
com.volosyukivan.WiFiKeyboard.java
com.volosyukivan.WidgetActivity.java
com.volosyukivan.WidgetConfigure.java
com.volosyukivan.WidgetProvider.java