Hatena::ブログ(Diary)

in the mythosil

2012-05-12

jQueryのgetJSONでハマった

JSONPAPI叩くときにjQueryのgetJSONは非常に便利ですが、

var url = "http://some/where?callback=?";
var param = {
    q: query,
    callback: callback, // NG
};
$.getJSON(url, param, callback);

function callback(json) {
    console.log(json);
}

なんて書いてしまうとcallback関数引数jsonがundefinedになる。

ちゃんとcallback関数が呼ばれるからタチが悪い。


javascript歴1ヶ月程度の自分にとっては、なかなか手強いバグでした。

2011-11-18

Perlで手軽に非同期TCPサーバ

yokohama.pmに行ってきて、またperl熱が上がってきたので、久しぶりにコード書いてみた。

AnyEvent::TCPServer

AnyEvent::Socketを使って非同期サーバ書こうとすると色々面倒くさかったりするので、もっと手軽に書けるモジュールを作ってみました。こんな感じで非同期のechoサーバを書けます。

use strict;                                                                                                                        
use warnings;
use 5.010;

use AnyEvent::TCPServer;

on_connect(sub {
  say "connection accepted";
});

on_read(line => sub {
  my ($handle, $line) = @_; 
  say "got line: $line";
  $handle->push_write("$line\n");
});

on_eof(sub {
  say "connection closed";
});

on_error(sub {
  my ($handle, $fatal, $message) = @_; 
  say "error: $message";
});

server->run(
  host => undef,
  port => 1986,
);

各イベントの処理を追加していって、最後にrunでサーバを起動します。

handleをdestroyするといった処理はモジュール側でやってしまうので書く必要はありません。

今後

今だと簡単な処理しか扱えないので、on_drainなどの処理を登録できるようにするのが当面の目標です。AnyEvent::Handleのイベントは一通り網羅できたほうがいいでしょう。

ソース

小さいモジュールだし、githubリポジトリ作るほど作り込んでないので、モジュールのソース貼っておきます。

package AnyEvent::TCPServer;                                                                                                       
use strict;
use warnings;

use AnyEvent::Socket;
use AnyEvent::Handle;

sub import {
  my $class = shift;
  my $app = $class->new;

  $SIG{PIPE} = "IGNORE";

  no strict 'refs';
  my $caller = caller;
  push @{"${caller}::ISA"}, $class;

  no warnings 'redefine';
  *{"${caller}::new"} = *{"${caller}::server"} = sub { $app };
  *{"${caller}::on_connect"} = sub (@) { $app->register_on_connect(shift) };
  *{"${caller}::on_read"}    = sub (%) { $app->register_on_read(@_) };
  *{"${caller}::on_eof"}     = sub (@) { $app->register_on_eof(shift) };
  *{"${caller}::on_error"}   = sub (@) { $app->register_on_error(shift) };

  strict->import;
  warnings->import;
}

sub new {
  my $class = shift;
  bless {
    on_connect => undef,
    on_read    => undef,
    on_eof     => undef,
    on_error   => undef,
    handles    => +{},
  }, $class;
}

sub run {
  my ($self, %config) = @_;

  my $cv = AE::cv;
  tcp_server $config{host}, $config{port}, sub {
    my ($fh) = @_;

    my $handle; $handle = AnyEvent::Handle->new(
      fh => $fh,
      on_read => sub {
        $handle->push_read(%{$self->{on_read}}) if defined $self->{on_read};
      },
      on_eof => sub {
        $self->{on_eof}->(@_) if defined $self->{on_eof};
        $handle->destroy;
        delete $self->{handles}->{$fh};
      },
      on_error => sub {
        $self->{on_error}->(@_) if defined $self->{on_error};
        $handle->destroy;
        $cv->send;
      },
    );
    $self->{handles}->{$fh} = $handle;
    $self->{on_connect}->($handle) if defined $self->{on_connect};
  };
  $cv->recv;
}

sub register_on_connect {
  my ($self, $on_connect) = @_;
  $self->{on_connect} = $on_connect;
}

