~ubuntu-branches/ubuntu/natty/elserv/natty

1 by OHASHI Akira
Import upstream version 0.4.0+0.20011203cvs
1
;;; elserv.el -- Yet another HTTP server on Emacsen
2
3
;; Copyright (C) 2001 Yuuichi Teranishi <teranisi@gohome.org>
4
5
;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6
;; Keywords: HTTP
7
8
;; This program is free software; you can redistribute it and/or modify
9
;; it under the terms of the GNU General Public License as published by
10
;; the Free Software Foundation; either version 2, or (at your option)
11
;; any later version.
12
;;
13
;; This program is distributed in the hope that it will be useful,
14
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
;; GNU General Public License for more details.
17
;;
18
;; You should have received a copy of the GNU General Public License
19
;; along with GNU Emacs; see the file COPYING.  If not, write to the
20
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21
;; Boston, MA 02111-1307, USA.
22
;;
23
24
;;; Commentary:
25
;;
26
27
;; API for server handling
28
29
;; elserv-start
30
;; elserv-find-process
31
;; elserv-stop
32
;; elserv-publish
33
;; elserv-unpublish
34
35
;; API for content making
36
37
;; elserv-make-result
38
;; elserv-make-redirect
39
40
;; Example:
41
;;
42
;; (require 'elserv)
43
;; (elserv-start 8080)
44
;; (elserv-publish (elserv-find-process 8080) "/"
45
;;                 :string "Hello World."
46
;;                 :content-type "text/plain")
47
;;
48
;; or write following lines in your .emacs.
49
;;
50
;; (autoload elserv-start "elserv" nil t)
51
;; (add-hook 'elserv-start-hook
52
;;           '(lambda ()
53
;;              (elserv-publish (elserv-find-process) "/"
54
;;                              :string "Hello World."
55
;;                              :content-type "text/plain")))
56
57
;;; History:
58
;;
59
;; Part of the codes are originally in an HTTP server embedded in Emacs
60
;; available from <URL:http://www.chez.com/emarsden/downloads/>.
61
62
;;; Code:
63
64
(require 'product)
65
(require 'pces)
66
(require 'poem)
67
(require 'std11)
68
69
(eval-when-compile
70
  (require 'cl)
71
  (require 'static))
72
73
(eval-and-compile
74
  (autoload 'elserv-autoindex "elserv-autoindex")
75
  (autoload 'elserv-xmlrpc-register "elserv-xmlrpc")
76
  (autoload 'elserv-negotiation "elserv-negotiation")
77
  (autoload 'elserv-negotiation-make-result "elserv-negotiation"))
78
79
(product-provide 'elserv
80
  (product-define "Elserv" nil
81
		  '(0 4 0)
82
		  "Never Surrender"))
83
84
(defgroup elserv nil
85
  "Elserv -- Yet another HTTP server on Emacsen."
86
  :group 'hypermedia)
87
88
(defcustom elserv-default-server-name (system-name)
89
  "*Default server name for Elserv."
90
  :type 'string
91
  :group 'elserv)
92
93
(defcustom elserv-default-port 8000
94
  "*Default port number for Elserv."
95
  :type 'integer
96
  :group 'elserv)
97
98
(defcustom elserv-program-name nil
99
  "*If non-nil, it is invoked as a command.
100
`elserv-daemon-name' is passed as first argument."
101
  :type '(choice (symbol :tag "Direct" nil)
102
		 (string :tag "Program Name"))
103
  :group 'elserv)
104
105
(defcustom elserv-daemon-name (if (fboundp 'locate-data-directory)
106
				  (expand-file-name
107
				   "elservd"
108
				   (locate-data-directory "elserv"))
109
				"elservd")
110
  "*Program name for Elserv daemon process."
111
  :type 'string
112
  :group 'elserv)
113
114
(defcustom elserv-publish-hash-length 31
115
  "*Length of publish hash."
116
  :type 'integer
117
  :group 'elserv)
118
119
(defcustom elserv-debug nil
120
  "*If non-nil, request string is inserted to the debug buffer."
121
  :type 'boolean
122
  :group 'elserv)
123
124
(defcustom elserv-directory-index-file "index.html"
125
  "*Index file name for the directory."
126
  :type 'string
127
  :group 'elserv)
128
129
(defcustom elserv-directory-autoindex t
130
  "*If non-nil and directory has no index file, generate html index in the
131
directory."
132
  :type 'boolean
133
  :group 'elserv)
134
135
(defcustom elserv-search-default-make-index t
136
  "*If non-nil, search index is created in `elserv-publish'."
137
  :type 'boolean
138
  :group 'elserv)
139
140
(defcustom elserv-use-negotiation t
141
  "*If non-nil, use content negotiation."
142
  :type 'boolean
143
  :group 'eliserv)
144
145
(defcustom elserv-keep-alive t
146
  "*Non-nil enable persistent connections.
147
\(more than one request per connection\)."
148
  :type 'boolean
149
  :group 'elserv)
150
151
(defcustom elserv-max-keep-alive-requests 100
152
  "*The maximum number of requests to allow during a persistent connection.
153
Set to nil to allow an unlimited amount.
154
We recommend you leave this number high, for maximum performance."
155
  :type 'integer
156
  :group 'elserv)
157
158
(defcustom elserv-keep-alive-timeout 15
159
  "*Number of seconds to wait for the next request on the same connection."
160
  :type 'integer
161
  :group 'elserv)
162
163
(defcustom elserv-identity-check nil
164
  "*Non-nil enables RFC1413-compliant logging.
165
\(logging of the remote user name for each connection\)"
166
  :type 'boolean
167
  :group 'elserv)
168
169
(defcustom elserv-max-clients 20
170
  "*Non-nil limits the number of clients who can simultaneously connect.
171
If this limit is ever reached, clients will be LOCKED OUT."
172
  :type 'integer
173
  :group 'elserv)
174
175
(defcustom elserv-access-log-file nil
176
  "*If file name is specified, access log is saved to the file."
177
  :type 'file
178
  :group 'elserv)
179
180
(defcustom elserv-access-log-max-size 50000
181
  "*Max size of access log file."
182
  :type 'integer
183
  :group 'elserv)
184
185
(defcustom elserv-icon-path (if (fboundp 'locate-data-directory)
186
				(locate-data-directory "elserv")
187
			      (let ((icons (expand-file-name "elserv/icons/"
188
							     data-directory)))
189
				(if (file-directory-p icons)
190
				    icons)))
191
  "*Icon directory path."
192
  :type 'directory
193
  :group 'elserv)
194
195
(defcustom elserv-icon-publish-path "/icons"
196
  "*Path to publish an icon directory specified by `elserv-icon-path'."
197
  :type 'string
198
  :group 'elserv)
199
200
(defcustom elserv-server-admin-full-name (user-full-name)
201
  "*Full name of the server admin."
202
  :type 'string
203
  :group 'elserv)
204
205
(defcustom elserv-server-admin-mail-address user-mail-address
206
  "*E-mail address of the server admin."
207
  :type 'string
208
  :group 'elserv)
209
210
(defconst elserv-url-unreserved-chars
211
  '(?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m
212
       ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z
213
       ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M
214
       ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z
215
       ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
216
       ?$ ?- ?_ ?. ?! ?~ ?* ?' ?( ?) ?,))
217
218
(defconst elserv-http-version "HTTP/1.1")
219
220
(defconst elserv-server-eol "\r\n"
221
  "The end-of-line string sent from the server.")
222
223
(defconst elserv-client-eor "\r\n\r\n"
224
  "The end-of-request string sent from the elservd.")
225
226
(defvar elserv-buffer-publish-hash nil)
227
(make-variable-buffer-local 'elserv-buffer-publish-hash)
228
(defvar elserv-buffer-request-handler nil)
229
(make-variable-buffer-local 'elserv-buffer-request-handler)
230
(defvar elserv-buffer-port nil)
231
(make-variable-buffer-local 'elserv-buffer-port)
232
(defvar elserv-buffer-client-process nil)
233
(make-variable-buffer-local 'elserv-buffer-client-process)
234
(defvar elserv-buffer-client-port nil)
235
(make-variable-buffer-local 'elserv-buffer-client-port)
236
(defvar elserv-buffer-search-index-buffer nil)
237
(make-variable-buffer-local 'elserv-buffer-search-index-buffer)
238
239
(defvar elserv-mime-types-alist
240
  '(("html" . "text/html")
241
    ("txt"  . "text/plain")
242
    ("jpg"  . "image/jpeg")
243
    ("jpeg" . "image/jpeg")
244
    ("gif"  . "image/gif")
245
    ("png"  . "image/png")
246
    ("tif"  . "image/tiff")
247
    ("tiff" . "image/tiff")
248
    ("css"  . "text/css")
249
    ("gz"   . "application/octet-stream")
250
    ("ps"   . "application/postscript")
251
    ("pdf"  . "application/pdf")
252
    ("eps"  . "application/postscript")
253
    ("tar"  . "application/x-tar")
254
    ("rpm"  . "application/x-rpm")
255
    ("zip"  . "application/zip")
256
    ("mp3"  . "audio/mpeg")
257
    ("mp2"  . "audio/mpeg")
258
    ("mid"  . "audio/midi")
259
    ("midi" . "audio/midi")
260
    ("wav"  . "audio/x-wav")
261
    ("au"   . "audio/basic")
262
    ("ram"  . "audio/pn-realaudio")
263
    ("ra"   . "audio/x-realaudio")
264
    ("mpg"  . "video/mpeg")
265
    ("mpeg" . "video/mpeg")
266
    ("qt"   . "video/quicktime")
267
    ("mov"  . "video/quicktime")
268
    ("avi"  . "video/x-msvideo"))
269
  "Alist of (SUFFIX .CONTENT-TYPE).")
270
271
(defsubst elserv-bytes (string)
272
  "Return the byte length of the STRING."
273
  (length (string-as-unibyte string)))
274
275
(defun elserv-mime-type (filename)
276
  "Return content-type for FILENAME."
277
  (or (cdr (assoc (file-name-extension filename) elserv-mime-types-alist))
278
      "text/plain"))
279
280
(put 'elserv-exception 'error-conditions
281
     '(elserv-exception error))
282
283
(defmacro elserv-define-status-code (name code msg)
284
  "Define status code with NAME, CODE, and MSG."
285
  `(progn
286
     (put ',name 'error-conditions '(,name elserv-exception error))
287
     (put ',name 'elserv-code ,code)
288
     (put ',name 'elserv-msg ,msg)))
289
290
(elserv-define-status-code elserv-ok                 200 "OK")
291
(elserv-define-status-code elserv-moved-permanently  301 "Moved permanently")
292
(elserv-define-status-code elserv-found              302 "Found")
293
(elserv-define-status-code elserv-see-other          303 "See Other")
294
(elserv-define-status-code elserv-not-modified       304 "Not Modified")
295
(elserv-define-status-code elserv-bad-request        400 "Bad request")
296
(elserv-define-status-code elserv-unauthorized       401 "Unauthorized")
297
(elserv-define-status-code elserv-forbidden          403 "Forbidden")
298
(elserv-define-status-code elserv-file-not-found     404 "Not found")
299
(elserv-define-status-code elserv-method-not-allowed 405 "Method not allowed")
300
(elserv-define-status-code elserv-internal-error   500 "Internal server error")
301
(elserv-define-status-code elserv-unimplemented    501 "Not implemented")
302
(elserv-define-status-code elserv-unavailable      503 "Service unavailable")
303
304
;;; Result
305
(defmacro elserv-make-result (&optional code header body
306
					user content-length)
307
  "Make a result structure.
308
CODE is the status code.
309
HEADER is the plist for header structure.
310
BODY is the body string.
311
USER is the user who is authenticated.
312
CONTENT-LENGTH is the length of the content."
313
  `(vector ,code ,header ,body ,user ,content-length))
314
315
(defmacro elserv-result-code (result)
316
  "Return code of RESULT."
317
  `(aref ,result 0))
318
319
(defmacro elserv-set-result-code (result code)
320
  "Set code of RESULT as CODE."
321
  `(aset ,result 0 ,code))
322
323
(defmacro elserv-result-header (result)
324
  "Return header of RESULT."
325
  `(aref ,result 1))
326
327
(defmacro elserv-set-result-header (result header)
328
  "Set header of RESULT as HEADER."
329
  `(aset ,result 1 ,header))
330
331
(defmacro elserv-result-body (result)
332
  "Return body of RESULT."
333
  `(aref ,result 2))
334
335
(defmacro elserv-set-result-body (result body)
336
  "Set body of RESULT as BODY."
337
  `(aset ,result 2 ,body))
338
339
(defmacro elserv-result-user (result)
340
  "Return user of RESULT."
341
  `(aref ,result 3))
342
343
(defmacro elserv-set-result-user (result user)
344
  "Set user of RESULT as USER."
345
  `(aset ,result 3 ,user))
346
347
(defmacro elserv-result-content-length (result)
348
  "Return content-length of RESULT."
349
  `(aref ,result 4))
350
351
(defmacro elserv-set-result-content-length (result content-length)
352
  "Set content-length of RESULT as CONTENT-LENGTH."
353
  `(aset ,result 4 ,content-length))
354
355
;;; Error
356
(defun elserv-error (why &optional msg)
357
  "Make a error response from WHY.
358
If optional MSG is specified, it is used as response body."
359
  (elserv-make-result
360
   (car why)
361
   '(content-type "text/html")
362
   (concat "<html><head><title>Error</title></head>\n"
363
	   "<body><h1>"
364
	   (get (car why) 'elserv-msg)
365
	   "</h1>\n<p>"
366
	   (or msg (cdr why))
367
	   "\n</body></html>\n")))
368
369
(put 'with-elserv-error-handler 'edebug-form-spec '(body))
370
(defmacro with-elserv-error-handler (&rest forms)
371
  "Evaluate FORMS like progn with elserv error handler."
372
  `(condition-case why
373
       (progn ,@forms)
374
     (elserv-exception (elserv-error why))
375
     (error (elserv-error (cons 'elserv-internal-error nil)
376
			  (format "Emacs Lisp error: %s\n" why)))))
377
378
(defun elserv-host-member (host list)
379
  "Return t if HOST is matched to any of the regexp in the LIST."
380
  (let ((case-fold-search t)
381
	match)
382
    (while list
383
      (if (or (string-match (car list) (nth 0 host))
384
	      (string-match (car list) (nth 1 host)))
385
	  (setq match t
386
		list nil)
387
	(setq list (cdr list))))
388
    match))
389
390
(defun elserv-make-predicate-from-plist (plist)
391
  "Make a check predicate from PLIST."
392
  (let (second pred)
393
    (while plist
394
      (when (eq (car plist) :allow)
395
	(setq pred
396
	      (list 'and (list 'elserv-host-member 'host
397
			       (append (list 'list) (cadr plist)))
398
		    (if (setq second (cadr (memq :deny (cdr plist))))
399
			(list 'not (list 'elserv-host-member 'host
400
					 (append (list 'list) second)))
401
		      t)))
402
	(setq plist nil))
403
      (when (eq (car plist) :deny)
404
	(setq pred
405
	      (list 'or (list 'not (list 'elserv-host-member
406
					 'host (append (list 'list
407
							     (cadr plist)))))
408
		    (if (setq second (cadr (memq :deny (cdr plist))))
409
			(list 'elserv-host-member 'host
410
			      (append (list 'list second))))))
411
	(setq plist nil))
412
      (setq plist (cdr plist)))
413
    (or pred t)))
414
415
(defun elserv-make-unauthorized-basic (request realm)
416
  "Make unauthorized RESULT for REQUEST.
417
Basic authorization response with REALM is created."
418
  (let ((result (elserv-make-result)))
419
    (elserv-set-result-code result 'elserv-unauthorized)
420
    (elserv-set-result-header result
421
			      `(www-authenticate
422
				,(concat "Basic realm=\"" realm "\"")
423
				content-type "text/html"))
424
    (elserv-set-result-body
425
     result
426
     (concat
427
      "<html><head><title>Authorization required</title></head>
428
<body><h1>Authorization Required</h1>This server could not verify that you are authorized to access the document requested. Either you supplied the wrong
429
credentials (e.g., bad password), or your browser doesn't understand how to supply the credentials required.
430
<hr>"
431
      (elserv-version) "</body></html>"))
432
    result))
433
434
(defun elserv-make-redirect (result where)
435
  "Make RESULT as a redirect to new location WHERE."
436
  (elserv-set-result-code result 'elserv-moved-permanently)
437
  (elserv-set-result-header result
438
			    (list 'location where
439
				  'content-type "text/html"
440
				  'uri where))
441
  (elserv-set-result-body result
442
			  "<html><head><title>Moved permanently</title></head>
443
<body><h1>Moved permanently</h1>This Page is moved permanently.</body>")
444
  result)
445
446
(defun elserv-version (&optional arg)
447
  "Return Elserv version.
448
If it is called interactively, version string is appeared on minibuffer.
449
If ARG is specified, don't display code name."
450
  (interactive "P")
451
  (let ((product-info (product-string-1 'elserv (not arg))))
452
    (if (interactive-p)
453
	(message "%s" product-info)
454
      product-info)))
455
456
;;; URL decode: original codes are cgi.el
457
(defun elserv-url-hex-char-p (ch)
458
  "Return non-nil if CH is hex char."
459
  (declare (character ch))
460
  (let ((hexchars '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
461
                    ?A ?B ?C ?D ?E ?F)))
462
    (member (upcase ch) hexchars)))
463
464
(defun elserv-url-decode-string (str)
465
  "Decode STR as URL string.
466
It replaces %xx to the corresponding character and + to ' '."
467
  (do ((i 0)
468
       (len (length str))
469
       (decoded '()))
470
      ((>= i len) (concat (nreverse decoded)))
471
    (let ((ch (aref str i)))
472
      (cond ((eq ?+ ch)
473
             (push ?\ decoded)
474
             (incf i))
475
            ((and (eq ?% ch)
476
                  (< (+ i 2) len)
477
                  (elserv-url-hex-char-p (aref str (+ i 1)))
478
                  (elserv-url-hex-char-p (aref str (+ i 2))))
479
             (let ((hex (string-to-number (substring str (+ i 1) (+ i 3)) 16)))
480
               (push (int-char hex) decoded)
481
               (incf i 3)))
482
            (t (push ch decoded)
483
               (incf i))))))
484
485
(defsubst elserv-position (char str)
486
  "Find the first occurrence of CHAR in STR."
487
  (let ((end (length str))
488
	(i 0)
489
	pos)
490
    (while (< i end)
491
      (if (eq (aref str i) char)
492
	  (setq pos i
493
		i end))
494
      (incf i))
495
    pos))
496
497
(defun elserv-url-decode (q)
498
  "Parse string Q as URL query.
499
\"foo=x&bar=y+re\" into ((\"foo\" .  \"x\") (\"bar\" \.  \"y re\"))
500
Substrings are plus-decoded and then URL-decoded."
501
  (when q
502
    (flet ((split-= (str)
503
            (let ((pos (or (elserv-position ?= str) 0)))
504
              (cons (elserv-url-decode-string (substring str 0 pos))
505
                    (elserv-url-decode-string (substring str (+ pos 1)))))))
506
      (mapcar #'split-= (split-string q "&")))))
507
508
509
;;; Object loading and saving.
510
(defun elserv-load (filename &optional coding)
511
  "Load OBJECT from the file specified by FILENAME.
512
File content is decoded with CODING."
513
  (if (not (file-readable-p filename))
514
      nil
515
    (with-temp-buffer
516
      (insert-file-contents-as-binary filename)
517
      (when coding
518
	(set-buffer-multibyte t)
519
	(decode-coding-region (point-min) (point-max) coding))
520
      (ignore-errors (read (current-buffer))))))
521
522
(defun elserv-make-directory (path)
523
  "Create directory on PATH recursively."
524
  (let ((parent (directory-file-name (file-name-directory path))))
525
    (if (null (file-directory-p parent))
526
	(elserv-make-directory parent))
527
    (make-directory path)))
528
529
(defsubst elserv-save-buffer (filename &optional coding)
530
  "Save current buffer to the file specified by FILENAME.
531
Directory of the file is created if it doesn't exist.
532
File content is encoded with CODING."
533
  (let ((dir (directory-file-name (file-name-directory filename))))
534
    (if (file-directory-p dir)
535
	() ; ok.
536
      (unless (file-exists-p dir) (elserv-make-directory dir)))
537
    (when coding
538
      (encode-coding-region (point-min) (point-max) coding))
539
    (write-region-as-binary (point-min) (point-max)
540
			    filename nil 'no-msg)))
541
542
(defun elserv-save (filename object &optional coding)
543
  "Save object.
544
FILENAME is the name of the saved file.
545
OBJECT is the object to be saved.
546
Directory of the file is created if it doesn't exist.
547
File content is encoded with CODING before saving."
548
  (with-temp-buffer
549
    (prin1 object (current-buffer))
550
    (elserv-save-buffer filename coding)
551
    object))
552
553
;;; Debug
554
(defvar elserv-debug-buffer nil)
555
(defun elserv-debug (string)
556
  "Insert STRING to the debug buffer."
557
  (when elserv-debug
558
    (if (or (null elserv-debug-buffer)
559
	    (not (bufferp elserv-debug-buffer))
560
	    (not (buffer-live-p elserv-debug-buffer)))
561
	(setq elserv-debug-buffer (get-buffer-create "*Debug elserv*")))
562
    (with-current-buffer elserv-debug-buffer
563
      (goto-char (point-max))
564
      (insert string))))
565
566
(defun elserv-process-filter (process string)
567
  "Process filter elserv.  PROCESS, STRING are argument for process filter."
568
  (elserv-debug string)
569
  (when (buffer-live-p (process-buffer process))
570
    (with-current-buffer (process-buffer process)
571
      (goto-char (point-max))
572
      (insert string)
573
      (goto-char (point-min))
574
      (while (re-search-forward elserv-client-eor nil t)
575
	(elserv-process-request process
576
				(elserv-parse-request
577
				 (buffer-substring (point-min) (point))))
578
	(delete-region (point-min) (point))))))
579
580
(defsubst elserv-client-start (port process)
581
  "Start client process for elservd.
582
PORT is the elservd client port.
583
PROCESS is the server process."
584
  (with-current-buffer (get-buffer-create (concat "*elserv client*"
585
						  (number-to-string
586
						   (elserv-process-port
587
						    process))))
588
    (set-buffer-multibyte nil)
589
    (open-network-stream-as-binary "_elserv"
590
				   (current-buffer)
591
				   "localhost" port)))
592
593
(defsubst elserv-process-request-internal (request client-process
594
						   process handler)
595
  "Process request.
596
REQUEST, CLIENT-PROCESS, PROCESS, HANDLER are used."
597
  (let (result header connection string)
598
    (setq result
599
	  (with-elserv-error-handler
600
	   (funcall handler process request)))
601
    (setq connection (elserv-decide-connection result request))
602
    (setq header (elserv-make-header result request connection))
603
    (setq string (concat (plist-get request 'key)
604
			 (if (string= connection "close")
605
			     ";" ":")
606
			 (number-to-string (+ (elserv-bytes header)
607
					      ;; redundant process.
608
					      (if (elserv-result-body result)
609
						  (elserv-bytes
610
						   (elserv-result-body result))
611
						0)))
612
			 "\r\n"))
613
    (process-send-string client-process string)
614
    (process-send-string client-process header)
615
    (elserv-debug string)
616
    (elserv-debug header)
617
    (when (elserv-result-body result)
618
      (process-send-string client-process (elserv-result-body result))
619
      (elserv-debug (elserv-result-body result))
620
      (elserv-debug "\r\n"))
621
    (process-send-string client-process "\r\n")
622
    (elserv-log process request result)))
623
624
(defun elserv-process-request (process request)
625
  "Process request string on the current buffer.
626
PROCESS is elserv process.
627
REQUEST is the request plist."
628
  ;; current buffer is process buffer.
629
  (let ((client-process elserv-buffer-client-process)
630
	(handler elserv-buffer-request-handler))
631
    (if elserv-buffer-client-port
632
	(progn
633
	  (unless (memq (process-status elserv-buffer-client-process)
634
			'(open run))
635
	    (delete-process elserv-buffer-client-process)
636
	    ;; restart.
637
	    (setq elserv-buffer-client-process
638
		  (elserv-client-start elserv-buffer-client-port process)
639
		  client-process elserv-buffer-client-process))
640
	  (with-current-buffer (process-buffer elserv-buffer-client-process)
641
	    (elserv-process-request-internal
642
	     request client-process process handler)))
643
      ;; Process greeting.
644
      (setq elserv-buffer-client-port
645
	    (string-to-number (plist-get request 'port))
646
	    elserv-buffer-client-process
647
	    (elserv-client-start elserv-buffer-client-port process)))))
648
649
(defsubst elserv-delete-cr-buffer ()
650
  "Delete CR from buffer."
651
  (save-excursion
652
    (goto-char (point-min))
653
    (while (search-forward "\r\n" nil t)
654
      (replace-match "\n")) ))
655
656
(defun elserv-parse-request (request)
657
  "Parse REQUEST string."
658
  (with-temp-buffer
659
    (set-buffer-multibyte nil)
660
    (insert request)
661
    (elserv-delete-cr-buffer)
662
    (goto-char (point-min))
663
    (let ((regexp (concat "\\(" std11-field-head-regexp "\\)[ \t]*"))
664
	  name body dest end)
665
      (while (re-search-forward regexp nil t)
666
	(setq name (downcase (buffer-substring
667
			      (match-beginning 1)(1- (match-end 1))))
668
	      end  (match-end 0)
669
	      name (intern (if (string-match "^elserv-" name)
670
			       (setq name (substring name (match-end 0)))
671
			     name))
672
	      body (buffer-substring end (std11-field-end)))
673
	(if (eq name 'client)
674
	    (setq body (split-string body)))
675
	(when (eq name 'content)
676
	  (setq name 'body)
677
	  (setq body (ignore-errors (base64-decode-string body))))
678
	(setq dest (nconc (list name body) dest)))
679
      dest)))
680
681
(defun elserv-decide-connection (result request)
682
  "Decide connection type by RESULT and REQUEST."
683
  (if (and elserv-keep-alive
684
	   (string-match "keep-alive"
685
			 (or (plist-get request 'connection) ""))
686
	   (eq (get (elserv-result-code result)
687
		    'elserv-code)
688
	       200))
689
      "keep-alive"
690
    "close"))
691
692
(defun elserv-make-header (result request connection)
693
  "Make an HTTP header string from RESULT, REQUEST, and CONNECTION."
694
  (concat elserv-http-version " "
695
	  (number-to-string (get (elserv-result-code result) 'elserv-code))
696
	  " "
697
	  (get (elserv-result-code result) 'elserv-msg)
698
	  "\r\nServer: " (elserv-version 'simple)
699
	  "\r\nAccept-Ranges: none"
700
	  "\r\nDate: " (let ((system-time-locale "C"))
701
			 (format-time-string "%a, %e %b %Y %T %Z"))
702
	  "\r\nConnection: " connection
703
	  (if (string= connection "keep-alive")
704
	      (concat
705
	       "\r\nKeep-Alive: timeout=" (number-to-string
706
					   elserv-keep-alive-timeout)
707
	       ", max=" (number-to-string
708
			 elserv-max-keep-alive-requests)))
709
	  "\r\n"
710
	  (let ((header (elserv-result-header result))
711
		str)
712
	    (while header
713
	      (setq str (concat str (capitalize (symbol-name (nth 0 header))) ": "
714
				(nth 1 header) "\r\n"))
715
	      (setq header (nthcdr 2 header)))
716
	    str)
717
	  "Content-Length: " (number-to-string
718
			      (+ 2 (or 
719
				    (elserv-result-content-length
720
				     result)
721
				    (if (elserv-result-body result)
722
					(elserv-bytes (elserv-result-body
723
						       result))
724
				      0))))
725
	  "\r\n"
726
	  "MIME-Version: 1.0\r\n\r\n"))
727
728
(defun elserv-process-sentinel (process string)
729
  "A sentinel for elserv process.  PROCESS, STRING are arguments for sentinel."
730
  (elserv-debug string)
731
  (delete-process process))
732
733
;;; Commands
734
735
;;;###autoload
736
(defun elserv-start (&optional port)
737
  "Start elserv server process.
738
Optional PORT is port number for the server process.
739
If PORT is not specified, `elserv-default-port' is used.
740
Return server process object."
741
  (interactive)
742
  (let (process args)
743
    (setq port (or port elserv-default-port))
744
    (setq args (list (number-to-string port)
745
		     (if elserv-identity-check "log" "nolog")
746
		     (number-to-string (or elserv-max-clients 0))
747
		     (number-to-string (or elserv-max-keep-alive-requests 0))
748
		     (number-to-string (or elserv-keep-alive-timeout 0))))
749
    (if elserv-program-name (setq args (cons elserv-daemon-name args)))
750
    (setq process (as-binary-process
751
		   (apply
752
		    'start-process
753
		    "elserv"
754
		    (get-buffer-create (concat "*elserv*"
755
					       (number-to-string port)))
756
		    (or elserv-program-name elserv-daemon-name)
757
		    args)))
758
    (with-current-buffer (process-buffer process)
759
      (set-buffer-multibyte nil)
760
      (erase-buffer)
761
      (setq elserv-buffer-search-index-buffer (elserv-search-initialize))
762
      (setq elserv-buffer-request-handler 'elserv-request-handler)
763
      (setq elserv-buffer-publish-hash
764
	    (make-vector elserv-publish-hash-length 0))
765
      (setq elserv-buffer-port port))
766
    (set-process-filter process 'elserv-process-filter)
767
    (set-process-sentinel process 'elserv-process-sentinel)
768
    (elserv-publish-default process)
769
    (get-buffer-create (concat "*Log of elserv*"
770
			       (number-to-string
771
				(elserv-process-port process))))
772
    (run-hooks 'elserv-start-hook)
773
    process))
774
775
(defun elserv-process-port (process)
776
  "Get port number of the Elserv server PROCESS."
777
  (with-current-buffer (process-buffer process)
778
    elserv-buffer-port))
779
780
(defun elserv-find-process (&optional port)
781
  "Find running Elserv server process.
782
If optional PORT is specified, find process with the specified port number.
783
Otherwise, an Elserv process last invoked is returned."
784
  (catch 'found
785
    (dolist (process (process-list))
786
      (if (string-match "^elserv" (process-name process))
787
	  (if port
788
	      (if (eq port (elserv-process-port process))
789
		  (throw 'found process))
790
	    (throw 'found process))))))
791
792
(defun elserv-stop (&optional port)
793
  "Stop running Elserv server process.
794
If optional PORT is specified, kill process with the specified port number.
795
Otherwise, an Elserv process last invoked is killed."
796
  (interactive)
797
  (let ((process (elserv-find-process port)))
798
    (if process
799
	(progn
800
	  (with-current-buffer (process-buffer process)
801
	    (if (buffer-live-p elserv-buffer-search-index-buffer)
802
		(kill-buffer elserv-buffer-search-index-buffer)))
803
	  (kill-buffer (process-buffer process))
804
	  (delete-process process)
805
	  (message "Elserv stopped."))
806
      (message "Elserv process not found."))))
807
808
;;; Access log
809
(defun elserv-log (process request result)
810
  "Record a server access log.
811
PROCESS is the Elserv server process.
812
REQUEST is the request structure.
813
RESULT is the result structure."
814
  (with-current-buffer (get-buffer-create
815
			(concat "*Log of elserv*"
816
				(number-to-string
817
				 (elserv-process-port process))))
818
    (let (point)
819
      (goto-char (point-max))
820
      (setq point (point))
821
      (insert
822
       (car (plist-get request 'client))
823
       " "
824
       (if elserv-identity-check
825
	   (or (plist-get request 'ident) "unknown")
826
	 "-")
827
       " "
828
       (or (elserv-result-user result) "-") ; remote user (auth)
829
       " "
830
       (let ((system-time-locale "C"))
831
	 (format-time-string "[%a, %d %b %Y %T %z] "))
832
       "\"" (plist-get request 'request) "\""
833
       " " (number-to-string (get (elserv-result-code result)
834
				  'elserv-code))
835
       " "
836
       (if (elserv-result-body result)
837
	   (number-to-string (elserv-bytes (elserv-result-body result)))
838
	 "0")
839
       " \"" (or (plist-get request 'referer) "-") "\" \""
840
       (or (plist-get request 'user-agent) "no agent info") "\"\n")
841
      (if elserv-access-log-file
842
	  (if (file-writable-p elserv-access-log-file)
843
	      (progn
844
		(if (> (nth 7 (file-attributes elserv-access-log-file))
845
		       elserv-access-log-max-size)
846
		    (ignore-errors
847
		      (rename-file elserv-access-log-file
848
				   (concat elserv-access-log-file
849
					   ".0") t)))
850
		(write-region point (point) elserv-access-log-file t 'no-msg))
851
	    (elserv-debug (concat elserv-access-log-file
852
				  " is not writable!!\n")))))))
853
854
;;; Process request.
855
(defun elserv-request-handler (process request)
856
  "Request handler.  PROCESS, REQUEST are arguments for request handler."
857
  (let ((req (plist-get request 'request))
858
	method func)
859
    (if (and (string-match "HTTP/1\\.1" req)
860
	     (null (plist-get request 'host)))
861
	(signal 'elserv-bad-request
862
		"HTTP 1.1 client must send a Host: field."))
863
    (if (string-match "\\`\\([^ ]+\\)\\s-\\([^ \t\r\n]*\\)" req)
864
	(progn
865
	  (setq method (match-string 1 req)
866
		func (intern (concat "elserv-handle-"
867
				     (downcase method))))
868
	  (if (fboundp func)
869
	      (funcall func process (match-string 2 req) request)
870
	    (signal 'elserv-not-implemented (concat 
871
					     method
872
					     " is not implemented"))))
873
      (signal 'elserv-bad-request req))))
874
875
(defun elserv-handle-get (process path request)
876
  "Handle GET request.
877
PROCESS is elserv process.
878
PATH is the requested path string.
879
REQUEST is the request structure."
880
  (elserv-service process path request))
881
882
(defun elserv-handle-head (process path request)
883
  "Handle HEAD request.
884
PROCESS is elserv process.
885
PATH is the requested path string.
886
REQUEST is the request structure."
887
  (let ((result (elserv-service process path request)))
888
    (elserv-set-result-content-length result (elserv-bytes
889
					      (elserv-result-body result)))
890
    (elserv-set-result-body result nil)
891
    result))
892
893
(defun elserv-handle-post (process path request)
894
  "Handle POST request.
895
PROCESS is elserv process.
896
PATH is the requested path string.
897
REQUEST is the request structure."
898
  (elserv-service process path request))
899
900
(defun elserv-authenticate-basic (result value password-alist)
901
  "Implementation of basic authenticate type.
902
RESULT is the result structure.
903
VALUE is authorization value from client.
904
PASSWORD-ALIST is the alist of cons cell like: (USER . PASSWORD)."
905
  (when (string-match "\\([^:]*\\):\\(.*\\)" value)
906
    (let (user passwd)
907
      (setq user (substring value (match-beginning 1)(match-end 1)))
908
      (setq passwd (substring value (match-beginning 2)(match-end 2)))
909
      (when (string= (cdr (assoc user password-alist)) passwd)
910
	(elserv-set-result-user result user)
911
	t))))
912
913
(defun elserv-authenticate (request auth result)
914
  "Return unauthorized result.
915
REQUEST is the request structure.
916
AUTH is the auth structure.
917
Return RESULT if REQUEST is not authorized by AUTH.
918
Otherwise, RESULT is set as authenticated and return nil."
919
  (let ((authorization (plist-get request 'authorization)))
920
    (if (plist-get auth :realm) ; authentication required.
921
	(if (null authorization)
922
	    (funcall
923
	     (intern
924
	      (concat "elserv-make-unauthorized-" (plist-get auth :type)))
925
	     request
926
	     (plist-get auth :realm))
927
	  (setq authorization (nth 1 (split-string authorization)))
928
	  (if (funcall
929
	       (intern (concat "elserv-authenticate-" (plist-get auth :type)))
930
	       result
931
	       (base64-decode-string authorization)
932
	       (plist-get auth :users))
933
	      ;; OK.
934
	      nil
935
	    ;; Try again.
936
	    (funcall
937
	     (intern
938
	      (concat "elserv-make-unauthorized-" (plist-get auth :type)))
939
	     request
940
	     (plist-get auth :realm)))))))
941
942
(defun elserv-check-predicate (request predicate)
943
  "Return forbidden result if REQUEST does not satisfy PREDICATE."
944
  (let ((host (plist-get request 'client)))
945
    (unless (eval predicate)
946
      (signal 'elserv-forbidden (concat (car host) " is not allowed.")))))
947
948
;; Publish & Service
949
(defun elserv-publish (process path &rest args)
950
  "Publish a document.
951
PROCESS is the server process of Elserv.
952
PATH is the requested path.
953
Rest of arguments ARGS are plist of the form (:ATTR1 VAL1 :ATTR2 VAL2 ...)."
954
  (let (data set-auth auth predicate host doc)
955
    ;; Virtual host.
956
    (if (setq host (plist-get args :host))
957
	(setq path (concat host path)))
958
    (with-current-buffer (process-buffer process)
959
      (when (setq set-auth (plist-get args :authenticate))
960
	(setq auth
961
	      (list :type (or (plist-get set-auth :type) "basic")
962
		    :realm (plist-get set-auth :realm)
963
		    :users (plist-get set-auth :users))))
964
      (setq predicate (elserv-make-predicate-from-plist args))
965
      (setq doc (plist-get args :description))
966
      (cond
967
       ((setq data (plist-get args :directory)) ; directory is set.
968
	(if (or elserv-search-default-make-index
969
		(plist-get args :index))
970
	    (elserv-search-add-directory-index elserv-buffer-search-index-buffer
971
					       path data))
972
	(set (intern path elserv-buffer-publish-hash)
973
	     (list 'elserv-service-directory
974
		   doc auth predicate data)))
975
       ((setq data (plist-get args :string))    ; string is set.
976
	(if (or elserv-search-default-make-index
977
		(plist-get args :index))
978
	    (elserv-search-add-index elserv-buffer-search-index-buffer
979
				     path "" doc))
980
	(set (intern path elserv-buffer-publish-hash)
981
	     (list 'elserv-service-string
982
		   doc auth predicate data
983
		   (plist-get args :content-type))))
984
       ((setq data (plist-get args :function))   ; handler is set.
985
	(if (or elserv-search-default-make-index
986
		(plist-get args :index))
987
	    (elserv-search-add-index elserv-buffer-search-index-buffer
988
				     path "" doc))
989
	(set (intern path elserv-buffer-publish-hash)
990
	     (nconc (list 'elserv-service-function
991
			  doc auth predicate data
992
			  (plist-get args :content-type)))))))))
993
994
(defun elserv-unpublish (process path)
995
  "Unpublish a published document.
996
PROCESS is the server process of Elserv.
997
PATH is the requested path."
998
  (with-current-buffer (process-buffer process)
999
    (unintern path elserv-buffer-publish-hash)))
1000
1001
(defsubst elserv-execute-service-maybe (ppath path host request)
1002
  "Call service function for PPATH, PATH, HOST and REQUEST, if registered.
1003
Return result structure. If function is not registered, return nil."
1004
  (let (sym func)
1005
    (when (and (or (setq sym (intern-soft 
1006
			      (concat host ppath)
1007
			      elserv-buffer-publish-hash))
1008
		   (setq sym (intern-soft
1009
			      ppath
1010
			      elserv-buffer-publish-hash)))
1011
	       (boundp sym)
1012
	       (setq func (append (symbol-value sym)
1013
				  (list path ppath request))))
1014
      (apply (car func) (cdr func)))))
1015
1016
(defun elserv-parse-path (path)
1017
  "Return a reversed list of substrings of PATH which are separated by '/'."
1018
  (let ((start 0) parts)
1019
    (while (string-match "/" path start)
1020
      (setq parts (cons (substring path start (match-beginning 0)) parts)
1021
	    start (match-end 0)))
1022
    (cons (substring path start) parts)))
1023
1024
(defun elserv-service (process path request)
1025
  "Provide a service.
1026
PROCESS is the server process of Elserv.
1027
PATH is the requested path string.
1028
REQUEST is the request structure."
1029
  (let ((host (plist-get request 'host))
1030
	path-list ppath rpath result)
1031
    ;; absolute URI.
1032
    (when (string-match "^http://\\([^/]+\\)\\(/\\)" path)
1033
      (setq host (substring path (match-beginning 1) (match-end 1))
1034
	    path (substring path (match-beginning 2))))
1035
    (setq path-list (elserv-parse-path path))
1036
    (with-current-buffer (process-buffer process)
1037
      (while path-list
1038
	(setq ppath (concat (mapconcat 'identity
1039
				       (reverse path-list) "/"))
1040
	      rpath (substring path (length ppath)))
1041
	(when (eq (length ppath) 0)
1042
	  (setq ppath "/"))
1043
	(when (string= ppath "/")
1044
	  (setq rpath path))
1045
	(if (setq result (elserv-execute-service-maybe
1046
			  ppath rpath
1047
			  host request))
1048
	    (setq path-list nil))
1049
	(setq path-list (cdr path-list)))
1050
      (or result
1051
	  (signal 'elserv-file-not-found path)))))
1052
1053
(defun elserv-service-directory (doc auth predicate root path ppath request)
1054
  "Service a directory.
1055
DOC is the documentation of the service.
1056
AUTH is the autenticator plist.
1057
PREDICATE is the predicate to check a request.
1058
ROOT is the top directory recorded by `elserv-publish'.
1059
PATH is the path string relative from published path.
1060
PPATH is the path string published by `elserv-publish'.
1061
REQUEST is the request structure (plist)."
1062
  (let ((result (elserv-make-result)))
1063
    (or (elserv-check-predicate request predicate)
1064
	(elserv-authenticate request auth result)
1065
	(let (filename realfile attr mime-type)
1066
	  (setq filename (concat root path))
1067
	  (setq path (elserv-url-decode-string path))
1068
	  (when (string-match "\\.\\." path)
1069
	    (signal 'elserv-forbidden (concat root path)))
1070
	  (if (zerop (length (file-name-nondirectory filename)))
1071
	      (setq filename (expand-file-name
1072
 			      elserv-directory-index-file
1073
			      filename)))
1074
	  (cond ((file-directory-p filename)
1075
		 (elserv-make-redirect
1076
		  result
1077
		  (concat "http://" (plist-get request 'host)
1078
			  (unless (string= ppath "/") ppath)
1079
			  path "/")))
1080
		((setq realfile
1081
		       (elserv-negotiation
1082
			filename (plist-get request 'accept-language)))
1083
		 (if (and elserv-use-negotiation
1084
			  (listp realfile))
1085
		     (elserv-negotiation-make-result
1086
		      result
1087
		      (plist-get request 'host)
1088
		      (concat (unless (string= ppath "/") ppath) path)
1089
		      realfile)
1090
		   (setq mime-type (elserv-mime-type filename))
1091
		   (setq attr (file-attributes realfile))
1092
		   ;; Trace symbolic link.
1093
		   (when (stringp (car attr))
1094
		     (setq realfile (expand-file-name (car attr) root))
1095
		     (setq attr (file-attributes realfile)))
1096
		   (elserv-set-result-code result 'elserv-ok)
1097
		   (elserv-set-result-header result
1098
					     `(content-type ,mime-type))
1099
		   (elserv-set-result-body result
1100
					   (with-temp-buffer
1101
					     (insert-file-contents-as-binary
1102
					      realfile)
1103
					     (buffer-string)))
1104
		   result))
1105
		((and elserv-directory-autoindex
1106
		      (file-directory-p (file-name-directory filename))
1107
		      (string= elserv-directory-index-file
1108
			       (file-name-nondirectory filename)))
1109
		 (elserv-autoindex
1110
		  result
1111
		  (plist-get request 'host)
1112
		  (concat (unless (string= ppath "/") ppath) path)
1113
		  (file-name-directory filename)))
1114
		(t (signal 'elserv-file-not-found
1115
			   (concat (unless (string= ppath "/") ppath)
1116
				   path))))))))
1117
1118
(defun elserv-service-string (doc auth predicate string content-type path ppath
1119
				  request)
1120
  "Service a string.
1121
DOC is the documentation of the service.
1122
AUTH is the autenticator plist.
1123
PREDICATE is the predicate to check a request.
1124
STRING is the content string recorded by `elserv-publish'.
1125
CONTENT-TYPE is the content-type string recorded by `elserv-publish'.
1126
PATH is the path string relative from published path.
1127
PPATH is the path string published by `elserv-publish'.
1128
REQUEST is the request structure (plist)."
1129
  (let ((result (elserv-make-result)))
1130
    (or (elserv-check-predicate request predicate)
1131
	(elserv-authenticate request auth result)
1132
	(progn
1133
	  (elserv-set-result-code result 'elserv-ok)
1134
	  (elserv-set-result-header result
1135
				    `(content-type ,content-type))
1136
	  (elserv-set-result-body result string)
1137
	  result))))
1138
1139
(defun elserv-service-function (doc auth predicate function
1140
				     content-type path ppath request)
1141
  "Service by a function.
1142
DOC is the documentation of the service.
1143
AUTH is the autenticator plist.
1144
PREDICATE is the predicate to check a request.
1145
FUNCTION is the symbol of the function registered.
1146
CONTENT-TYPE is the content-type string registered.
1147
PATH is the path string relative from published path.
1148
PPATH is the published path string.
1149
REQUEST is the request structure (plist)."
1150
  (let ((result (elserv-make-result)))
1151
    (or (elserv-check-predicate request predicate)
1152
	(elserv-authenticate request auth result)
1153
	(progn
1154
	  (funcall function result
1155
		   (elserv-url-decode-string path)
1156
		   ppath request)
1157
	  (unless (elserv-result-code result)
1158
	    (elserv-set-result-code result 'elserv-ok)
1159
	    (unless (plist-get (elserv-result-header result) 'content-type)
1160
	      (elserv-set-result-header result
1161
					(append
1162
					 (elserv-result-header result)
1163
					 `(content-type ,(or content-type
1164
							     "text/plain"))))))
1165
	  result))))
1166
1167
(defun elserv-package-publish (process path name)
1168
  "Publish package.
1169
PROCESS is the server process of Elserv.
1170
PATH is the path to publish.
1171
NAME is the name of the package to publish."
1172
  (require (intern (concat "es-" name)))
1173
  (let ((sym (intern (concat "elserv-" name "-publish"))))
1174
    (if (fboundp sym)
1175
	(funcall sym process path)
1176
      (error "Cannot publish as package: %s." name))))
1177
1178
(defun elserv-publish-default (process)
1179
  "Publish default pages for PROCESS."
1180
  ;; Publish monitor.
1181
  (elserv-package-publish process "/" "monitor")
1182
  (elserv-package-publish process "/monitor" "monitor")
1183
  ;; Publish icons.
1184
  (if (and elserv-icon-path
1185
	   (file-directory-p elserv-icon-path))
1186
      (elserv-publish process elserv-icon-publish-path
1187
		      :directory elserv-icon-path)))
1188
1189
;;; Search
1190
(defconst elserv-search-index-buffer-name " *elserv search*"
1191
  "Buffer name for elserv search index.")
1192
1193
(defun elserv-search-initialize ()
1194
  (generate-new-buffer elserv-search-index-buffer-name))
1195
1196
(defun elserv-search-buffer (buffer regexp)
1197
  (let (bol result)
1198
    (with-current-buffer buffer
1199
      (goto-char (point-min))
1200
      (while (re-search-forward regexp nil t)
1201
	(beginning-of-line)
1202
	(setq bol (point))
1203
	(when (search-forward ":" nil t)
1204
	  (setq result (cons (buffer-substring bol (- (point) 1))
1205
			     result)))
1206
	(end-of-line)))
1207
    result))
1208
1209
(defun elserv-search-list-files-internal (dir &optional relative)
1210
  (let (files)
1211
    (dolist (file (delete ".." (delete "." (directory-files dir))))
1212
      (if (file-directory-p (expand-file-name file dir))
1213
	  (setq files (nconc (mapcar
1214
			      (lambda (f)
1215
				(concat relative
1216
					(if relative "/")
1217
					f))
1218
			      (elserv-search-list-files-internal
1219
 			       (expand-file-name file dir)
1220
			       file))
1221
			     files))
1222
	(setq files (cons (concat relative
1223
				  (if relative "/")
1224
				  file) files))))
1225
    files))
1226
1227
(defun elserv-search-list-files (dir)
1228
  (elserv-search-list-files-internal dir))
1229
1230
(defun elserv-search-add-index (buffer ppath path index)
1231
  (when (buffer-live-p buffer)
1232
    (with-current-buffer buffer
1233
      (goto-char (point-max))
1234
      (insert ppath (if (or (string= ppath "/") (string= path ""))
1235
			"" "/")
1236
	      path ":" (or index "") "\n"))))
1237
1238
(defun elserv-search-add-directory-index (buffer ppath dir)
1239
  (dolist (file (elserv-search-list-files dir))
1240
    (elserv-search-add-index buffer ppath file nil)))
1241
1242
(defun elserv-search (regexp)
1243
  "Search content which matches REGEXP."
1244
  ;; current buffer is process buffer.
1245
  (elserv-search-buffer elserv-buffer-search-index-buffer regexp))
1246
1247
;;; Utils
1248
1249
(defun elserv-replace-in-string (str regexp newtext &optional literal)
1250
  "Replace all matches in STR for REGEXP with NEWTEXT string.
1251
And returns the new string.
1252
Optional LITERAL non-nil means do a literal replacement.
1253
Otherwise treat \\ in NEWTEXT string as special:
1254
  \\& means substitute original matched text,
1255
  \\N means substitute match for \(...\) number N,
1256
  \\\\ means insert one \\."
1257
  (let ((rtn-str "")
1258
	(start 0)
1259
	(special)
1260
	match prev-start)
1261
    (while (setq match (string-match regexp str start))
1262
      (setq prev-start start
1263
	    start (match-end 0)
1264
	    rtn-str
1265
	    (concat
1266
	     rtn-str
1267
	     (substring str prev-start match)
1268
	     (cond (literal newtext)
1269
		   (t (mapconcat
1270
		       (function
1271
			(lambda (c)
1272
			  (if special
1273
			      (progn
1274
				(setq special nil)
1275
				(cond ((eq c ?\\) "\\")
1276
				      ((eq c ?&)
1277
				       (substring str (match-beginning 0)
1278
						  (match-end 0)))
1279
				      ((and (>= c ?0) (<= c ?9))
1280
				       (if (> c (+ ?0 (length
1281
						       (match-data))))
1282
					   ;; Invalid match num
1283
					   (error "Invalid match num: %c" c)
1284
					 (setq c (- c ?0))
1285
					 (substring str (match-beginning c)
1286
						    (match-end c))))
1287
				      (t (char-to-string c))))
1288
			    (if (eq c ?\\) (progn (setq special t) nil)
1289
			      (char-to-string c)))))
1290
		       newtext ""))))))
1291
    (concat rtn-str (substring str start))))
1292
1293
(provide 'elserv)
1294
1295
;;; elserv.el ends here