Ruby1.8.6でWebDavクライアント

RubyWebDavクライアントを探してみるも、信頼できそうなものがない。Rubyソースコードを見てみると標準添付のライブラリにWebdav対応コードが実装されている。下のコードで動作した。
Ruby1.9の機能として公式リファレンスに載っているメソッド(HTTPRequest#body_stream=)を使用したが、Ruby1.8.6でも正常に動作した。
ただしファイル名に日本語が含まれる場合は、文字コードに注意する必要がある。Apache Webdavにアップロードする場合は適切にサーバ側で文字コード変換をしてくれるようだが、ダウンロードする場合は、Windowsの場合はダウンロード先ファイル名をShift_JISで指定する必要がある。

require 'net/http'
#
# webdav-client
#
class WebdavClient
  #
  # webdav_url: host url (ex http://hostname:port/path/to/ )
  # proxy_addr(option) : proxy_addr (ex proxy.host.com )
  # proxy_port(option) : proxy_port (ex 3128)
  def initialize(webdav_url, proxy_addr = nil, proxy_port = nil)
    unless /([a-z]+):\/\/([a-zA-Z0-9\.]+)(:[0-9]+){0,1}(.*)/ =~ webdav_url
      raise ArgumentError.new("Invalid URL #{webdav_url}")
    end
    address = $2
    port = $3 || 80
    port.delete(':') if port.is_a?(String)
    basic_path = $4 || '/'
    basic_path = basic_path + '/' unless basic_path[basic_path.length - 1].chr() == '/'
    @basic_path = basic_path
    @http = Net::HTTP.new(address, port, proxy_addr, proxy_port)
  end
  
  # =put=
  # put file
  #
  # src_path: local file path
  # dest_path: remote-server destination path
  def put(src_path, dest_path = nil)
    dest_path = File.basename(src_path) if dest_path.nil?()
    req = Net::HTTP::Put.new(@basic_path + dest_path)
    req.content_length = File.size(src_path)
    File.open(src_path, 'rb'){ |io|
      req.body_stream = io
      res = @http.request(req)
      raise Exception.new("Invalid HttpResponse Code: #{res.code} #{res.message}") unless res.code == '201'
    }
  end
  
  # =mkdir=
  # make directory
  #
  # path: remote-server path
  def mkdir(path)
    req = Net::HTTP::Mkcol.new(@basic_path + path)
    res = @http.request(req)
    raise Exception.new("Invalid HttpResponse Code: #{res.code} #{res.message}") unless res.code == '201'
  end

  # =delete=
  # delete file, delete directory
  #
  # path: remote-server path
  def delete(path)
    req = Net::HTTP::Delete.new(@basic_path + path)
    res = @http.request(req)
    raise Exception.new("Invalid HttpResponse Code: #{res.code} #{res.message}") unless res.code == '204'
  end

  # =get=
  # get file
  #
  # src_path: remote-server path
  # dest_path: local file path
  def get(src_path, dest_path = nil)
    dest_path = File.basename(src_path) if dest_path.nil?()
    req = Net::HTTP::Get.new(@basic_path + src_path)
    res = @http.request(req)
    raise Exception.new("Invalid HttpResponse Code: #{res.code} #{res.message}") unless res.code == '200'
    File.open(dest_path, "wb"){|io|
      io.write(res.body)
    }
  end

  # =copy=
  # copy file to file on webdav-server
  #
  # src_path: remote-server source file path
  # dest_path: remote-server destination file path
  def copy(src_path, dest_path)
    req = Net::HTTP::Copy.new(@basic_path + src_path)
    req['Destination'] = @basic_path + dest_path
    res = @http.request(req)
    raise Exception.new("Invalid HttpResponse Code: #{res.code} #{res.message}") unless res.code == '204'
  end

  # =move=
  # rename file
  #
  # src_path: remote-server source file path
  # dest_path: remote-server destination file path
  def move(src_path, dest_path)
    req = Net::HTTP::Move.new(@basic_path + src_path)
    req['Destination'] = @basic_path + dest_path
    res = @http.request(req)
    raise Exception.new("Invalid HttpResponse Code: #{res.code} #{res.message}") unless res.code == '204'
  end
end

使い方

#http://hostname:80/webdav/で初期化
w = WebdavClient.new('http://hostname:80/webdav/')
w.put('test.txt') #test.txtをアップロード
w.get('test.txt') #test.txtをダウンロード
w.mkdir('test') #testディレクトリ作成
w.copy('test.txt', 'test2.txt') #Webdavサーバ上でtest.txtをtest2.txtにコピー
w.move('test2.txt', 'test3.txt') #Webdavサーバ上でtest2.txtをtest3.txtにリネーム
w.delete('test') #testディレクトリ削除
w.delete('test.txt') #test.txtファイル削除
require 'kconv'
w.put('test.txt', 'テスト.txt') #test.txtをテスト.txtとしてアップロード
w.get('テスト.txt', 'テスト.txt'.tosjis()) #テスト.txtをテスト.txt(Shift_JIS)としてダウンロード