sub register_on_read {
  my ($self, %on_read) = @_;
  $self->{on_read} = \%on_read;
}

sub register_on_eof {
  my ($self, $on_eof) = @_;
  $self->{on_eof} = $on_eof;
}

sub register_on_error {
  my ($self, $on_error) = @_;
  $self->{on_error} = $on_error;
}

1;

アドバイス等頂けると幸いです。

2011-10-26

AES128暗号化 - Java編 -

先日iPhone(というかObjective-C)でのAES128暗号化について触れましたが、今回はJava編です。未確認ですが、おそらくAndroidでも動きます。

/*
 *  $ javac CryptAES.java
 *  $ java CryptAES <secret_key> <iv> <message>
 */
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

class CryptAES {
  static final String cipher_type = "AES/CBC/PKCS5Padding";

  public static void main(String[] args) {
    String key  = args[0];
    String iv   = args[1];
    String data = args[2];

    byte[] enc = encode(key, iv, data.getBytes());
    byte[] dec = decode(key, iv, enc);

    for (int i = 0; i < enc.length; i++) {
      System.out.printf("%02x", enc[i]);
    }
    System.out.println();
        
    System.out.println(new String(dec));
  }

  public static byte[] encode(String skey, String iv, byte[] data) {
    return process(Cipher.ENCRYPT_MODE, skey, iv, data);
  }

  public static byte[] decode(String skey, String iv, byte[] data) {
    return process(Cipher.DECRYPT_MODE, skey, iv, data);
  }

  private static byte[] process(int mode, String skey, String iv, byte[] data) {
    SecretKeySpec key = new SecretKeySpec(skey.getBytes(), "AES");
    AlgorithmParameterSpec param = new IvParameterSpec(iv.getBytes());
    try {
      Cipher cipher = Cipher.getInstance(cipher_type);
      cipher.init(mode, key, param);
      return cipher.doFinal(data);
    } catch (Exception e) {
      System.err.println(e.getMessage());
      throw new RuntimeException(e);
    }
  }

}

今回はivは指定のものを使うようにしてありますが、特に指定がなければ自動で生成されますし、そのivをあとから取り出すことももちろん可能です。

パディング方式に関しては外部ライブラリをいれない限りはPKCS7が使えません。ただし、デフォルトで使えるPKCS5は7と互換性が(ほぼ)あるため、特に問題が起きることはないでしょう(逆にPKCS7のライブラリバグってるという話もあるので、無理にPKCS7にしようとすると本末転倒な結果になりかねません)。

それから、128bitの指定などは特にしていないのですが、secret keyやivの長さ(どちらも16バイト)から自動で判別しているのだと思います。secret keyの長さを変えれば自動で256bitになったりするのでしょうか(要検証)。

Javaは普段使ってないので、もしマズいところがあればご指摘頂けるとありがたいです。

2011-10-17

iPhoneでAES128暗号化

iPhoneアプリ開発で暗号化を行う必要が出てきたので、試しに書いてみました。

暗号方式にはAES128を、Padding方式にはPKCS7を使っています。

ここで使っているCommonCryptoはiOS SDKについてくるものですが、mac上でも普通に使うことができます(iPhone Simulator用のライブラリを無理矢理使います)。

使い方としては、下のコードをコンパイルして、

$ ./a.out aaaabbbbccccdddd eeeeffffgggghhhh message

というようにすると"message"が暗号化され、さらに続けて復号化されます。

第一引数は鍵、第二引数はInitial Vectorです。どちらも16バイトである必要があります。

/*                                                                                                  
 * gcc -std=c99 crypto.m -framework Foundation
 */
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCryptor.h>

