sekka 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +22 -0
- data/bin/.gitignore +1 -0
- data/bin/sekka-jisyo +98 -0
- data/bin/sekka-server +83 -0
- data/emacs/http-cookies.el +416 -0
- data/emacs/http-get.el +448 -0
- data/emacs/sekka.el +1069 -0
- data/lib/sekka/alphabet-lib.nnd +59 -0
- data/lib/sekka/approximatesearch.rb +72 -0
- data/lib/sekka/convert-jisyo.nnd +129 -0
- data/lib/sekka/henkan.nnd +464 -0
- data/lib/sekka/jisyo-db.nnd +184 -0
- data/lib/sekka/kvs.rb +135 -0
- data/lib/sekka/roman-lib.nnd +660 -0
- data/lib/sekka/sekkaversion.rb +6 -0
- data/lib/sekka/util.nnd +64 -0
- data/lib/sekka.ru +36 -0
- data/lib/sekkaconfig.rb +62 -0
- data/lib/sekkaserver.rb +127 -0
- data/test/alphabet-lib.nnd +188 -0
- data/test/approximate-bench.nnd +83 -0
- data/test/common.nnd +51 -0
- data/test/henkan-main.nnd +942 -0
- data/test/jisyo.nnd +94 -0
- data/test/roman-lib.nnd +422 -0
- data/test/util.nnd +100 -0
- metadata +223 -0
data/README
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
Sekka (石火)
|
3
|
+
|
4
|
+
Sekkaはモードレスなローマ字かな漢字変換エンジンです。SKKにインスパイアされています。
|
5
|
+
|
6
|
+
1. 特徴
|
7
|
+
|
8
|
+
1) Emacsの上で動き、次のようなインタフェースを持ちます。
|
9
|
+
日本語変換モードというものはありません。(Emacsのマイナーモードで作成)
|
10
|
+
いつでも[Ctrl-J]キーで、カーソルキーの直前のローマ字が変換可能です。
|
11
|
+
例えば、Nihongo[Ctrl-J] で 「日本語」に変換します。
|
12
|
+
|
13
|
+
2) SKKの大文字小文字指定ルールが適用されます。
|
14
|
+
OkonaU => 行う に変換される。(送り仮名あり)
|
15
|
+
okonaU => 行う アルファベット中に大文字が1文字でもあれば漢字かな混じりとみなされる。
|
16
|
+
okonau => おこなう (全て平仮名)に変換される。
|
17
|
+
Kanji => 漢字,幹事 など、送り仮名なしの漢字に変換される
|
18
|
+
|
19
|
+
3) ローマ字綴りミスはあいまい検索により、適度に救済されます。
|
20
|
+
(Jaro-Winkler文字列類似度による検索)
|
21
|
+
|
22
|
+
4) ローマ字の表記ゆれ、AZIK等の特殊ローマ字にも標準で対応します。(通常ローマ字、AZICなどモード切替なしですべて対応可能)
|
data/bin/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/sekka-jisyo~
|
data/bin/sekka-jisyo
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- mode: nendo; syntax: scheme ; coding: utf-8 -*-
|
3
|
+
require 'nendo'
|
4
|
+
$LOAD_PATH.push( File.dirname(__FILE__) + "/../lib" )
|
5
|
+
core = Nendo::Core.new()
|
6
|
+
core.setArgv( ARGV )
|
7
|
+
core.loadInitFile
|
8
|
+
core.evalStr( <<";;END-OF-SCRIPT" )
|
9
|
+
;;;
|
10
|
+
;;; sekka-jisyo - Sekkaの辞書メンテナンスツール
|
11
|
+
;;;
|
12
|
+
;;; Copyright (c) 2010 Kiyoka Nishiyama <kiyoka@sumibi.org>
|
13
|
+
;;;
|
14
|
+
;;; Redistribution and use in source and binary forms, with or without
|
15
|
+
;;; modification, are permitted provided that the following conditions
|
16
|
+
;;; are met:
|
17
|
+
;;;
|
18
|
+
;;; 1. Redistributions of source code must retain the above copyright
|
19
|
+
;;; notice, this list of conditions and the following disclaimer.
|
20
|
+
;;;
|
21
|
+
;;; 2. Redistributions in binary form must reproduce the above copyright
|
22
|
+
;;; notice, this list of conditions and the following disclaimer in the
|
23
|
+
;;; documentation and/or other materials provided with the distribution.
|
24
|
+
;;;
|
25
|
+
;;; 3. Neither the name of the authors nor the names of its contributors
|
26
|
+
;;; may be used to endorse or promote products derived from this
|
27
|
+
;;; software without specific prior written permission.
|
28
|
+
;;;
|
29
|
+
;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
30
|
+
;;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
31
|
+
;;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
32
|
+
;;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
33
|
+
;;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
34
|
+
;;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
35
|
+
;;; TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
36
|
+
;;; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
37
|
+
;;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
38
|
+
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
39
|
+
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
40
|
+
;;;
|
41
|
+
;;; $Id:
|
42
|
+
;;;
|
43
|
+
(use sekka.convert-jisyo)
|
44
|
+
(use sekka.jisyo-db)
|
45
|
+
|
46
|
+
(define (convert-skk-jisyo filename)
|
47
|
+
(let1 lines (with-open
|
48
|
+
filename
|
49
|
+
(lambda (f)
|
50
|
+
(convert-skk-jisyo-f f)))
|
51
|
+
(for-each print lines)))
|
52
|
+
|
53
|
+
|
54
|
+
(define (load-sekka-jisyo sekka-file target)
|
55
|
+
(with-open
|
56
|
+
sekka-file
|
57
|
+
(lambda (f)
|
58
|
+
(when (not (rxmatch #/.tch$/ target))
|
59
|
+
(set-kvs-type 'memcache))
|
60
|
+
(load-sekka-jisyo-f f target))))
|
61
|
+
|
62
|
+
|
63
|
+
(define (dump-sekka-jisyo sekka-file)
|
64
|
+
(let1 f STDOUT
|
65
|
+
(dump-sekka-jisyo-f f (+ sekka-file ".tch"))))
|
66
|
+
|
67
|
+
|
68
|
+
(define (display-help)
|
69
|
+
(print "Usage : ")
|
70
|
+
(print " sekka-jisyo convert SKK-JISYO.L ... output SEKKA-JISYO to STDOUT")
|
71
|
+
(print " sekka-jisyo load SEKKA-JISYO.L SEKKA-JISYO.L.tch ... load SEKKA-JISYO to DB(*.tch)")
|
72
|
+
(print " sekka-jisyo dump SEKKA-JISYO.L ... dump DB(*.tch) to SEKKA-JISYO(STDOUT)"))
|
73
|
+
|
74
|
+
|
75
|
+
(define (main argv)
|
76
|
+
(cond
|
77
|
+
((= 0 (length argv))
|
78
|
+
(display-help))
|
79
|
+
(else
|
80
|
+
(let1 command (string->symbol (first argv))
|
81
|
+
(cond
|
82
|
+
((eq? 'convert command)
|
83
|
+
(if (< (length argv) 2)
|
84
|
+
(display-help)
|
85
|
+
(convert-skk-jisyo (second argv))))
|
86
|
+
((eq? 'load command)
|
87
|
+
(if (< (length argv) 3)
|
88
|
+
(display-help)
|
89
|
+
(load-sekka-jisyo (second argv) (third argv))))
|
90
|
+
((eq? 'dump command)
|
91
|
+
(if (< (length argv) 2)
|
92
|
+
(display-help)
|
93
|
+
(dump-sekka-jisyo (second argv))))
|
94
|
+
(else
|
95
|
+
(errorf "Error: no such command [%s] \n" command )))))))
|
96
|
+
|
97
|
+
(main *argv*)
|
98
|
+
;;END-OF-SCRIPT
|
data/bin/sekka-server
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'rack'
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/sekkaconfig")
|
8
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/sekka/sekkaversion")
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
DICTDIR = File.expand_path( "~/.sekka-server" )
|
13
|
+
DICTURL = "http://sumibi.org/sekka/dict/" + SekkaVersion.version
|
14
|
+
|
15
|
+
TC_FILE = DICTDIR + "/SEKKA-JISYO.SMALL.tch"
|
16
|
+
SUMFILE = DICTDIR + "/SEKKA-JISYO.SMALL.md5"
|
17
|
+
TC_URL = DICTURL + "/SEKKA-JISYO.SMALL.tch"
|
18
|
+
SUMURL = DICTURL + "/SEKKA-JISYO.SMALL.md5"
|
19
|
+
|
20
|
+
TC_FILE_LIST = [ DICTDIR + "/SEKKA-JISYO.CUSTOM.tch",
|
21
|
+
DICTDIR + "/SEKKA-JISYO.LARGE.tch",
|
22
|
+
DICTDIR + "/SEKKA-JISYO.SMALL.tch" ]
|
23
|
+
|
24
|
+
MEMCACHED = "localhost:11211" # memcahced
|
25
|
+
|
26
|
+
|
27
|
+
def main
|
28
|
+
if not File.directory?( DICTDIR )
|
29
|
+
Dir.mkdir( DICTDIR )
|
30
|
+
STDERR.printf( "Info: created directory [%s]\n", DICTDIR )
|
31
|
+
end
|
32
|
+
|
33
|
+
if not File.exist?( TC_FILE )
|
34
|
+
STDERR.printf( "Info: Downloading SEKKA-JISYO\n" )
|
35
|
+
# 辞書をダウンロードする
|
36
|
+
cmd = sprintf( "curl -o %s %s", TC_FILE, TC_URL )
|
37
|
+
STDERR.printf( "Command : %s\n", cmd )
|
38
|
+
system( cmd )
|
39
|
+
cmd = sprintf( "curl -o %s %s", SUMFILE, SUMURL )
|
40
|
+
STDERR.printf( "Command : %s\n", cmd )
|
41
|
+
system( cmd )
|
42
|
+
|
43
|
+
# チェックサムを確認する
|
44
|
+
downloadSum = ""
|
45
|
+
open( TC_FILE ) { |f|
|
46
|
+
dataBody = f.read
|
47
|
+
downloadSum = Digest::MD5.hexdigest( dataBody )
|
48
|
+
}
|
49
|
+
open( SUMFILE ) { |f|
|
50
|
+
correctSum = f.readline.chomp.split[0]
|
51
|
+
STDERR.printf( " downloaded file's MD5 : %s\n", downloadSum )
|
52
|
+
STDERR.printf( " correct MD5 : %s\n", correctSum )
|
53
|
+
if downloadSum == correctSum
|
54
|
+
STDERR.printf( "Info: downloaded file [%s] verify OK.\n", TC_FILE )
|
55
|
+
else
|
56
|
+
STDERR.printf( "Error: downloaded file [%s] verify NG.\n", TC_FILE )
|
57
|
+
File.unlink( TC_FILE )
|
58
|
+
exit( 1 )
|
59
|
+
end
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# 辞書ディレクトリに存在している辞書ファイルリストを作る
|
64
|
+
list = TC_FILE_LIST.select { |name| File.exist?( name ) }
|
65
|
+
|
66
|
+
# 設定項目をConfigオブジェクトに代入
|
67
|
+
SekkaServer::Config.setup( list[0], MEMCACHED, 12929 )
|
68
|
+
|
69
|
+
# サーバースクリプトのrootディレクトリへ移動
|
70
|
+
FileUtils.cd(File.dirname(__FILE__) + "/../")
|
71
|
+
|
72
|
+
# サーバー起動
|
73
|
+
Rack::Server.start(
|
74
|
+
:environment => "development",
|
75
|
+
:pid => nil,
|
76
|
+
:Port => SekkaServer::Config.listenPort,
|
77
|
+
:Host => "0.0.0.0",
|
78
|
+
:AccessLog => [],
|
79
|
+
:config => "./lib/sekka.ru"
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
main()
|
@@ -0,0 +1,416 @@
|
|
1
|
+
;;; http-cookies.el --- simple HTTP cookies implementation
|
2
|
+
|
3
|
+
;; Copyright (C) 2004, David Hansen
|
4
|
+
|
5
|
+
;; Author: David Hansen <david.hansen@physik.fu-berlin.de>
|
6
|
+
;; Maintainer: David Hansen <david.hansen@physik.fu-berlin.de>
|
7
|
+
;; Version: 1.0.0
|
8
|
+
;; Keywords: hypermedia
|
9
|
+
|
10
|
+
;; This file is not part of GNU Emacs.
|
11
|
+
|
12
|
+
;; This is free software; you can redistribute it and/or modify
|
13
|
+
;; it under the terms of the GNU General Public License as published by
|
14
|
+
;; the Free Software Foundation; either version 2, or (at your option)
|
15
|
+
;; any later version.
|
16
|
+
|
17
|
+
;; This is distributed in the hope that it will be useful,
|
18
|
+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
19
|
+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
20
|
+
;; GNU General Public License for more details.
|
21
|
+
|
22
|
+
;; You should have received a copy of the GNU General Public License
|
23
|
+
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
24
|
+
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
25
|
+
;; Boston, MA 02111-1307, USA.
|
26
|
+
|
27
|
+
|
28
|
+
;;; Commentary:
|
29
|
+
|
30
|
+
;; Implementation of old netscape cookies (used by maybe all servers) and
|
31
|
+
;; version 1 cookies.
|
32
|
+
;;
|
33
|
+
;; See http://www.faqs.org/rfcs/rfc2109.html and
|
34
|
+
;; http://wp.netscape.com/newsref/std/cookie_spec.html
|
35
|
+
|
36
|
+
;;; Change log:
|
37
|
+
|
38
|
+
;;; TODO:
|
39
|
+
|
40
|
+
;; - whitelist
|
41
|
+
;; - blacklist
|
42
|
+
;; - reading from file, saving to file
|
43
|
+
;; - expire
|
44
|
+
|
45
|
+
;;; Code:
|
46
|
+
|
47
|
+
(require 'time-date)
|
48
|
+
|
49
|
+
(defconst http-cookies-version "1.0.0")
|
50
|
+
|
51
|
+
(defgroup http-emacs ()
|
52
|
+
"Simple HTTP client implementation in elisp.")
|
53
|
+
|
54
|
+
(defcustom http-emacs-use-cookies nil
|
55
|
+
"Use cookies in the http-emacs package. *EXPERIMENTAL*"
|
56
|
+
:type 'boolean
|
57
|
+
:group 'http-emacs)
|
58
|
+
|
59
|
+
(defcustom http-emacs-cookie-file "~/.emacs-cookies"
|
60
|
+
"*File where to store the cookies."
|
61
|
+
:type 'file
|
62
|
+
:group 'http-emacs)
|
63
|
+
|
64
|
+
(defconst http-token-value-regexp
|
65
|
+
"^[ \t]*\\(.*?\\)[ \t]*=[ \t]*\"?\\(.*?\\)\"?[ \t]*;?[ \t]*$"
|
66
|
+
"Regexp to match a token=\"value\"; in a cookie.")
|
67
|
+
|
68
|
+
(defvar http-cookies-accept-functions
|
69
|
+
'(http-cookie-check-path
|
70
|
+
http-cookie-check-domain
|
71
|
+
http-cookie-check-hostname)
|
72
|
+
"*List of functions used to determine if we accept a cookie or not.
|
73
|
+
If one of these function returns nil the cookie will be rejected. Each
|
74
|
+
function can access the free variables `cookie', `host' (from the url)
|
75
|
+
`path' (from the URL) and `url' to make its decision.")
|
76
|
+
|
77
|
+
(defvar http-cookies-host-hash
|
78
|
+
(make-hash-table :test 'equal)
|
79
|
+
"Hash to look up cookies by host name.")
|
80
|
+
|
81
|
+
(defvar http-cookies-domain-hash
|
82
|
+
(make-hash-table :test 'equal)
|
83
|
+
"Hash to look up cookies by domain.")
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
;; functions for parsing the header
|
88
|
+
|
89
|
+
(defun http-cookies-ns-to-rfc (line)
|
90
|
+
"Make the header value LINE a bit more RFC compatible.
|
91
|
+
Make old netscape cookies a bit more RFC 2109 compatible by quoting
|
92
|
+
the \"expires\" value. We need this to be able to properly split
|
93
|
+
the header value if there is more than one cookie."
|
94
|
+
(let ((start 0))
|
95
|
+
(while (string-match "expires[ \t]*=[ \t]*\\([^\";]+?\\)\\(;\\|$\\)"
|
96
|
+
line start)
|
97
|
+
(setq start (match-end 0))
|
98
|
+
(setq line (replace-match "\"\\1\"" t nil line 1)))
|
99
|
+
line))
|
100
|
+
|
101
|
+
(defun http-cookies-find-char-in-string (char string &optional start)
|
102
|
+
"Return the first position of CHAR in STRING.
|
103
|
+
If START is non-nil start at position START."
|
104
|
+
(unless start
|
105
|
+
(setq start 0))
|
106
|
+
(let ((i start) (len (length string)) pos)
|
107
|
+
(while (and (not pos) (< i len))
|
108
|
+
(when (= (aref string i) char)
|
109
|
+
(setq pos i))
|
110
|
+
(setq i (1+ i)))
|
111
|
+
pos))
|
112
|
+
|
113
|
+
(defun http-cookies-find-quoted-strings (header-value)
|
114
|
+
"Return list of positions of quoted strings in HEADER_VALUE.
|
115
|
+
Return a list of pairs with the beginning and end of quoted strings
|
116
|
+
in a \"Set-cookie: \" header value."
|
117
|
+
(let ((start 0) qstring-pos)
|
118
|
+
(while (string-match "=[ \t]*\\(\".*?[^\\]\"\\)" header-value start)
|
119
|
+
(add-to-list 'qstring-pos (cons (match-beginning 1) (1- (match-end 1))))
|
120
|
+
(setq start (match-end 1)))
|
121
|
+
qstring-pos))
|
122
|
+
|
123
|
+
(defun http-cookies-split-string (header-value sep-char)
|
124
|
+
"Split the HEADER-VALUE at the character SEP-CHAR.
|
125
|
+
Ignores SEP-CHAR if it is in a quoted string. Return a list of the
|
126
|
+
substrings."
|
127
|
+
(let ((qstrings (http-cookies-find-quoted-strings header-value))
|
128
|
+
(start 0) (beg 0) pos in-qstring strings)
|
129
|
+
(while (setq pos (http-cookies-find-char-in-string
|
130
|
+
sep-char header-value start))
|
131
|
+
(unless (= pos start) ; ignore empty strings
|
132
|
+
;; check if pos is in a quoted string
|
133
|
+
(dolist (qstring-pos qstrings)
|
134
|
+
(unless in-qstring
|
135
|
+
(when (and (> pos (car qstring-pos)) (< pos (cdr qstring-pos)))
|
136
|
+
(setq in-qstring t))))
|
137
|
+
(if in-qstring
|
138
|
+
(setq in-qstring nil)
|
139
|
+
(add-to-list 'strings (substring header-value beg pos))
|
140
|
+
(setq beg (1+ pos))))
|
141
|
+
(setq start (1+ pos)))
|
142
|
+
;; add the last token
|
143
|
+
(add-to-list 'strings (substring header-value beg))
|
144
|
+
strings))
|
145
|
+
|
146
|
+
(defun http-cookies-parse-cookie (string)
|
147
|
+
"Parse one cookie.
|
148
|
+
Return an alist ((NAME . VALUE) (attr1 . value1) (attr2 . value2) ...)
|
149
|
+
or nil on error."
|
150
|
+
(let (attrs error)
|
151
|
+
(dolist (attr (http-cookies-split-string string ?\;))
|
152
|
+
(if (string-match http-token-value-regexp attr)
|
153
|
+
(add-to-list 'attrs (cons (match-string 1 attr)
|
154
|
+
(match-string 2 attr)))
|
155
|
+
;; match the secure attribute
|
156
|
+
(if (string-match "[ \t]*\\([a-zA-Z]+\\)[ \t]*" attr)
|
157
|
+
(add-to-list 'attrs (cons (match-string 1 attr) t))
|
158
|
+
(setq error t)
|
159
|
+
(message "Cannot parse cookie %s" string))))
|
160
|
+
(unless error
|
161
|
+
attrs)))
|
162
|
+
|
163
|
+
(defun http-cookies-set (url headers)
|
164
|
+
"Set the cookies from the response to a request of URL.
|
165
|
+
Set HEADERS to the headers of the response."
|
166
|
+
(let ((host (http-cookies-url-host url)) (path (http-cookies-url-path url))
|
167
|
+
header-value cookie)
|
168
|
+
;; The server may send several "Set-Cookie:" headers.
|
169
|
+
(dolist (line headers)
|
170
|
+
(when (equal (car line) "set-cookie")
|
171
|
+
(setq header-value (http-cookies-ns-to-rfc (cdr line)))
|
172
|
+
;; there may be several cookies separated by ","
|
173
|
+
(dolist (raw-cookie (http-cookies-split-string header-value ?\,))
|
174
|
+
(setq cookie (http-cookies-parse-cookie raw-cookie))
|
175
|
+
;; (message "%s" raw-cookie)
|
176
|
+
(when (http-cookies-accept)
|
177
|
+
;; (message "accepted")
|
178
|
+
(http-cookies-store host cookie)))))))
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
;; storing cookies
|
183
|
+
|
184
|
+
(defun http-cookies-name (cookie)
|
185
|
+
"Return the name of the COOKIE."
|
186
|
+
(car (car cookie)))
|
187
|
+
|
188
|
+
(defun http-cookies-path (cookie)
|
189
|
+
"Return the value of the path attribute of the COOKIE."
|
190
|
+
(let ((attr (or (assoc "path" cookie) (assoc "Path" cookie))))
|
191
|
+
(when attr
|
192
|
+
(cdr attr))))
|
193
|
+
|
194
|
+
(defun http-cookies-domain (cookie)
|
195
|
+
"Return the value of the domain attribute of the COOKIE."
|
196
|
+
(let ((attr (or (assoc "domain" cookie) (assoc "Domain" cookie))))
|
197
|
+
(when attr
|
198
|
+
(cdr attr))))
|
199
|
+
|
200
|
+
(defun http-cookies-expires (cookie)
|
201
|
+
"Return the value of the expires attribute of the COOKIE."
|
202
|
+
(let ((attr (assoc "expires" cookie)))
|
203
|
+
(when attr
|
204
|
+
(cdr attr))))
|
205
|
+
|
206
|
+
(defun http-cookies-max-age (cookie)
|
207
|
+
"Return the value of the Max-Age attribute of the COOKIE."
|
208
|
+
(let ((attr (assoc "Max-Age" cookie)))
|
209
|
+
(when attr
|
210
|
+
(cdr attr))))
|
211
|
+
|
212
|
+
(defun http-cookies-version (cookie)
|
213
|
+
"Return the value of the version attribute of the COOKIE."
|
214
|
+
(let ((version (assoc "Version" cookie)))
|
215
|
+
(when version
|
216
|
+
(if (equal version "1")
|
217
|
+
t
|
218
|
+
(message "Cookie version %s not supported." version)
|
219
|
+
nil))))
|
220
|
+
|
221
|
+
(defun http-cookies-equal (c1 c2)
|
222
|
+
"Return non nil if the given cookies are equal.
|
223
|
+
Old netscape cookies are equal if the name and path attributes are equal.
|
224
|
+
Version 1 cookies are equal if name path and domain are equal."
|
225
|
+
(if (and (http-cookies-version c1) (http-cookies-version c2))
|
226
|
+
;; version 1 cookies
|
227
|
+
(and (equal (http-cookies-name c1) (http-cookies-name c2))
|
228
|
+
(equal (http-cookies-path c1) (http-cookies-path c2))
|
229
|
+
(equal (http-cookies-domain c1) (http-cookies-domain c2)))
|
230
|
+
;; netscape cookies
|
231
|
+
(and (equal (http-cookies-name c1) (http-cookies-name c2))
|
232
|
+
(equal (http-cookies-path c1) (http-cookies-path c2)))))
|
233
|
+
|
234
|
+
(defun http-cookies-expired (expire-string)
|
235
|
+
"Return non nil if EXPIRE-STRING is in the past."
|
236
|
+
(> (time-to-seconds (time-since expire-string)) 0.0))
|
237
|
+
|
238
|
+
(defun http-cookies-remove (cookie key table)
|
239
|
+
"Remove cookies \"equal\" to COOKIE from the list stored with KEY in TABLE."
|
240
|
+
(let ((cookie-list (gethash key table)) new-list)
|
241
|
+
(dolist (entry cookie-list)
|
242
|
+
(unless (http-cookies-equal entry cookie)
|
243
|
+
(add-to-list 'new-list entry)))
|
244
|
+
(when cookie-list
|
245
|
+
(remhash key table)
|
246
|
+
(puthash key new-list table))))
|
247
|
+
|
248
|
+
(defun http-cookies-store (host cookie)
|
249
|
+
"Store the given COOKIE from HOST in the hash tables.
|
250
|
+
Remove cookie from the tables if the given COOKIE expires in the past or
|
251
|
+
has an \"Max-Age\" of 0."
|
252
|
+
(let ((domain (http-cookies-domain cookie))
|
253
|
+
(max-age (http-cookies-max-age cookie))
|
254
|
+
(expires (http-cookies-expires cookie))
|
255
|
+
(cookie-list))
|
256
|
+
;; remove an possible "equal" old cookie
|
257
|
+
(http-cookies-remove cookie host http-cookies-host-hash)
|
258
|
+
(when domain
|
259
|
+
(http-cookies-remove cookie domain http-cookies-domain-hash))
|
260
|
+
;; check if expires is in the past or Max-Age is zero
|
261
|
+
(unless (or (and max-age (= (string-to-number max-age) 0))
|
262
|
+
(and expires (http-cookies-expired expires)))
|
263
|
+
;; convert "Max-Age" to "expire"
|
264
|
+
(when max-age
|
265
|
+
;; this value does not have to be in the "right" format
|
266
|
+
;; it's enough if `parse-time-string' can parse it
|
267
|
+
(setq expires (format-time-string
|
268
|
+
"%Y-%m-%d %T %z"
|
269
|
+
(time-add (current-time) (seconds-to-time max-age))
|
270
|
+
t))
|
271
|
+
(setcdr (assoc "Max-Age" cookie) expires)
|
272
|
+
(setcar (assoc "Max-Age" cookie) "expires"))
|
273
|
+
(setq cookie-list (gethash host http-cookies-host-hash))
|
274
|
+
(add-to-list 'cookie-list cookie)
|
275
|
+
(puthash host cookie-list http-cookies-host-hash)
|
276
|
+
(when domain
|
277
|
+
(setq cookie-list (gethash domain http-cookies-domain-hash))
|
278
|
+
(add-to-list 'cookie-list cookie)
|
279
|
+
(puthash domain cookie-list http-cookies-domain-hash)))))
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
;; building the header to send back the cookie
|
284
|
+
|
285
|
+
(defun http-cookies-cookie-to-string (cookie)
|
286
|
+
"Return the cookie as a string to be used as a header value."
|
287
|
+
(let* ((name (http-cookies-name cookie))
|
288
|
+
(value (cdr (assoc name cookie)))
|
289
|
+
(path (http-cookies-path cookie))
|
290
|
+
(domain (http-cookies-domain cookie))
|
291
|
+
(string))
|
292
|
+
(if (http-cookies-version cookie)
|
293
|
+
;; version 1 cookie
|
294
|
+
(progn
|
295
|
+
(setq string (concat "$Version = \"1\"; " name " = \"" value "\""))
|
296
|
+
(when path
|
297
|
+
(setq string (concat string "; $Path = \"" path "\"")))
|
298
|
+
(when domain
|
299
|
+
(setq string (concat string "; $Domain = \"" domain "\""))))
|
300
|
+
;; netscape cookies
|
301
|
+
(setq string (concat name "=" value)))))
|
302
|
+
|
303
|
+
(defun http-cookies-cookie-in-list (cookie list)
|
304
|
+
"Return non-nil if a cookie \"equal\" to the given COOKIE is in LIST."
|
305
|
+
(let ((in-list))
|
306
|
+
(dolist (element list)
|
307
|
+
(unless in-list
|
308
|
+
(setq in-list (http-cookies-equal cookie element))))
|
309
|
+
in-list))
|
310
|
+
|
311
|
+
(defun http-cookies-path-depth (cookie)
|
312
|
+
"Return the number of dashes in the path attribute of the cookie."
|
313
|
+
(let ((patch http-cookies-path cookie) (n 0) (start 0))
|
314
|
+
(while (setq start (http-cookies-find-char-in-string ?\/ path start))
|
315
|
+
(setq n (1+ n)))
|
316
|
+
n))
|
317
|
+
|
318
|
+
(defun http-cookie-path-depth-less (c1 c2)
|
319
|
+
"Return non nil if the path depth of cookie C1 is less than C2."
|
320
|
+
(< (http-cookies-path-depth c1) (http-cookies-path-depth c2)))
|
321
|
+
|
322
|
+
(defun http-cookies-build-header (url)
|
323
|
+
"Return a pair (\"Cookie\" . <header value>).
|
324
|
+
Use this to send back cookies to the given URL."
|
325
|
+
(let ((host (http-cookies-url-host url)) (domain) (cookie-list) (string))
|
326
|
+
(when (string-match "^[^.]+\\(\\..+\\)" host)
|
327
|
+
(setq domain (match-string 1 host))
|
328
|
+
(dolist (cookie (gethash host http-cookies-host-hash))
|
329
|
+
(unless (http-cookies-expired (http-cookies-expires cookie))
|
330
|
+
(add-to-list 'cookie-list cookie)))
|
331
|
+
(dolist (cookie (gethash domain http-cookies-domain-hash))
|
332
|
+
(unless (or (http-cookies-cookie-in-list cookie cookie-list)
|
333
|
+
(http-cookies-expired (http-cookies-expires cookie)))
|
334
|
+
(add-to-list 'cookie-list cookie)))
|
335
|
+
(setq cookie-list (sort cookie-list 'http-cookies-path-depth-less))
|
336
|
+
(dolist (cookie cookie-list)
|
337
|
+
(if string
|
338
|
+
(setq string (concat string "; "
|
339
|
+
(http-cookies-cookie-to-string cookie)))
|
340
|
+
(setq string (http-cookies-cookie-to-string cookie)))))
|
341
|
+
(cons "Cookie" string)))
|
342
|
+
|
343
|
+
|
344
|
+
|
345
|
+
;; extract parts of the url
|
346
|
+
|
347
|
+
(defun http-cookies-url-host (url)
|
348
|
+
"Return the hostname of URL"
|
349
|
+
(unless (string-match
|
350
|
+
"http://\\([^/:]+\\)\\(:\\([0-9]+\\)\\)?/\\(.*/\\)?\\([^:]*\\)"
|
351
|
+
url)
|
352
|
+
(error "Cannot parse URL %s." url))
|
353
|
+
(match-string 1 url))
|
354
|
+
|
355
|
+
(defun http-cookies-url-path (url)
|
356
|
+
"Return the path of the URL."
|
357
|
+
(unless (string-match
|
358
|
+
"http://\\([^/:]+\\)\\(:\\([0-9]+\\)\\)?/\\(.*/\\)?\\([^:]*\\)"
|
359
|
+
url)
|
360
|
+
(error "Cannot parse URL %s." url))
|
361
|
+
(concat "/" (or (match-string 4 url) "")))
|
362
|
+
|
363
|
+
|
364
|
+
|
365
|
+
;; functions to check the cookie (implementation of 4.3.2 of RFC 2109)
|
366
|
+
|
367
|
+
(defun http-cookies-accept ()
|
368
|
+
"Return non nil if the cookie should be accepted.
|
369
|
+
The tests are based on the functions in `http-cookies-accept-functions'."
|
370
|
+
(let ((accept t))
|
371
|
+
(dolist (fun http-cookies-accept-functions)
|
372
|
+
(when accept
|
373
|
+
(setq accept (funcall fun))))
|
374
|
+
accept))
|
375
|
+
|
376
|
+
(defun http-cookie-check-path ()
|
377
|
+
"Return nil if the \"path\" attribute is not a prefix of th URL."
|
378
|
+
(let ((cookie-path (cdr (assoc "path" cookie))))
|
379
|
+
(if cookie-path
|
380
|
+
(if (string-match (concat "^" cookie-path) path)
|
381
|
+
t
|
382
|
+
(message "Rejecting cookie: path attribute \"%s\" is not a prefix\
|
383
|
+
of the URL %s." cookie-path url)
|
384
|
+
nil)
|
385
|
+
t)))
|
386
|
+
|
387
|
+
(defun http-cookie-check-domain ()
|
388
|
+
"Return nil if the domain is bogus.
|
389
|
+
Return nil if the domain does not start with a \".\" or does not contain
|
390
|
+
an embedded dot."
|
391
|
+
(let ((domain (cdr (assoc "domain" cookie))))
|
392
|
+
(if domain
|
393
|
+
(if (string-match "^\\.[^.]+\\.[^.]+" domain)
|
394
|
+
t
|
395
|
+
(message "Rejection cookie: domain \"%s\" does not start with a dot\
|
396
|
+
or does not contain an embedded dot." domain)
|
397
|
+
nil)
|
398
|
+
t)))
|
399
|
+
|
400
|
+
(defun http-cookie-check-hostname ()
|
401
|
+
"Return nil if the domain doesn't match the host.
|
402
|
+
Return nil if the domain attribute does not match the host name or the
|
403
|
+
host name without the domain attribute still contains one or more dots."
|
404
|
+
;; FIXME: hostname might be an IP address
|
405
|
+
(let ((domain (cdr (assoc "domain" cookie))))
|
406
|
+
(if (not domain)
|
407
|
+
t
|
408
|
+
(when (string-match (concat domain "$") host)
|
409
|
+
(not (http-cookies-find-char-in-string
|
410
|
+
?\. (substring host 0 (match-beginning 0))))))))
|
411
|
+
|
412
|
+
|
413
|
+
|
414
|
+
(provide 'http-cookies)
|
415
|
+
|
416
|
+
;;; http-cookies.el ends here
|