inohilog

/var/log/inohiro.log

はてなダイアリーにAtomPudで投稿する

1年以上前にはてなダイアリーに新しい記事を投稿するコードを書いたんですが、今回はてなダイアリーがAtomPub(Atom Publishing Protocol)に対応したと言うことで、急に新しくコードを書いてみた。
認証部分が「WSSE認証」になり、いきなり戸惑ったんだけど先駆者達のコードを参考に書いてみました。

とりあえずこれでPost出来ます。Postする時のXmlをXDocumentで作ろうとした形跡がありますが、XML宣言がXDocument(XElement)のsaveメソッドを読んだときにしか追加されない(オブジェクト自体に追加されるわけではなく、保存されたファイルに追加される)ので、結局StringBuilderでXMLを書いています。XDocumentの方から宣言を設定もしくは取得するプロパティも有るんだけど、それを使ってもダメ。これはどうすれば。。。

public string Post( string username, string password, string title, string content )
{
	string header = CreateWsseHeader( username, password );
	
         // XML宣言を追加することが出来ない。。。
	/*
	XNamespace ns = "http://purl.org.atom/ns#";
	XDocument article = new XDocument(
		new XDeclaration( "1.0", "utf-8", "yes" ),
		new XElement( ns + "entry",
			new XElement( "title", "title2" ),
			new XElement( "content", new XAttribute( "type", "text/plain" ), "content2" ),
			new XElement( "updated", DateTime.Now.ToString( "o", new CultureInfo( "ja-jp" ) ) ) )
		); 
	 article.Declaration = new XDeclaration( "1.0", "utf-8", "no" );
	*/

     	StringBuilder xml = new StringBuilder();
	xml.Append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" );
	xml.Append( "<entry xmlns=\"http://purl.org/atom/ns#\">" );
	xml.Append( "<title>" );
	xml.Append( title );
	xml.Append( "</title>" );
	xml.Append( "<content type=\"text/plain\">" );
	xml.Append( content );
	xml.Append( "</content>" );
	xml.Append( "<updated>" );
	xml.Append( DateTime.Now.ToString( "o", new CultureInfo( "ja-jp" ) ) );
	xml.Append( "</updated></entry>" );

	HttpWebRequest request = ( HttpWebRequest )WebRequest.Create( "http://d.hatena.ne.jp/InoHiro/atom/blo
	request.Method = "POST";
	request.Headers.Add( "X-WSSE", header );
	request.ContentType = "application/x.atom+xml";

	Stream requestream = request.GetRequestStream();
	byte[] data = Encoding.UTF8.GetBytes( xml.ToString() );
	requestream.Write( data, 0, data.Length );
	requestream.Close();

	HttpWebResponse response = ( HttpWebResponse ) request.GetResponse();
	if( response.StatusCode == HttpStatusCode.Created )
		return "created!";
	else
		return "failed...";
}

private string CreateWsseHeader( string username, string password )
{
	// HTTPリクエスト毎に生成するセキュリティ・トークン(ランダム文字列)
	byte[] b_nonce = new byte[8];
	Random rand = new Random();
	rand.NextBytes( b_nonce );

	// nonce 生成時の日時
	string created = DateTime.Now.ToUniversalTime().ToString( "o" );
	byte[] b_created = Encoding.UTF8.GetBytes( created );

	byte[] b_password = Encoding.UTF8.GetBytes( password );
	SHA1Managed sh1 = new SHA1Managed();
	sh1.Initialize();

	// Nonce, Created, パスワードを連結
	byte[] origin = new byte[b_nonce.Length + b_created.Length + b_password.Length];
	Array.Copy( b_nonce, 0, origin, 0, b_nonce.Length );
	Array.Copy( b_created, 0, origin, b_nonce.Length, b_created.Length );
	Array.Copy( b_password, 0, origin, b_nonce.Length + b_created.Length, b_password.Length );

	// ハッシュ値を生成
	byte[] passwordDigest = sh1.ComputeHash( origin );

	string header = string.Format(
		"UsernameToken Username=\"{0}\", PasswordDigest=\"{1}\", Nonce=\"{2}\", Created=\"{3}\"",
		username, Convert.ToBase64String( passwordDigest ), Convert.ToBase64String( b_nonce ), created );

	return header;
}

とりあえず今後編集(Put)とか削除(Delete)のところも書いていきたいなーと。実はこれまでのコードをクラスライブラリにして公開しようと思って、コードを書き直していたところだったので、残念。あとはRubyでも書いてみますかね。忘れないように。
まあ認証のところとか簡単になって、わかりやすくなったので良いと思う。
あと例外処理をちゃんと書いておかないと400が返ってきたときに例外で落ちる。

参考情報

C#

WSSE認証について。サンプルが欲しかったのでググったんですが、コードを公開してくださっていたので大変参考になりました。
C#でWSSE認証クライアント