@interface NSData (AES)
- (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv;
- (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv;
- (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv;
@end

@implementation NSData (AES)
- (NSData *)AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv
{
  char keyPtr[kCCKeySizeAES128 + 1]; 
  memset(keyPtr, 0, sizeof(keyPtr));
  [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

  char ivPtr[kCCBlockSizeAES128 + 1]; 
  memset(ivPtr, 0, sizeof(ivPtr));
  [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];

  NSUInteger dataLength = [self length];
  size_t bufferSize = dataLength + kCCBlockSizeAES128;
  void *buffer = malloc(bufferSize);

  size_t numBytesCrypted = 0;
  CCCryptorStatus cryptStatus = CCCrypt(operation,
                                        kCCAlgorithmAES128,
                                        kCCOptionPKCS7Padding,
                                        keyPtr,
                                        kCCBlockSizeAES128,
                                        ivPtr,
                                        [self bytes],
                                        dataLength,
                                        buffer,
                                        bufferSize,
                                        &numBytesCrypted);
  if (cryptStatus == kCCSuccess) {
    return [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
  }
  free(buffer);
  return nil;
}

- (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv
{
  return [self AES128Operation:kCCEncrypt key:key iv:iv];
}

- (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv
{
  return [self AES128Operation:kCCDecrypt key:key iv:iv];
}
@end

int main(int argc, char const* argv[])
{
  NSAutoreleasePool* pool;
  pool = [[NSAutoreleasePool alloc] init];

  NSString *key = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
  NSString *iv = [NSString stringWithCString:argv[2] encoding:NSUTF8StringEncoding];
  NSString *data_str = [NSString stringWithCString:argv[3] encoding:NSUTF8StringEncoding];
  NSData *data = [data_str dataUsingEncoding:NSUTF8StringEncoding];

  NSData *en_data = [data AES128EncryptWithKey:key iv:iv];
  NSData *de_data = [en_data AES128DecryptWithKey:key iv:iv];

  NSString *de_str = [[[NSString alloc] initWithData:de_data
                                            encoding:NSUTF8StringEncoding] autorelease];

  NSLog(@"%@", en_data);
  NSLog(@"%@", de_str);

  [pool drain];
  return 0;
}

暗号の話は難しいですね。勉強が必要です。


補足(2011/10/18):

CCCryptはデフォルトでCBC(cipher block chaining)を使うようになっています。ECB(electronic codebook)を指定することもできますが、使う理由は特にないでしょう。

2011-08-21

libeventでHTTPサーバを試す

libeventに含まれるevhttpを使って、簡単なhttpサーバを作ってみました。

404を返す

まずは404 not foundを返すコードを書いてみます。ただし、GET以外のメソッドの場合はBad Requestを返すようにします。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <event.h>                                                                                  
#include <evhttp.h>

#define HTTPD_ADDR "0.0.0.0"
#define HTTPD_PORT 8080

void req_handler(struct evhttp_request *r, void *arg)
{
  if (r->type != EVHTTP_REQ_GET)
    evhttp_send_error(r, HTTP_BADREQUEST, "Available GET only");
  else
    evhttp_send_error(r, HTTP_NOTFOUND, "Not Found");
}

int main(int argc, const char* argv[])
{
  struct event_base *ev_base;
  struct evhttp *httpd;

  ev_base = event_base_new();
  httpd = evhttp_new(ev_base);
  if (evhttp_bind_socket(httpd, HTTPD_ADDR, HTTPD_PORT) < 0) {
    perror("evhttp_bind_socket()");
    exit(EXIT_FAILURE);
  }
  evhttp_set_gencb(httpd, req_handler, NULL);
  event_base_dispatch(ev_base);
  evhttp_free(httpd);
  event_base_free(ev_base);

  return 0;
}

コンパイル・実行して、ブラウザlocalhost:8080にアクセスすると"Not Found"と表示されます。

plain textを返す

今度はtext/plainで"Hello World"と返すようにしてみます。main関数は上のものと同じなので省略します。

void req_handler(struct evhttp_request *r, void *arg)
{
  struct evbuffer *evbuf;
  char message[] = "Hello World";
  int message_length = strlen(message);
  char content_length[8];

  evbuf = evbuffer_new();
  if (evbuf == NULL) {
    evhttp_send_error(r, HTTP_SERVUNAVAIL, "Failed to create buffer");
    return;
  }

  snprintf(content_length, 7, "%d", message_length);
  evhttp_add_header(r->output_headers, "Content-Type", "text/plain");
  evhttp_add_header(r->output_headers, "Content-Length", content_length);
  evbuffer_add(evbuf, message, message_length);
  evhttp_send_reply(r, HTTP_OK, "", evbuf);
  evbuffer_free(evbuf);
}

少し複雑になってきましたが、基本的にはheaderを用意して、struct evbufferにbodyを書き込んで送る、というだけです。

もう少しマシなhttpサーバ

エラー対策などはかなり無視しますが、それなりに動くhttpサーバを書いてみます。

#define DOCUMENT_ROOT "."

static const char *get_mime_type(const char *filepath)
{
  const char *filetype = strrchr(filepath, '.');

  if (strcasecmp(filetype, ".html") == 0 ||
      strcasecmp(filetype, ".htm") == 0)
    return "text/html";
  else if (strcasecmp(filetype, ".js") == 0)
    return "text/javascript";
  else if (strcasecmp(filetype, ".css") == 0)
    return "text/css";
  else if (strcasecmp(filetype, ".txt") == 0)
    return "text/plain";
  else if (strcasecmp(filetype, ".ico") == 0)
    return "image/x-icon";
  else if (strcasecmp(filetype, ".png") == 0)
    return "image/png";
  else if (strcasecmp(filetype, ".gif") == 0)
    return "image/gif";
  else if (strcasecmp(filetype, ".jpeg") == 0 ||
           strcasecmp(filetype, ".jpg") == 0)
    return "image/jpeg";
  else if (strcasecmp(filetype, ".pdf") == 0)
    return "application/pdf";
  return "application/octet-stream";
}

static const char *get_content_from_file(const char *req_path, char **content, int *content_length)
{
  struct stat sb;
  static char filepath[1024];
  FILE *fp;
  int rsize;

  *content = NULL;
  sprintf(filepath, "%s%s", DOCUMENT_ROOT, req_path);

  if (stat(filepath, &sb) < 0)
    return filepath;

  if (S_ISDIR(sb.st_mode)) {
    if (strcmp(req_path, "/") == 0)
      sprintf(filepath, "%s%sindex.html", DOCUMENT_ROOT, req_path);
    else
      sprintf(filepath, "%s%s/index.html", DOCUMENT_ROOT, req_path);

    if (stat(filepath, &sb) < 0)
      return filepath;
  }

  *content_length = (int)sb.st_size;

  fp = fopen(filepath, "rb");
  if (fp) {
    *content = (char *)malloc(*content_length);
    rsize = fread(*content, 1, *content_length, fp);
    fclose(fp);                                                                                     
    if (rsize != *content_length) {
      free(*content);
      *content = NULL;
    }
  }

  return filepath;
}

void req_handler(struct evhttp_request *r, void *arg)
{
  struct evbuffer *evbuf;
  const char *req_path, *res_path;
  char *content;
  int content_length;
  char content_length_str[12];

  if (r->type != EVHTTP_REQ_GET) {
    evhttp_send_error(r, HTTP_BADREQUEST, "Available GET only");
    return;
  }

  req_path = evhttp_request_uri(r);
  res_path = get_content_from_file(req_path, &content, &content_length);
  if (content == NULL) {
    evhttp_send_error(r, HTTP_NOTFOUND, "Not Found");
    return;
  }
  sprintf(content_length_str, "%d", content_length);

  evhttp_add_header(r->output_headers, "Content-Type", get_mime_type(res_path));
  evhttp_add_header(r->output_headers, "Content-Length", content_length_str);

  evbuf = evbuffer_new();
  evbuffer_add(evbuf, content, content_length);
  evhttp_send_reply(r, HTTP_OK, "", evbuf);
  evbuffer_free(evbuf);
}

mime typeが全然足りないとか、mime typeの判別はハッシュ使ったほうがいいとかは気にしないでください。とりあえずそれなりにまともな動作をします。

まとめ

evhttpの基本的な使い方は分かったので、あとは細かいことをやっていくだけですね。わざわざhttpサーバを書く機会も滅多にないと思いますが、楽しかったので満足です。