sekka 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|