├── .hgignore ├── .gitignore ├── requirements.txt ├── default-webserver-image.png ├── test_docroot └── example.html ├── .hgtags ├── default-webserver-test.html ├── examples ├── styles.css ├── chat-tests.el ├── chat.html ├── insideout.el ├── elnode-org.el ├── chat.el ├── demo.el └── chat.js ├── Makefile ├── packagedir.el ├── default-wiki-index.creole ├── elnode_tutorial.org ├── elnode.org ├── elnode_tutorial.creole ├── README.creole ├── elnode-wiki.el ├── elnode-rle.el ├── COPYING └── elnode-tests.el /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: regexp 2 | .*~ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | .#* 4 | *.elc 5 | /.elpa 6 | /elnode-[0-9.]* 7 | /README$ 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | '((web "0.1.4")(db "0.0.1")(kv "0.0.9")(fakir "0.0.14")(creole "0.8.14")) 2 | -------------------------------------------------------------------------------- /default-webserver-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magnars/elnode/master/default-webserver-image.png -------------------------------------------------------------------------------- /test_docroot/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello!!!!

4 | 5 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 659227a0f1776a39b579ec471b419d8fd6fe3ee7 working_GET 2 | 6b5e46d02ff9cac9c5da77a454dd7a08ff9a4126 release_0_0_1 3 | 6b5e46d02ff9cac9c5da77a454dd7a08ff9a4126 release_0_0_1 4 | f6066f7961b34945059ada0254b7eb33bd45a953 release_0_0_1 5 | -------------------------------------------------------------------------------- /default-webserver-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is Elnode's Test HTML file

4 |

If you are reading this then most likely Elnode is working ok.

5 | 6 |

This file is normally stored in the user's directory. It can 7 | normally be found in:

8 | 9 |
10 | ~/.emacs.d/elnode
11 | 
12 | 13 |

To learn more about Elnode visit the website.

14 | 15 |

Just to show that Elnode will serve anything, here's a png:

16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | div#page { 6 | background-color: LightGrey; 7 | margin-left: 3em; 8 | margin-right: 3em; 9 | padding-left: 1em; 10 | height: 100%; 11 | } 12 | 13 | .hidden { 14 | display: none; 15 | } 16 | 17 | label { 18 | width: 20%; 19 | } 20 | 21 | table { 22 | width: 80%; 23 | } 24 | 25 | tr td { 26 | border-bottom: solid thin; 27 | } 28 | 29 | td.username { 30 | width: 20%; 31 | } 32 | 33 | td.message { 34 | width: 80%; 35 | } 36 | 37 | form { 38 | width: 100%; 39 | } 40 | -------------------------------------------------------------------------------- /examples/chat-tests.el: -------------------------------------------------------------------------------- 1 | ;;; 2 | 3 | (ert-deftest chat-list-since () 4 | "Test that the since and add stuff works." 5 | (flet ((select (seq &rest indeces) 6 | "Select multiple elements from a list." 7 | (loop for i in indeces 8 | if (elt seq i) 9 | collect (elt seq i)))) 10 | (let ((chat-list '()) 11 | (t1 (current-time))) 12 | (chat-add "nic" "hello everyone!") 13 | (let ((cls (chat-list-since t1))) 14 | (should 15 | (equal 16 | (select (car cls) 1 2) 17 | '("nic" "hello everyone!"))))))) 18 | 19 | 20 | ;;; chat-tests.el ends 21 | -------------------------------------------------------------------------------- /examples/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chat! 4 | 5 | 6 | 7 |
8 |

Chat!

9 | 10 |
11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 | 30 | 32 | 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # package.el multi-file package install 2 | 3 | # These are the variables that are specific to the package 4 | NAME=elnode 5 | VERSION=0.9.9.6.1 6 | DOC="A simple event handling HTTP server." 7 | 8 | # Everything beyond here should be generic 9 | REQUIREMENTS=requirements.txt 10 | package_parts = $(shell cat build-parts.txt) 11 | PACKAGE=$(NAME)-$(VERSION) 12 | TARBALL=$(PACKAGE).tar 13 | EMACS=emacs 14 | 15 | all: tarball 16 | 17 | buildtest: 18 | echo $(package_parts) 19 | 20 | # Install the tarball in a test package store 21 | test: tarball 22 | $(EMACS) -Q --batch -l ./packagedir.el -- $(TARBALL) 23 | 24 | # Install the tarball in the user's emacs 25 | install: tarball 26 | $(EMACS) --batch -l ~/.emacs.d/init.el -l ./build.el -- $(TARBALL) 27 | 28 | 29 | # Really would like this to clean the elc files of the package_parts 30 | clean-elc: 31 | rm -f *.elc 32 | 33 | clean: clean-elc 34 | rm -rf .elpa 35 | rm -rf $(TARBALL) 36 | rm -rf $(PACKAGE) 37 | rm -rf $(NAME)-pkg.el 38 | rm -rf README 39 | 40 | tarball: $(TARBALL) 41 | 42 | $(TARBALL): $(PACKAGE) $(PACKAGE)/$(NAME)-pkg.el 43 | tar cf $@ $< 44 | 45 | $(PACKAGE): $(package_parts) 46 | mkdir $@ 47 | cp $(package_parts) $@ 48 | 49 | $(PACKAGE)/$(NAME)-pkg.el: 50 | echo "(define-package \"$(NAME)\" \"$(VERSION)\" \"$(DOC)\" `cat $(REQUIREMENTS)`)" > $@ 51 | 52 | # End 53 | -------------------------------------------------------------------------------- /packagedir.el: -------------------------------------------------------------------------------- 1 | ;; Sets the package store to a dummy location 2 | 3 | (setq package-user-dir 4 | (concat 5 | (file-name-directory 6 | (or (buffer-file-name) 7 | load-file-name 8 | default-directory)) 9 | ".elpa")) 10 | (load-file "build.el") 11 | 12 | ;; Load all the files in the list 13 | (flet ((map-regex (buffer regex fn) 14 | (with-current-buffer buffer 15 | (save-excursion 16 | (goto-char (point-min)) 17 | (let (res) 18 | (save-match-data 19 | (while (re-search-forward regex nil t) 20 | (let ((f (match-data))) 21 | (setq res 22 | (append res 23 | (list 24 | (save-match-data 25 | (funcall fn f)))))))) 26 | res))))) 27 | (map-regex 28 | (find-file-noselect 29 | (concat 30 | (file-name-directory 31 | (or (buffer-file-name) 32 | load-file-name 33 | default-directory)) "build-parts.txt")) 34 | "^\\(.*.el\\(\\.gz\\)*\\)$" 35 | (lambda (md) 36 | (let ((filename (match-string 0))) 37 | (when (file-exists-p filename) 38 | (load-file filename)))))) 39 | 40 | ;; And now run the tests 41 | (ert-run-tests-batch-and-exit) 42 | 43 | ;; End 44 | -------------------------------------------------------------------------------- /examples/insideout.el: -------------------------------------------------------------------------------- 1 | ;; Expose your running emacs to the local web by using elnode 2 | ;; Copyright (C) 2010 by Nic Ferrier 3 | 4 | (defun insideout-html-escape (str) 5 | "Escape significant HTML characters in 'str'" 6 | (replace-regexp-in-string 7 | "<\\|\\&" 8 | (lambda (src) 9 | (cond 10 | ((equal src "&") "&") 11 | ((equal src "<") "<"))) 12 | str)) 13 | 14 | (defun insideout-render (buf-name) 15 | "Render buffer 'buf' as HTML" 16 | (with-current-buffer (get-buffer buf-name) 17 | (format " 18 |
%s
19 | " 20 | (insideout-html-escape 21 | (buffer-substring-no-properties (point-min) (point-max)))))) 22 | 23 | (defun insideout-handler (httpcon) 24 | "Turn your emacs insideout with elnode. 25 | 26 | This exposes all your non * buffers to localhost - so beware." 27 | (let ((p (elnode-http-pathinfo httpcon))) 28 | (let ((buf-name (progn 29 | (string-match "/\\(.*\\)" p) 30 | (match-string 1 p)))) 31 | ;; An individual buffer 32 | (progn 33 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) 34 | (elnode-http-return 35 | httpcon 36 | (if (bufferp (get-buffer buf-name)) 37 | (insideout-render buf-name) 38 | ;; The buffer index 39 | (format "" 40 | (mapconcat 41 | (lambda (buf) 42 | (if (not (string-match "\\*" (buffer-name buf))) 43 | (format "
  • %s
  • " 44 | (buffer-name buf) 45 | (buffer-name buf)))) 46 | (buffer-list) 47 | "\n")))))))) 48 | 49 | (elnode-start 'insideout-handler 8028 "localhost") 50 | 51 | ;; End 52 | -------------------------------------------------------------------------------- /examples/elnode-org.el: -------------------------------------------------------------------------------- 1 | ;;; elnode-org.el 2 | 3 | (require 'elnode) 4 | 5 | (defun elnode-org-handler (httpcon) 6 | (elnode-docroot-for "~/work/org" 7 | with org-file 8 | on httpcon 9 | do (with-current-buffer (find-file-noselect org-file) 10 | (let ((org-html 11 | ;; This might throw errors so you could condition-case it 12 | (org-export-as-html 3 nil nil 'string))) 13 | (elnode-send-html httpcon org-html))))) 14 | 15 | (defun elnode-org-update-handler (httpcon) 16 | "Elnode handler to do org-mode updates. 17 | 18 | Specify `file-name' for the file to update, `node-match' for an 19 | org-agenda match, `in-node-match' to specify what will be 20 | replaced in the node matched org line, `replace-match' for the 21 | replacement." 22 | (elnode-method httpcon 23 | (POST 24 | (let* ((file-name (elnode-http-param httpcon "file-name")) 25 | (node-match (elnode-http-param httpcon "node-match")) 26 | (in-node-match (elnode-http-param httpcon "in-node-match")) 27 | (replace-match (elnode-http-param httpcon "replace-match"))) 28 | (elnode-org--update 29 | file-name 30 | node-match 31 | in-node-match 32 | replace-match))))) 33 | 34 | (defun elnode-org--update (file-name node-match in-node-match replace-match) 35 | "Update org-mode specified FILE-NAME. 36 | 37 | NODE-MATCH specifies a match expression in the manner of org-agenda views. 38 | 39 | IN-NODE-MATCH specifies a string match expression used with the 40 | bounds of the matched node line. 41 | 42 | REPLACE-MATCH specifies the replacement for the IN-NODE-MATCH." 43 | (with-current-buffer (find-file-noselect file-name) 44 | (org-map-entries 45 | (lambda () 46 | (replace-regexp 47 | in-node-match 48 | replace-match 49 | nil 50 | (point) 51 | (line-end-position))) 52 | node-match))) 53 | 54 | (elnode-start 'elnode-org-handler :port 8002) 55 | 56 | 57 | ;;; elnode-org.el ends here 58 | -------------------------------------------------------------------------------- /examples/chat.el: -------------------------------------------------------------------------------- 1 | ;;; chat example - very simple webchat -*- lexical-binding: t -*- 2 | (require 'esxml) 3 | (require 'cl) 4 | 5 | (defvar chat-list '()) 6 | 7 | (defun chat-add (user text) 8 | (add-to-list 9 | 'chat-list 10 | (list (current-time) user text))) 11 | 12 | (defun chat-list-since (since) 13 | (loop for rec in chat-list 14 | if (time-less-p since (car rec)) 15 | collect rec)) 16 | 17 | ;; And now the elnode 18 | 19 | (require 'elnode) 20 | 21 | (defun chat-comet-handler (httpcon) 22 | "Defer until there is new chat." 23 | (let ((entered (current-time))) 24 | (elnode-defer-until (chat-list-since entered) 25 | (elnode-send-json 26 | httpcon elnode-defer-guard-it :jsonp t)))) 27 | 28 | (defun chat-send-handler (httpcon) 29 | (let* ((username (elnode-http-cookie httpcon "chatusername" t)) 30 | (msg (elnode-http-param httpcon "msg"))) 31 | (chat-add username msg) 32 | (elnode-send-json httpcon (json-encode '("thanks"))))) 33 | 34 | 35 | ;; Main page setup stuff 36 | 37 | (defconst chat-dir (file-name-directory 38 | (or (buffer-file-name) 39 | load-file-name 40 | default-directory))) 41 | 42 | (defun chat-list-to-html () 43 | "Return the `chat-list' as rows for initial chat display." 44 | (loop for entry in chat-list 45 | if (equal 3 (length entry)) 46 | concat 47 | (esxml-to-xml 48 | `(tr 49 | () 50 | (td 51 | ((class . ,(concat "username " (elt entry 1)))) ,(elt entry 1)) 52 | (td ((class . "message")) ,(elt entry 2)))))) 53 | 54 | (defun chat-main-templater () 55 | "Return the `chat-list' as rows for initial chat display." 56 | (list 57 | (cons 58 | "messages" 59 | (let ((chat-list 60 | (subseq chat-list 61 | 0 (if (> (length chat-list) 10) 62 | 12 63 | (length chat-list))))) 64 | (chat-list-to-html))))) 65 | 66 | (defun chat-main-handler (httpcon) 67 | "The main handler." 68 | (let ((chat-js (concat chat-dir "chat.js")) 69 | (chat-html (concat chat-dir "chat.html")) 70 | (chat-css (concat chat-dir "styles.css"))) 71 | (elnode-hostpath-dispatcher 72 | httpcon 73 | `(("^.*//chat/poll/" . chat-comet-handler) 74 | ("^.*//chat/send/" . chat-send-handler) 75 | ("^.*//chat.js" . ,(elnode-make-send-file chat-js)) 76 | ("^.*//styles.css" . ,(elnode-make-send-file chat-css)) 77 | ("^.*//" . ,(elnode-make-send-file 78 | chat-html 79 | :replacements 'chat-main-templater)))))) 80 | 81 | ;;; chat.el ends here 82 | -------------------------------------------------------------------------------- /default-wiki-index.creole: -------------------------------------------------------------------------------- 1 | = Elnode Wiki = 2 | 3 | This is Elnode's Wiki. It is based on the {{{creole}}} wiki language 4 | and is written completely in EmacsLisp. 5 | 6 | == What does it do? == 7 | 8 | It does syntax coloring: 9 | 10 | {{{ 11 | ##! emacs-lisp 12 | (defun elnode-wiki-handler (httpcon wikiroot) 13 | "A low level handler for Wiki operations. 14 | 15 | Send the Wiki page requested, which must be a file existing under 16 | the WIKIROOT, back to the HTTPCON. 17 | 18 | Update operations are protected by authentication." 19 | (elnode-method httpcon 20 | (GET 21 | (elnode-docroot-for wikiroot 22 | with target-path 23 | on httpcon 24 | do 25 | (if (equal target-path (expand-file-name (concat wikiroot "/"))) 26 | (elnode-wiki-page httpcon (concat wikiroot "/index.creole")) 27 | (elnode-wiki-page httpcon target-path)))) 28 | (POST 29 | (elnode-with-auth httpcon 'elnode-wiki-auth 30 | (let* ((path (elnode-http-pathinfo httpcon)) 31 | (text (elnode-wiki--text-param httpcon))) 32 | (if (not (elnode-http-param httpcon "preview")) 33 | ;; A save request in which case save the new text and then 34 | ;; send the wiki text. 35 | (elnode-wiki--save-request httpcon wikiroot path text) 36 | ;; Might be a preview request in which case send back the WIKI 37 | ;; text that's been sent. 38 | (with-temp-file "/tmp/preview" 39 | (insert text)) 40 | (elnode-wiki-send httpcon "/tmp/preview" path))))))) 41 | }}} 42 | 43 | It does links, for example to 44 | [[http://github.com/nicferrier/elwikicreole|Emacs Creole]] which is 45 | the Wiki render engine used to display pages. 46 | 47 | It does all the normal Wiki things like headings and lists. 48 | 49 | You can also do some special Emacs things, like org-mode tables: 50 | 51 | | Date | Amount | Description | 52 | |------------+--------+---------------------| 53 | | 2011-11-15 | 100.15 | Expensive lunch out | 54 | | 2011-11-18 | 7.30 | Dry cleaning | 55 | | 2011-11-21 | 22.50 | Takeaway curry | 56 | |------------+--------+---------------------| 57 | | | 129.95 | | 58 | #+TBLFM: @5$2=vsum(@I..@II) 59 | 60 | and lisp callouts: 61 | 62 | <<( 63 | (mapconcat 64 | (lambda (s) 65 | (format "* %s" s)) 66 | '("which" "eval" "lisp" "and" "render" "the" "results") 67 | "\n") 68 | )>> 69 | 70 | 71 | == Authentication == 72 | 73 | By default, the Wiki uses an authentication database in the Emacs 74 | instance running Elnode and the Wiki server. 75 | 76 | If you want to add a user to the Wiki so you can edit pages you can do this in Emacs: 77 | 78 | {{{ 79 | M-x elnode-auth-user-add 80 | }}} 81 | 82 | and it will ask you for a username and a password. The user will be 83 | stored in a persistent database. 84 | 85 | 86 | == Where the Wiki pages are == 87 | 88 | By default the Elnode Wiki stores files in your {{{~/.emacs.d}}} 89 | directory which is actually defined by the variable 90 | {{{user-emacs-directory}}} in Emacs. 91 | 92 | There is normally a directory {{{elnode}}} in that directory which 93 | contains directories for the Web server document root and the Wiki. 94 | 95 | The location of the Wiki files can be configured though, try: 96 | 97 | {{{ 98 | M-x customize-variable [RET] elnode-wikiserver-wikiroot 99 | }}} 100 | 101 | == More customization == 102 | 103 | There are many other things in Elnode's Wiki that can be customized, 104 | including the header and footer. Use: 105 | 106 | {{{ 107 | M-x customize-group [RET] elnode-wikiserver [RET] 108 | }}} 109 | 110 | There is more to do with the Elnode Wiki server because there is so 111 | much that Emacs can do. 112 | -------------------------------------------------------------------------------- /elnode_tutorial.org: -------------------------------------------------------------------------------- 1 | 2 | * suggestions 3 | ** How about going from "hello world" on? 4 | *** Start with that, 5 | *** and then, move on to publishing a static file, 6 | *** then a buffer, 7 | **** with calling functions to manipulate the buffer and re-present it? 8 | ** That'd save me a fair bit of tinkering :) 9 | 10 | * installing 11 | ** use elpa/marmalade 12 | * what elnode gives you by default 13 | ** require elnode 14 | *** elnode-init 15 | **** starts a server 16 | ***** on port 8000 17 | 18 | 19 | * hello world 20 | ** install elnode with marmalade 21 | ** open a new emacs buffer C-x C-f my-elnode-hello-world.el 22 | ** make a handler 23 | (defun my-elnode-hello-world-handler (httpcon) 24 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) 25 | (elnode-http-return 26 | httpcon 27 | "

    Hello World

    ")) 28 | (elnode-start my-elnode-hello-world-handler 8028 "localhost") 29 | ** now evaluate that with M-x eval-buffer 30 | ** now open localhost:8028 in your browser 31 | 32 | * publish some files 33 | ** elnode provides a webserver, more accurately a fileserver 34 | ** the webserver is turned on by default 35 | ** open localhost:8000 and you should see ~/public_html 36 | *** if you don't have ~/public_html then make one? 37 | *** or configure elnode-webserver-docroot 38 | ** make a new webserver 39 | *** make a new docroot 40 | **** mkdir ~/myspecialdocroot 41 | *** put an html file in there 42 | cat < ~/myspecialdocroot/saybum.html 43 | 44 |

    BUM!

    45 | 46 | *** open a new emacs buffer 47 | *** put the following lisp in 48 | (defvar my-elnode-webserver-handler 49 | (elnode-webserver-handler-maker "~/myspecialdocroot")) 50 | (elnode-start my-elnode-webserver-handler 8001 "localhost") 51 | *** now evaluate that with M-x eval-buffer 52 | *** now open localhost:8001/saybum.html 53 | *** now open localhost:8001 54 | **** you should see an automatic index 55 | 56 | * stopping a server 57 | ** stop 8028 58 | ** stop 8001 59 | 60 | * add a binding to the standard server 61 | ** we can add bindings to the standard elnode server 62 | ** go back to hello world - C-x b my-elnode-hello-world.el 63 | ** remove the server-start and add this: 64 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler)) 65 | ** so now it should be: 66 | (defun my-elnode-hello-world-handler (httpcon) 67 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) 68 | (elnode-http-return 69 | httpcon 70 | "

    Hello World

    ")) 71 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler)) 72 | ** now eval the buffer with M-x eval-buffer 73 | ** now open localhost:8000/helloworld/ in your browser 74 | ** just to prove the webserver is still there, open localhost:8000/ 75 | *** check it's still the directory ~/public_html 76 | ** check the variable elnode-hostpath-default-table with C-h v elnode-hostpath-default-table 77 | Its value is (("/helloworld/" . my-elnode-hello-world-handler) 78 | ("[^/]+/.*" . elnode-webserver)) 79 | ** elnode-hostpath-default-table can also be customized 80 | *** but any handler will have to be loaded so you probably need to package and load your elnode module 81 | 82 | * publishing something else? 83 | ** let's try and make an online editor 84 | ** make a new file my-elnode-editor.el 85 | (defvar my-elnode-editor-buffer (get-buffer-create "*my-elnode-editor-buffer*")) 86 | 87 | (defun my-elnode-editor-handler (httpcon) 88 | (elnode-http-start httpcon 200 '("Content-Type" . "text/plain")) 89 | (elnode-http-return 90 | httpcon 91 | (with-current-buffer my-elnode-editor-buffer 92 | (buffer-substring-no-properties (point-min) (point-max))))) 93 | ** eval that 94 | ** go type some data in *my-elnode-editor-buffer* 95 | ** then M-x elnode-start my-elnode-editor-handler 8002 localhost 96 | ** try and hit localhost:8002 97 | ** go update the buffer 98 | ** refresh the webpage 99 | ** but what about someone else updating the buffer? 100 | ** make another handler to handle updates 101 | (defun my-elnode-editor-update-handler (httpcon) 102 | (let ((change-text (elnode-http-param httpcon "change"))) 103 | (with-current-buffer my-elnode-editor-buffer 104 | (goto-char (point-max)) 105 | (insert (if (stringp change-text) 106 | change-text 107 | "")))) 108 | (elnode-http-start httpcon 302 '("Location" . "/")) 109 | (elnode-http-return httpcon)) 110 | ** now we need to map these two handlers 111 | *** one to / and the other to /update/ 112 | ** make a new variable 113 | (defvar my-elnode-editor-urls 114 | `( 115 | ("$" . my-elnode-editor-handler) 116 | ("update/.*$" . my-elnode-editor-update-handler))) 117 | ** and make a dispatcher handler for the urls 118 | (defun my-elnode-editor-dispatcher-handler (httpcon) 119 | (elnode-dispatcher httpcon my-elnode-editor-urls)) 120 | *** a dispatcher handler is a handler that accepts requests and dispatches them to further handlers. 121 | *** moar about dispatcher handlers. 122 | ** now stop the old server 123 | ** M-x elnode-stop 8002 124 | ** Now start the new server with the dispatcher handler 125 | ** then M-x elnode-start my-elnode-editor-dispatcher-handler 8002 localhost 126 | ** now visit localhost:8002 and see the buffer 127 | ** now visit localhost:8002/update/?change=lah+dee+dah%0d and see the updated buffer 128 | -------------------------------------------------------------------------------- /examples/demo.el: -------------------------------------------------------------------------------- 1 | ;; Demo handlers for elnode 2 | 3 | (require 'elnode) 4 | 5 | (defun nicferrier-handler (httpcon) 6 | "Demonstration function. 7 | 8 | This is a simple handler that just sends some HTML in response to 9 | any request." 10 | (let* ((host (elnode-http-header httpcon "Host")) 11 | (pathinfo (elnode-http-pathinfo httpcon)) 12 | ) 13 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 14 | (elnode-http-return 15 | httpcon 16 | (format 17 | " 18 | 19 |

    %s

    20 | HELLO @ %s %s %s 21 | 22 | 23 | " 24 | (or (cdr (assoc "name" (elnode-http-params httpcon))) "no name") 25 | host 26 | pathinfo 27 | (elnode-http-version httpcon))))) 28 | 29 | (defun nicferrier-process-handler (httpcon) 30 | "Demonstration function 31 | 32 | This is a handler based on an asynchronous process." 33 | (let* ((host (elnode-http-header httpcon "Host")) 34 | (pathinfo (elnode-http-pathinfo httpcon)) 35 | ) 36 | (elnode-http-start httpcon 200 '("Content-type" . "text/plain")) 37 | (elnode-child-process httpcon "cat" (expand-file-name "~/elnode/node.el")))) 38 | 39 | (defun nicferrier-process-webserver (httpcon) 40 | "Demonstration webserver. 41 | 42 | Shows how to use elnode's built in webserver toolkit to make 43 | something that will serve a docroot." 44 | ;; Find the directory where this file is defined so we can serve 45 | ;; files from there 46 | (let ((docroot (file-name-directory 47 | (buffer-file-name 48 | (car 49 | (save-excursion 50 | (find-definition-noselect 'nicferrier-process-webserver nil))))))) 51 | (let ((webserver (elnode-webserver-handler-maker docroot))) 52 | (funcall webserver httpcon)))) 53 | 54 | (defun nicferrier-mapper-handler (httpcon) 55 | "Demonstration function 56 | 57 | Shows how a handler can contain a dispatcher to make it simple to 58 | handle more complex requests." 59 | (elnode-dispatcher httpcon 60 | '(("$" . nicferrier-handler) 61 | ("nicferrier/$" . nicferrier-handler)))) 62 | 63 | (defun nicferrier-post-handler (httpcon) 64 | "Handle a POST. 65 | 66 | If it's not a POST send a 400." 67 | (if (not (equal "POST" (elnode-http-method httpcon))) 68 | (progn 69 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 70 | (elnode-http-return httpcon (format " 71 | 72 | 73 |
    74 | 75 | 76 | 77 |
    78 | 79 | 80 | " (elnode-http-pathinfo httpcon)))) 81 | (let ((params (elnode-http-params httpcon))) 82 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 83 | (elnode-http-return 84 | httpcon 85 | (format "\n" 86 | (mapconcat 87 | (lambda (param) 88 | (format "
  • %s: %s
  • " (car param) (cdr param))) 89 | params 90 | "\n")))))) 91 | 92 | (defun nicferrier-tache-enabled-post-handler (httpcon) 93 | "Handle a POST. 94 | 95 | If it's not a POST send a 400." 96 | (if (not (equal "POST" (elnode-http-method httpcon))) 97 | (progn 98 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 99 | (elnode-http-return httpcon (format " 100 | 101 | 102 |
    103 | 104 | 105 | 106 |
    107 | 108 | 109 | " (elnode-http-pathinfo httpcon)))) 110 | (let ((params (elnode-http-params httpcon))) 111 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 112 | (elnode-http-return 113 | httpcon 114 | (tache "\n" 115 | `(:params 116 | (lambda () 117 | (mapconcat 118 | (lambda (e) 119 | `(:paramcar ,(car e)) 120 | :paramcdr ,(cdr e)) 121 | params 122 | "\n")))) 123 | ) 124 | ) 125 | ) 126 | ) 127 | 128 | 129 | (defun nicferrier-everything-mapper-handler (httpcon) 130 | "Demonstration function 131 | 132 | Shows how a handler can contain a dispatcher to make it simple to 133 | handle more complex requests." 134 | (elnode-dispatcher 135 | httpcon 136 | `(("$" . nicferrier-post-handler) 137 | ("nicferrier/\\(.*\\)$" . ,(elnode-webserver-handler-maker "~/public_html"))))) 138 | 139 | 140 | 141 | ;; need to update this so something can 142 | (defvar nicferrier-defer-switch nil) 143 | 144 | (defun nicferrier-defer-handler (httpcon) 145 | (if nicferrier-defer-switch 146 | (progn 147 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 148 | (elnode-http-return httpcon "BING!") 149 | ) 150 | (progn 151 | (setq nicferrier-defer-switch 't) 152 | (elnode-defer-now 'nicferrier-defer-handler)))) 153 | 154 | ;; Boot elpad on port 8002 155 | (elnode-start 'nicferrier-defer-handler 8002 "localhost") 156 | 157 | ;; End 158 | -------------------------------------------------------------------------------- /elnode.org: -------------------------------------------------------------------------------- 1 | 2 | * planned FAQ 3 | ** FAQ for installation 4 | *** why does it not start? 5 | **** have you got something running on port 8000? 6 | **** how to change the port and restart 7 | 8 | ** programming 9 | *** what's a hello world handler look like? 10 | **** the simplest elnode handler is 11 | (defun hello-world (httpcon) 12 | (elnode-send-html httpcon "Hello World")) 13 | 14 | *** how do I start a handler? 15 | **** using M-x elnode-start [RET] handler-name 16 | ***** the handler-name is any function 17 | ***** it will complete in the normal minibuffer way 18 | *** how can I make a handler serve some static files? 19 | **** so easy, like this: 20 | (setq my-webserver-handler (elnode-webserver-handler-maker "~/directory")) 21 | *** what if I want to do something a bit unusual to the file before it's served? like tempalting 22 | **** you need to write a proper handler for that 23 | **** let's say you want to replace {{{name}}} with a single word 24 | (defun my-files-handler (httpcon) 25 | (elnode-docroot-for DIRECTORY 26 | with target-filename 27 | on httpcon 28 | do 29 | (with-current-buffer (find-file-noselect target-filename) 30 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 31 | (elnode-http-return 32 | httpcon 33 | (replace-regexp-in-string "{{{\\(.*\\)}}}" "bleh" 34 | (buffer-substring (point-min) (point-max))))))) 35 | *** the logging is crazy, can I turn if off? 36 | **** yep. 37 | M-x customize-variable [RET] elnode-log-files-directory 38 | **** and 39 | M-x customize-variable [RET] elnode-error-log-to-messages 40 | **** are 2 interesting ones 41 | 42 | ** Other questions 43 | *** What if my friends laugh at me for running a web browser in my editor? 44 | **** Get better friends? #emacs is a good source of fun people 45 | **** Alternately start a new business that uses elnode and pisses on the competition 46 | ***** because it is faster and more reliable. 47 | ***** then buy new friends. 48 | **** Or go back to using Ruby because Ruby is, ya know, really cool. Like your friends say. 49 | 50 | 51 | * auth stuff 52 | ** things auth requires 53 | *** test 54 | **** are you currently authenticated? 55 | **** most often this is testing a cookie 56 | **** on success do whatever you were going to do 57 | *** failure action 58 | **** redirect to a login page 59 | **** serve a login page 60 | 61 | ** login pages 62 | *** test 63 | **** are the credentials correct? 64 | *** success 65 | **** set a token to remember the request somehow 66 | ***** store something in the server? 67 | ****** so you can validatethe auth 68 | ******* login makes token 69 | ******* store token against username 70 | ******* put token:username:server-secretkey on cookie 71 | ******* 72 | **** redirect to some page 73 | ***** maybe identified by a parameter or the referrer 74 | *** failure 75 | **** redirect to a login failed page 76 | **** return the same page with errors 77 | *** links 78 | **** registration page 79 | 80 | 81 | (with-elnode-auth 82 | (:test cookie 83 | :cookie-name my-auth 84 | :failure-type redirect 85 | :redirect "/mylogin") 86 | ...) 87 | 88 | :redirect could be: 89 | 90 | a string - which would point to a relative or absolute url which must 91 | be mapped indepentently 92 | 93 | a (dispatcher . handler) pair - a cons of a dispatcher and a handler, 94 | the dispatcher is automatically wrapped with a detector for a url 95 | that serves the handler 96 | 97 | a (dispatcher handler string) list - as for the 98 | dispatcher/handler cons but with the addition of the string to name 99 | the url to login 100 | 101 | ** idea about data/handlers 102 | *** for login, the processing of the authentication request (the username and password check) is the bit we can specify as part of the auth system 103 | **** it goes 104 | ***** get a username/password 105 | ****** and possibly other things like "cookie approval" 106 | ***** check against database 107 | ****** plus any other rules, like "cookie approval is yes" 108 | ***** make cookie 109 | ***** redirect to wherever we were supposed to be redirecting 110 | ****** this could have been specified 111 | ******* as a parameter 112 | ******* or it could be fixed 113 | ******* or looked up in the server side environment 114 | *** the bit we can't specify 115 | **** the look of the login page 116 | **** or even the url of the login page 117 | **** or how the login page works 118 | ***** we need to be able to support AJAX login 119 | ***** so you can login from the main page and from non-contextual flows 120 | *** it's frustrating because the only thing we care about on the login page is 121 | **** the login form, which is very specifiable 122 | **** particularly the url which the form POSTs to 123 | ***** which must have our auth handler on the end of it 124 | *** so we need a high level abstraction for dealing with this 125 | *** if we could specify interactive elements, like FORMs as 126 | **** a description of the data 127 | **** possibly a template 128 | ***** it should be possible to have a default template 129 | ***** client side template? 130 | **** the handler code to handle the call 131 | *** and then have those wrap in the same way as the (dispatcher . handler) form above 132 | *** reasons this would be good 133 | **** the separate description of the data means it could be used for ajax and context pages 134 | **** the template is optional 135 | ***** maybe we could have contextual templates as well 136 | ****** a template for ajax 137 | ****** a template for page 138 | **** the authentication processor is probably fixed 139 | **** this could be the tip of a larger abstraction to do better website building 140 | 141 | how about we make a function to return a wrap spec? 142 | 143 | like this: 144 | 145 | (with-elnode-auth 146 | (:test cookie 147 | :cookie-name my-auth 148 | :failure-type redirect 149 | :redirect (elnode-make-auth-wrap 'my-app form-template)) 150 | ...) 151 | 152 | where (elnode-make-auth-wrap to-wrap template &optional path) 153 | => '(my-app (lambda (httpcon) (do-something-with template)) path) 154 | 155 | ** templates for auth - capturing some thoughts 156 | *** the current vogue is for mustache like templates 157 | *** these are dumb text replacers 158 | *** I prefer sed/xslt like templates 159 | **** not dumb, but more introspectively transformative 160 | *** can we make a simpler, less generic, transform language than xslt? 161 | *** it needs to transform data, such as json into HTML or XML 162 | *** things it might be 163 | **** a sequence of rules 164 | ***** for this bit of data, do this 165 | ****** { "password": "" } -> 166 | ***** questions about this 167 | ****** what's the pattern matching language??? 168 | ****** how do we link the "things" together? 169 | ******* eg: BR tags? 170 | ******* wrapping individually in DIVs? 171 | **** a bunch of associated rules 172 | ***** wrap everything we produce in some tag 173 | ****** eg: FORM tags 174 | 175 | 176 | * v0.9.9 todo 177 | ** new async stuff with RLE 178 | ** default wiki page and webserver root 179 | 180 | * screencasts 181 | ** introducing elnode 182 | *** start with plain emacs24 183 | *** install marmalade 184 | *** install elnode 185 | *** what does elnode do out of the box? 186 | **** webserver 187 | **** wiki 188 | **** auth database 189 | **** logging 190 | ** programming with elnode 191 | *** start with some files 192 | *** make a webserver with elnode-webserver-handler-maker 193 | *** org-mode 194 | ** chat 195 | *** what do you need? 196 | **** a list to store chat 197 | ***** a list of triples? (username date text) 198 | **** a handler to receive the chats 199 | ***** a POST or something 200 | **** a handler for people to call to wait and receive chats 201 | ***** should use elnode-defer-or-do to check for new chats 202 | * v1.00 todo 203 | ** stuff 204 | *** vagrant image 205 | *** heroku update 206 | **** vulcan helps build the version of unix you need to host the build pack 207 | ***** http://quickleft.com/blog/hacking-heroku-with-custom-build-packs 208 | **** the buildpack 209 | ***** https://github.com/technomancy/heroku-buildpack-emacs/tree/master/bin 210 | *** ami? 211 | ** code 212 | *** defer bugs? 213 | *** logging to processes 214 | *** client server stuff 215 | *** htmlize bugs? 216 | **** these seem to be fixed by new creole 217 | -------------------------------------------------------------------------------- /elnode_tutorial.creole: -------------------------------------------------------------------------------- 1 | = Getting Started with Elnode - the webserver for Emacs = 2 | 3 | This is a tutorial that will hopefully show you how to install and get 4 | started making web services with Elnode. 5 | 6 | Elnode is a node.js like webserver tool for Emacs. It let's you make 7 | and run web servers and services from inside Emacs. 8 | 9 | 10 | == Installing Elnode == 11 | 12 | You should install Elnode from the package available on 13 | [[http://marmalade-repo.org/packages/elnode|Marmalade]]. 14 | 15 | For dealing with package repositories check out the 16 | [[http://www.emacswiki.org/emacs/ELPA|Emacs Wiki]] but the short version 17 | is to add the following to your {{{.emacs}}} or your 18 | {{{.emacs.d/init.el}}}: 19 | 20 | {{{ 21 | (add-to-list 22 | 'package-archives 23 | '("marmalade" . "http://marmalade-repo.org/packages/")) 24 | }}} 25 | 26 | And then do: 27 | 28 | {{{ 29 | M-x list-packages 30 | }}} 31 | 32 | find Elnode in the list and press {{{i}}} or {{{RET}}} to install it. 33 | 34 | If you don't want to use packages you can just install {{{elnode.el}}} 35 | on your {{{load-path}}} somewhere and: 36 | 37 | {{{ 38 | (require 'elnode) 39 | }}} 40 | 41 | == Hello World! == 42 | 43 | Now we've installed Elnode you'll want to start making web services 44 | with it. Let's start with a Hello World example. 45 | 46 | open a new Emacs file 47 | 48 | {{{ 49 | C-x C-f my-elnode-hello-world.el 50 | }}} 51 | 52 | enter the Lisp code for the handler, for that old time feel you could type this in, or if you're under 35, maybe just cut and paste 53 | 54 | {{{ 55 | (defun my-elnode-hello-world-handler (httpcon) 56 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) 57 | (elnode-http-return 58 | httpcon 59 | "

    Hello World

    ")) 60 | (elnode-start 'my-elnode-hello-world-handler :port 8028 :host "localhost") 61 | }}} 62 | 63 | make the Lisp code //live// 64 | 65 | {{{ 66 | M-x eval-buffer 67 | }}} 68 | 69 | now open [[http://localhost:8028]] in your browser - you should see //Hello World!// 70 | 71 | 72 | == Publish some files == 73 | 74 | Elnode provides a builtin webserver that can serve files from a 75 | directory on your computer. The Elnode webserver is turned on by 76 | default (it's all configurable though). 77 | 78 | === The default webserver === 79 | 80 | By default the webserver delivers files from: 81 | 82 | {{{ 83 | ~/public_html 84 | }}} 85 | 86 | so if you have a public_html directory in your home directory then 87 | just browse to [[http://localhost:8000]] and you should see an index 88 | of that directory. 89 | 90 | If you don't have a {{{~/public_html}}} directory then just make one 91 | and drop a file or two in it. 92 | 93 | Alternately, try configuring the webserver root directory: 94 | 95 | {{{ 96 | M-x customize-variable RET elnode-webserver-docroot RET 97 | }}} 98 | 99 | to another directory. Then try hitting [[http://localhost:8000]] 100 | again. 101 | 102 | 103 | === Making another webserver === 104 | Now let's make a new webserver service. 105 | 106 | Make a new docroot: 107 | 108 | {{{ 109 | mkdir ~/myspecialdocroot 110 | }}} 111 | 112 | Put an html file in there: 113 | 114 | {{{ 115 | cat < ~/myspecialdocroot/saybum.html 116 | 117 |

    BUM!

    118 | 119 | }}} 120 | 121 | Now we have something to serve we can use Elnode to make the web service. 122 | 123 | Open a new Emacs file: 124 | 125 | {{{ 126 | C-x C-f my-elnode-webserver.el 127 | }}} 128 | 129 | Add this Lisp: 130 | 131 | {{{ 132 | (defconst my-elnode-webserver-handler 133 | (elnode-webserver-handler-maker "~/myspecialdocroot")) 134 | (elnode-start 'my-elnode-webserver-handler :port 8001 :host "localhost") 135 | }}} 136 | 137 | Now evaluate that with: {{{M-x eval-buffer}}} 138 | 139 | Now open [[http://localhost:8001/saybum.html]] 140 | 141 | Now open [[http://localhost:8001]] - you should see an automated index 142 | of {{{~/myspecialdocroot}}}. 143 | 144 | == Stopping a server == 145 | 146 | We've started a couple of servers now. Let's stop the two servers that 147 | we've started: 148 | 149 | {{{ 150 | M-x elnode-stop RET 8028 RET 151 | M-x elnode-stop RET 8001 RET 152 | }}} 153 | 154 | Those servers are now stopped and you won't be able to hit them. 155 | 156 | == Add a binding to the builtin server == 157 | 158 | Instead of starting new servers all the time we can add bindings to 159 | the standard Elnode server. Why would we do this? I think using a 160 | separate server for developing something initially is a good idea, but 161 | then you either have something you want to package up as it's own 162 | server (a wiki engine you've developed and want to give to other 163 | people, for example) or you have something you want to make available 164 | in your own default server. Of course, it's always a judgement, the 165 | way URLs work mean that you can pretty much always make any service 166 | available on it's own server or under a URL on another one. 167 | 168 | Let's make our Hello World example available again by binding it to 169 | the default server (which is still listening on port 8000 if you 170 | haven't changed anything). 171 | 172 | Go back to hello world: 173 | 174 | {{{ 175 | C-x b my-elnode-hello-world.el 176 | }}} 177 | 178 | Remove the {{{elnode-start}}} line and add this: 179 | 180 | {{{ 181 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler)) 182 | }}} 183 | 184 | So now it should look like this: 185 | 186 | {{{ 187 | (defun my-elnode-hello-world-handler (httpcon) 188 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) 189 | (elnode-http-return 190 | httpcon 191 | "

    Hello World

    ")) 192 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler)) 193 | }}} 194 | 195 | Now eval the buffer with {{{M-x eval-buffer}}} 196 | 197 | Now open [[http://localhost:8000/helloworld/]] in your browser. 198 | 199 | Just to prove the webserver is still there, open 200 | [[http://localhost:8000/]]. This should still show your 201 | {{{~/public_html}}} directory (or whatever you configured 202 | {{{elnode-webserver-docroot}}} to). 203 | 204 | Check the variable {{{elnode-hostpath-default-table}}} with {{{C-h v elnode-hostpath-default-table}}} 205 | 206 | The value should be something like: 207 | 208 | {{{ 209 | (("/helloworld/" . my-elnode-hello-world-handler) 210 | ("[^/]+/.*" . elnode-webserver)) 211 | }}} 212 | 213 | {{{elnode-hostpath-default-table}}} can also be customized to add more 214 | services. But any handler mapped in there will have to be loaded in at 215 | Emacs startup so you either need to package and load your Elnode code 216 | or put it in your {{{load-path}}} and {{{require}}} it from Emacs 217 | init. 218 | 219 | == A more advanced example - publishing a buffer == 220 | 221 | So far, all the examples have been quite trivial. Though I hope you 222 | think it's interesting that you can do all these things quite easily 223 | from inside Emacs. 224 | 225 | But now let's try something harder - let's make an web based editor. 226 | 227 | This is an exercise that will grow with the tutorial. I hope you'll be 228 | interested in the first draft, even though it's going to be relatively 229 | simple. 230 | 231 | Make a new file {{{C-x C-f my-elnode-editor.el}}}. 232 | 233 | Add the following Lisp code: 234 | 235 | {{{ 236 | (defvar my-elnode-editor-buffer (get-buffer-create "*my-elnode-editor-buffer*")) 237 | 238 | (defun my-elnode-editor-handler (httpcon) 239 | (elnode-http-start httpcon 200 '("Content-Type" . "text/plain")) 240 | (elnode-http-return 241 | httpcon 242 | (with-current-buffer my-elnode-editor-buffer 243 | (buffer-substring-no-properties (point-min) (point-max))))) 244 | }}} 245 | 246 | Eval that with {{{M-x eval-buffer}}}. 247 | 248 | Now go and type some text in ~*my-elnode-editor-buffer~*. This will be 249 | served by the editor service. 250 | 251 | Now let's start the service: 252 | 253 | {{{ 254 | M-x elnode-start 255 | my-elnode-editor-handler 256 | 8002 257 | localhost 258 | }}} 259 | 260 | Now try and hit [[http://localhost:8002]] - you should see whatever 261 | you typed in the ~*my-elnode-editor-buffer~*. 262 | 263 | Try updating the text in the buffer and refreshing the browser. We're 264 | displaying that buffer whatever it has in it. 265 | 266 | Ok. So we've published a buffer. But what about someone else updating 267 | it? 268 | 269 | Let's make another handler to handle updates, add this to your {{{my-elnode-editor.el}}}: 270 | 271 | {{{ 272 | (defun my-elnode-editor-update-handler (httpcon) 273 | (let ((change-text (elnode-http-param httpcon "change"))) 274 | (with-current-buffer my-elnode-editor-buffer 275 | (goto-char (point-max)) 276 | (if (stringp change-text) 277 | (insert change-text)))) 278 | (elnode-http-start httpcon 302 '("Location" . "/")) 279 | (elnode-http-return httpcon)) 280 | }}} 281 | 282 | Now we have two handlers we'll have to map them together 283 | somehow. Let's map one to the root ({{{/}}}) and one to 284 | {{{/update/}}}. Add the following code to {{{my-elnode-editor.el}}}: 285 | 286 | {{{ 287 | (defconst my-elnode-editor-urls 288 | `(("$" . my-elnode-editor-handler) 289 | ("update/.*$" . my-elnode-editor-update-handler))) 290 | }}} 291 | 292 | And now we need to add a handler to do the dispatching for these URLs, 293 | add this to {{{my-elnode-editor.el}}} as well: 294 | 295 | {{{ 296 | (defun my-elnode-editor-dispatcher-handler (httpcon) 297 | (elnode-dispatcher httpcon my-elnode-editor-urls)) 298 | }}} 299 | 300 | //What is a dispatcher?// - a dispatcher is a handler that take a list 301 | of URL pattern mappings and works out, by reading the data from the 302 | HTTP connection, what handler should be invoked for what request. 303 | 304 | Now we have our new dispatcher based code we need to stop the old server: 305 | 306 | {{{ 307 | M-x elnode-stop 8002 308 | }}} 309 | 310 | And now start the new server with the dispatcher handler: 311 | 312 | {{{ 313 | M-x elnode-start 314 | my-elnode-editor-dispatcher-handler 315 | 8002 316 | localhost 317 | }}} 318 | 319 | Now visit [[http://localhost:8002]] and see the buffer as it stands 320 | and then visit 321 | [[http://localhost:8002/update/?change=%0dlah+dee+dah%0d]] and see the 322 | updated buffer. 323 | 324 | == More advanced again - Make a webapp around the service == 325 | 326 | Let's take our editor on another step. Let's add some static files and 327 | have the Elnode handlers be called by client side Javascript. 328 | 329 | If we're going to add some static files, we'll need a webserver. We 330 | already know how to do that. Once we've got some javascript though, 331 | we'll probably not want to retrieve the text by {{{HTTP GET}}}ing the 332 | root url, so let's alter that binding to {{{/text/}}} as well: 333 | 334 | {{{ 335 | (defconst my-elnode-editor-webserver-handler 336 | (elnode-webserver-handler-maker "~/my-directory")) 337 | "The webserver handler.") 338 | 339 | (defconst my-elnode-editor-urls 340 | '(("text/$" . my-elnode-editor-handler) 341 | ("update/.*$" . my-elnode-editor-update-handler) 342 | ("[^/]+/.*" . my-elnode-editor-webserver-handler))) 343 | }}} 344 | 345 | Obviously {{{~/my-directory}}} needs to be the place where you are 346 | going to save your HTML and Javascript files. 347 | 348 | Now we need those HTML and Javascript files. Let's make the HTML 349 | first: 350 | 351 | {{{ 352 | 353 | 354 | 357 | 359 | 360 | 361 | 363 | 364 | 365 | }}} 366 | 367 | We're going to pull jQuery from Google's Content Delivery 368 | Network. We've put in a placeholder for our own Javascript file and 369 | other than that the HTML is really just a {{{textarea}}} 370 | element. We'll use //that// for putting the buffer text in. 371 | 372 | Now, what should the Javascript do? 373 | 374 | * when the page loads 375 | * make an AJAX call to Elnode for the buffer text 376 | * stick the received text into the {{{textarea}}} 377 | 378 | Ok. So here is {{{my-elnode-editor.js}}}: 379 | 380 | {{{ 381 | var my_elnode_editor = (function () { 382 | var self = { 383 | /** Get the text from Emacs. 384 | */ 385 | get_text: function () { 386 | $.ajax("/text/", { 387 | dataType: "text", 388 | success: function (data, textStatus, jqXHR) { 389 | $("#text").text(data); 390 | } 391 | }); 392 | } 393 | }; 394 | return self; 395 | })(); 396 | 397 | $(document).ready( 398 | function () { 399 | my_elnode_editor.get_text(); 400 | } 401 | ); 402 | }}} 403 | 404 | Save this as {{{my-elnode-editor.js}}} (in whatever directory the 405 | webserver is serving) and save the HTML in the same directory, call it 406 | {{{my-elnode-editor.html}}}, say? 407 | 408 | You don't even have to restart the Elnode handler, because it already 409 | is pointing to the dispatcher handler. If you just: 410 | 411 | {{{ 412 | M-x eval-buffer 413 | }}} 414 | 415 | this will re-evaluate the URL mappings. Now if you visit 416 | [[http://localhost:8002/my-elnode-editor.html]] you should see the 417 | webpage with the {{{textarea}}} and the text of your buffer. 418 | 419 | 420 | 421 | == That's all for now! == 422 | 423 | This is as far as Nic has got writing the tutorial. More will come soon I hope: 424 | 425 | * {{{defer}}} with an example based around the editor service 426 | * debugging a running Elnode service 427 | -------------------------------------------------------------------------------- /README.creole: -------------------------------------------------------------------------------- 1 | = Elnode = 2 | 3 | An evented IO webserver in Emacs Lisp. 4 | 5 | 6 | == Requirements == 7 | 8 | Elnode will not run properly on anything less than Emacs 24. Elnode 9 | requires Emacs 24's lexical binding as it makes extensive use of 10 | closures. 11 | 12 | 13 | == Rationale == 14 | 15 | Elnode is a great for these things: 16 | 17 | * nice simple server with few dependancies (just Emacs and {{{cat}}} basically) 18 | * prototyping webapps 19 | * browser testing 20 | * asynchronous apps, like chat apps 21 | 22 | 23 | == Installation == 24 | 25 | Elnode is packaged in [[http://marmalade-repo.org/packages/elnode|marmalade]]. 26 | 27 | For dealing with package repositories check out the 28 | [[http://www.emacswiki.org/emacs/ELPA|Emacs Wiki]] but the short version 29 | is to add the following to your {{{.emacs}}} or your 30 | {{{.emacs.d/init.el}}}: 31 | 32 | {{{ 33 | (add-to-list 34 | 'package-archives 35 | '("marmalade" . "http://marmalade-repo.org/packages/")) 36 | }}} 37 | 38 | And then do: 39 | 40 | {{{ 41 | M-x list-packages 42 | }}} 43 | 44 | find Elnode in the list and press {{{i}}} or {{{ENTER}}} to install it. 45 | 46 | If you don't want to use packages you can just install {{{elnode.el}}} 47 | on your {{{load-path}}} somewhere and: 48 | 49 | {{{ 50 | (require 'elnode) 51 | }}} 52 | 53 | === Install from this repository === 54 | 55 | You can make a package locally, with this repository: 56 | 57 | {{{ 58 | M-x compile [RET] make clean all [RET] 59 | }}} 60 | 61 | And then you can install that package: 62 | 63 | {{{ 64 | M-x package-install-file [RET] elnode-0.9.9.tar [RET] 65 | }}} 66 | 67 | You can also use the other Makefile targets, such as {{{test}}}. 68 | 69 | Unless you are developing Elnode I advise that you use the packaged 70 | version. 71 | 72 | == Out of the box == 73 | 74 | When Elnode initializes it automatically starts a webserver and a Wiki 75 | engine. 76 | 77 | If you: 78 | 79 | {{{ 80 | M-x customize-group 81 | elnode 82 | }}} 83 | 84 | you can alter a number of variables pertaining to the default 85 | configuration, including the directory used to keep files. 86 | 87 | By default the package installs files in your {{{.emacs.d}}} - it uses 88 | a directory called {{{elnode}}} for the Wiki root and the 89 | webroot. Both are configurable with Elnode config variables. 90 | 91 | You can also just ignore the built in stuff completely and write your 92 | own servers. 93 | 94 | 95 | == How does it work? == 96 | 97 | The simplest thing that Elnode does is let you start a webserver on a directory: 98 | 99 | {{{ 100 | M-x elnode-make-webserver [RET] 101 | Serve files from: [enter directory] [RET] 102 | TCP Port (try something over 8000): 8009 [RET] 103 | }}} 104 | 105 | and there will be a webserver started on port 8009 serving files from 106 | whatever directory you specified. 107 | 108 | Elnode's power is most visible to programmers though. 109 | 110 | You can define a handler function: 111 | 112 | {{{ 113 | (defun my-test-handler (httpcon) 114 | "Demonstration function" 115 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 116 | (elnode-http-return httpcon "HELLO!")) 117 | }}} 118 | 119 | And then start the server: 120 | 121 | {{{ 122 | (elnode-start 'my-test-handler :port 8010 :host "localhost") 123 | }}} 124 | 125 | You can also start the server interactively... with: 126 | 127 | {{{ 128 | M-x elnode-start 129 | }}} 130 | 131 | it interactively asks for the handler function and a port. 132 | 133 | === Stopping the server === 134 | 135 | If you can remember the port you started your server on then you'll be 136 | able to stop it, like: 137 | 138 | {{{ 139 | (elnode-stop 8010) 140 | }}} 141 | 142 | You can also stop interactively: 143 | 144 | {{{ 145 | M-x elnode-stop 146 | }}} 147 | 148 | 149 | 150 | == API == 151 | 152 | === Mapping paths to handlers === 153 | 154 | {{{elnode-hostpath-dispatcher}}} takes a-list of path/handler mappings: 155 | 156 | {{{ 157 | ##!emacs-lisp 158 | (defvar 159 | my-app-routes 160 | '(("^my-host.example.com//wiki/\\(.*\\)" . elnode-wikiserver) 161 | ("^admin.example.com//admintool/\\(.*\\)" . user-admin) 162 | ("^.*//\\(.*\\)" . elnode-webserver))) 163 | 164 | (defun root-handler (httpcon) 165 | (elnode-hostpath-dispatcher httpcon my-app-routes)) 166 | 167 | (elnode-start 'root-handler :port 8009) 168 | }}} 169 | 170 | This will create a server on port 8009 being handled by 171 | {{{root-handler}}} which will root the requests to the appropriate handler. 172 | 173 | Any request for the host {{{my-host.example.com}}} with the path 174 | {{{/wiki/}}} will be sent to the Elnode Wiki engine. 175 | 176 | Any request for the host {{{admin.example.com}}} with the path 177 | {{{/admintool/}}} will be sent to the {{{user-admin}}} handler, 178 | presumably that is defined somewhere. 179 | 180 | Any other request will be sent to the default Elnode webserver. 181 | 182 | Elnode itself uses a hostpath dispatcher on the default Elnode server. 183 | This can actually be configured with the variable 184 | {{{elnode-hostpath-default-table}}}, so you can actually change the 185 | default behaviour of the Elnode default server just with Emacs config. 186 | 187 | 188 | The use of regexs in Elnode's mapping is supported by other 189 | tools. Sub-expressions are capturable in mapping support routines such 190 | as {{{elnode-docroot-for}}}. 191 | 192 | When a handler is called by {{{elnode-hostpath-dispatcher}}} then the 193 | parts of the match are available through the function 194 | {{{elnode-http-mapping}}}. So we could code the {{{user-admin}}} 195 | handler like this: 196 | 197 | {{{ 198 | ##! emacs-lisp 199 | (defun user-admin (httpcon) 200 | (let ((username (elnode-http-mapping httpcon 1))) 201 | (user-admin-send-admin-page httpcon username))) 202 | }}} 203 | 204 | The {{{(elnode-http-mapping httpcon 1)}}} accesses the first 205 | sub-expression of the regex that caused the match: 206 | 207 | {{{ 208 | ("^admin.example.com//admintool/\\(.*\\)" . user-admin) 209 | }}} 210 | 211 | so, everything AFTER the {{{admintool/}}}. 212 | 213 | Some tools in Elnode do this for you, so you don't have to. Again, 214 | look at {{{elnode-docroot-for}}}. 215 | 216 | === Serving files === 217 | 218 | There are several helpers for serving files with Elnode. You can serve 219 | directories of files directly by making a webserver handler. A 220 | function {{{elnode-webserver-handler-maker}}} can make webservers: 221 | 222 | {{{ 223 | ##! emacs-lisp 224 | 225 | (setq my-webserver 226 | (elnode-webserver-handler-maker "~/my-webroot")) 227 | 228 | (elnode-start my-webserver :port 8010) 229 | }}} 230 | 231 | The Elnode webserver also produces index pages and can be configured 232 | with a number of variables: 233 | 234 | * {{{elnode-webserver-index-page-template}}} defines the page template used for the index 235 | * {{{elnode-webserver-index-file-template}}} defines the template for each file in the index, normally it's just an A tag ponting to the file. 236 | 237 | 238 | === More controlled serving === 239 | 240 | If you need more control over serving files you can write handlers 241 | with {{{elnode-docroot-for}}}. This does a lot of complex work for you 242 | to map a directory tree to a webserver namespace. 243 | 244 | This example shows how to use {{{elnode-docroot-for}}} 245 | 246 | {{{ 247 | ##! emacs-lisp 248 | 249 | (defun elnode-org-handler (httpcon) 250 | (elnode-docroot-for "~/work/org" 251 | with org-file 252 | on httpcon 253 | do (with-current-buffer (find-file-noselect org-file) 254 | (let ((org-html 255 | ;; This might throw errors so you could condition-case it 256 | (org-export-as-html 3 nil nil 'string))) 257 | (elnode-send-html httpcon org-html))))) 258 | }}} 259 | 260 | The first argument is the directory of files which you want to serve, 261 | then {{{with variable}}} specifies the name of a variable to use in 262 | the body of the code which will be bound to the filename of the file 263 | the user wants. Then {{{on httpcon}}} specifies the HTTP connection to 264 | use and then {{{do ....}}} specifies the code to use. 265 | 266 | {{{elnode-docroot-for}}} processes incomming requests on the 267 | {{{httpcon}}} you specify by checking the request matches a file in 268 | the directory you specify (it sends a 404 if it does not find one). 269 | 270 | It also does last modified caching on the file and sends an HTTP 304 271 | response if the file has not been updated since the last request. 272 | 273 | If a matching file exists and the it is not cached then 274 | {{{elnode-docroot-for}}} runs the {{{do}}} code to send the response 275 | correctly. 276 | 277 | === Sending files === 278 | 279 | Elnode also has {{{elnode-send-file}}} for sending files to the response, 280 | along with {{{elnode-docroot-for}}} this makes a powerful simple 281 | webserver tool. {{{elnode-send-file}}} can be used to send any 282 | arbitary file: 283 | 284 | {{{ 285 | ##! emacs-lisp 286 | (defun my-status-page (httpcon) 287 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 288 | (elnode-send-file httpcon "~/static-status-file.html")) 289 | }}} 290 | 291 | A handler that will only ever respond with one static file. Of 292 | course, this isn't very interesting, combined with 293 | {{{elnode-docroot-for}}} it can be used to serve directories and the 294 | like, or you could work out the filename to be sent with some other 295 | method. 296 | 297 | There is another use for {{{elnode-send-file}}} which is simple 298 | templating. You can pass parameters to {{{elnode-send-file}}} and it 299 | will template them into the file: 300 | 301 | {{{ 302 | (defun my-templater(httpcon) 303 | (let ((hash (make-hash-table 304 | :test 'equal 305 | :data "username" "nicferrier"))) 306 | (elnode-http-start httpcon 200 '("Content-type" . "text/html")) 307 | (elnode-send-file 308 | httpcon "~/my-template.html" 309 | :replacements hash))) 310 | }}} 311 | 312 | The template file must have sections marked up like: 313 | 314 | <> 317 | 318 | for each of the variables. 319 | 320 | This makes for simple but quite powerful templating. 321 | 322 | === Really Really simple file sending === 323 | 324 | It's also possible to make send file functions automatically so if you 325 | want to map a handler that serves just one file in a dispatcher that's 326 | possible: 327 | 328 | {{{ 329 | ##! emacs-lisp 330 | `(("^my-host.example.com//wiki/\\(.*\\)" . elnode-wikiserver) 331 | ("^.*//styles.css" . ,(elnode-make-send-file "~/mainstyles.css")) 332 | ("^.*//\\(.*\\)" . elnode-webserver)) 333 | }}} 334 | 335 | It's also possible to use templating with this style of programming by 336 | passing a function returning the alist variable map as 337 | {{{:replacements}}}: 338 | 339 | {{{ 340 | ##! emacs-lisp 341 | (defun my-templater () 342 | '(("username" . "william occam"))) 343 | 344 | `(("^my-host.example.com//wiki/\\(.*\\)" . elnode-wikiserver) 345 | ("^.*//styles.css" . ,(elnode-make-send-file 346 | "~/mainstyles.css" 347 | :replacements 'my-templater)) 348 | ("^.*//\\(.*\\)" . elnode-webserver)) 349 | }}} 350 | 351 | This makes templating and setting up very simple websites very easy 352 | indeed. 353 | 354 | === Accessing data in the HTTP request === 355 | 356 | There are a bunch of functions that do what you would expect about 357 | data in the HTTP request: 358 | 359 | {{{ 360 | ##! emacs-lisp 361 | 362 | (elnode-http-method httpcon) 363 | => "POST" 364 | 365 | (elnode-http-pathinfo httpcon) 366 | => "/wiki/blah.creole" 367 | 368 | (elnode-http-query httpcon) 369 | => "a=10&b=20&c=the+quick+brown+fox" 370 | 371 | (elnode-http-params httpcon) 372 | => (("a" . "10")("b" . "20")("c" . "the quick brown fox")) 373 | 374 | (elnode-http-param httpcon "username") 375 | => "nicferrier" 376 | 377 | (elnode-http-cookie httpcon "session-id") 378 | => "1213313" 379 | 380 | (elnode-http-header httpcon "Date") 381 | => "Mon, Feb 27 2012 22:10:21 GMT" 382 | 383 | (elnode-http-header httpcon 'date) 384 | => "Mon, Feb 27 2012 22:10:21 GMT" 385 | 386 | (elnode-http-header httpcon 'date :time) ;; with convert flag set to :time 387 | => (20299 65357) 388 | }}} 389 | 390 | Note that Elnode generally can accept symbol's as well as strings to 391 | name things, if it can't it's a bug, 392 | [[https://github.com/nicferrier/elnode/issues|please report it]]. 393 | 394 | Also, Elnode can handle some conversions sometimes. I would like to 395 | respond to user demand about when and where to do that and what to 396 | do. Please give me feedback. 397 | 398 | === Elnode's raw data === 399 | 400 | Elnode stores most of it's internal state on the connection object and 401 | it's all accessible, interesting properties and how to access them: 402 | 403 | {{{ 404 | ##! emacs-lisp 405 | 406 | (process-get httpcon :elnode-http-status) 407 | => "GET / HTTP/1.1" 408 | 409 | (process-get httpcon :elnode-http-resource) 410 | => "/" 411 | 412 | (process-get httpcon :elnode-http-version) 413 | => "1.1" 414 | }}} 415 | 416 | These are not supported by Elnode at all, there is no guarantee that 417 | the names of these properties won't change. If you feel that you want 418 | official support (ie: a function) then make an issue on the Elnode 419 | github. 420 | 421 | 422 | == To Do? == 423 | 424 | If you're playing with elnode but you can't think of anything to do with it... 425 | 426 | * an elpa repository written with elnode 427 | ** turn the package list into html 428 | ** allow packages to be downloaded from elnode 429 | ** upload of packages will require fixing the request management a little 430 | * an emacsclient with elnode 431 | ** write a command line client that submits data to the server over HTTP 432 | ** it should interact with the emacs user in the same way that emacs server does 433 | ** //why?// because then a single Emacs could have just 1 server socket open for all sorts of different roles 434 | * alter {{{elnode-webserver-handler-maker}}} to do indexing better 435 | ** take an optional index producing function? 436 | ** take keyword flags that set the behaviour? 437 | ** eg: {{{:doindexes 't }}} 438 | * browse-current-buffer 439 | ** start an elnode server on some random port exposing the current buffer 440 | ** automatically open a browser on the started server 441 | -------------------------------------------------------------------------------- /elnode-wiki.el: -------------------------------------------------------------------------------- 1 | ;;; elnode-wiki.el --- a wiki with Elnode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010, 2011, 2012 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Maintainer: Nic Ferrier 7 | ;; Created: 5th October 2010 8 | ;; Keywords: lisp, http, hypermedia 9 | 10 | ;; This file is NOT part of GNU Emacs. 11 | 12 | ;; This program 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 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program 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 this program. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; 27 | ;; This is a Wiki Engine completely written in EmacsLisp, using Elnode 28 | ;; as a server. 29 | ;; 30 | ;;; Source code 31 | ;; 32 | ;; elnode's code can be found here: 33 | ;; http://github.com/nicferrier/elnode 34 | 35 | ;;; Style note 36 | ;; 37 | ;; This codes uses the Emacs style of: 38 | ;; 39 | ;; elnode-wiki--private-function 40 | ;; 41 | ;; for private functions. 42 | 43 | 44 | ;;; Code: 45 | 46 | (require 'elnode) 47 | (require 'db) 48 | (eval-when-compile 'fakir) 49 | (require 'creole nil 't) 50 | ;;(require 'vc) 51 | 52 | (defgroup elnode-wikiserver nil 53 | "A Wiki server written with Elnode." 54 | :group 'elnode) 55 | 56 | ;;;###autoload 57 | (defconst elnode-wikiserver-wikiroot-default 58 | (expand-file-name (concat elnode-config-directory "wiki/")) 59 | "The default location of the wiki root. 60 | 61 | This is used to detect whether elnode needs to create this 62 | directory or not.") 63 | 64 | ;;;###autoload 65 | (defcustom elnode-wikiserver-wikiroot 66 | elnode-wikiserver-wikiroot-default 67 | "The root for the Elnode wiki files. 68 | 69 | This is where elnode-wikiserver serves wiki files from." 70 | :type '(directory) 71 | :group 'elnode-wikiserver) 72 | 73 | (defcustom elnode-wikiserver-body-header 74 | "
    " 75 | "HTML BODY preamable of a rendered Wiki page." 76 | :type '(string) 77 | :group 'elnode-wikiserver) 78 | 79 | (defcustom elnode-wikiserver-body-footer 80 | "" 93 | "HTML BODY footter for a rendered Wiki page." 94 | :type '(string) 95 | :group 'elnode-wikiserver) 96 | 97 | (defcustom elnode-wikiserver-body-footer-not-loggedin 98 | "" 101 | "HTML BODY footter for a rendered Wiki page." 102 | :type '(string) 103 | :group 'elnode-wikiserver) 104 | 105 | (defun elnode-wiki--setup () 106 | "Setup the wiki." 107 | (elnode--dir-setup elnode-wikiserver-wikiroot 108 | elnode-wikiserver-wikiroot-default 109 | "default-wiki-index.creole" 110 | "index.creole")) 111 | 112 | (ert-deftest elnode-wiki--setup () 113 | "Test the wiki setup function." 114 | ;; Test that it's not called if we can't find the source file 115 | (let (called) 116 | (flet ((make-directory (dirname &optional parents) 117 | (setq called t)) 118 | ;; We fake buffer-file-name so that the wiki-index-source 119 | ;; will not be found 120 | (buffer-file-name () 121 | "/tmp/elnode/elnode-wiki.el")) 122 | (elnode-wiki--setup) 123 | (should-not called))) 124 | ;; Test that when called we're going to copy things right 125 | (let (make-dir 126 | copy-file 127 | ;; Ensure the configurable wikiroot is set to the default 128 | (elnode-wikiserver-wikiroot elnode-wikiserver-wikiroot-default)) 129 | (flet ((make-directory (dirname &optional parents) 130 | (setq make-dir (list dirname parents))) 131 | (dired-copy-file (from to ok-flag) 132 | (setq copy-file (list from to ok-flag))) 133 | ;; Mock the source filename environment 134 | (buffer-file-name () 135 | "/tmp/elnode--wiki-setup-test/elnode-wiki.el") 136 | (file-exists-p (filename) 137 | (equal 138 | filename 139 | "/tmp/elnode--wiki-setup-test/default-wiki-index.creole"))) 140 | (elnode-wiki--setup) 141 | (should 142 | (equal 143 | (list 144 | ;; This is the dir we should make 145 | '("/home/nferrier/.emacs.d/elnode/wiki/" t) 146 | ;; This is the copy file spec 147 | '("/tmp/elnode--wiki-setup-test/default-wiki-index.creole" 148 | "/home/nferrier/.emacs.d/elnode/wiki/index.creole" 149 | nil)) 150 | ;; So this is the directory that make-directory will create 151 | ;; and the copy-file spec 152 | (list make-dir copy-file)))))) 153 | 154 | (defun elnode--wiki-call (out-buf page-text page) 155 | "Call a wiki page sending output OUT-BUF. 156 | 157 | The page is faked with PAGE-TEXT." 158 | (flet 159 | ((elnode--worker-lisp-helper (child-lisp) 160 | `((progn 161 | (require 'creole) 162 | (require 'cl) 163 | (flet ((creole--get-file (filename) 164 | (let ((buf (get-buffer-create "wikibuf"))) 165 | (with-current-buffer buf 166 | (insert ,page-text)) 167 | buf))) 168 | ,@child-lisp))))) 169 | (elnode-wait-for-exit 170 | (elnode-worker-elisp 171 | out-buf 172 | ((target page) 173 | (page-info page) 174 | (header elnode-wikiserver-body-header) 175 | (footer elnode-wikiserver-body-footer)) 176 | (require 'creole) 177 | (creole-wiki 178 | target 179 | :destination t 180 | :variables `((page . ,page-info)) 181 | :body-header header 182 | :body-footer footer))))) 183 | 184 | ;; Deprecated 185 | (defun elnode-wiki-send (httpcon wikipage &optional pageinfo) 186 | "Sends the WIKIPAGE to the HTTPCON. 187 | 188 | If PAGEINFO is specified it's the HTTP path to the Wiki page. 189 | 190 | Uses Elnode's worker elisp stuff which is now deprecated." 191 | (elnode-http-start httpcon 200 `("Content-type" . "text/html")) 192 | (let ((page (or pageinfo (elnode-http-pathinfo httpcon)))) 193 | (elnode-worker-elisp 194 | httpcon 195 | ((target wikipage) 196 | (page-info page) 197 | (header elnode-wikiserver-body-header) 198 | (footer elnode-wikiserver-body-footer)) 199 | (require 'creole) 200 | (creole-wiki 201 | target 202 | :destination t 203 | :variables `((page . ,page-info)) 204 | :body-header header 205 | :body-footer footer)))) 206 | 207 | (define-obsolete-function-alias 208 | 'elnode-wiki-send 209 | 'elnode-wiki-page 210 | "24.1" 211 | "The worker elisp code that this depends on is deprecated in 212 | favour of Enode RLE.") 213 | 214 | (defvar elnode-wiki-page-use-rle nil 215 | "Whether to use RLE for this wiki or not. 216 | 217 | The RLE stuff is not really stable yet so this is a switch that 218 | let's developers play with but does not affect use.") 219 | 220 | (defun elnode-wiki-page-rle (httpcon wikipage &optional pageinfo) 221 | "Creole render the WIKIPAGE to the HTTPCON. 222 | 223 | If PAGEINFO is specified it's the HTTP path to the Wiki page. 224 | 225 | This version uses RLE which renders the Wiki page in a child 226 | Emacs." 227 | (let ((authenticated (elnode-http-cookie httpcon "elnodeauth"))) 228 | (let ((page-info (or pageinfo (elnode-http-pathinfo httpcon))) 229 | (header elnode-wikiserver-body-header) 230 | (footer (if authenticated 231 | elnode-wikiserver-body-footer 232 | elnode-wikiserver-body-footer-not-loggedin))) 233 | (elnode-async-do 234 | httpcon 235 | requires (creole elnode) 236 | with-environment ((target wikipage) 237 | (page-info page-info) 238 | (header header) 239 | (footer footer)) 240 | do 241 | (creole-wiki 242 | target 243 | :destination t 244 | :variables `((page . ,page-info)) 245 | :body-header header 246 | :body-footer footer))))) 247 | 248 | (defun elnode-wiki-page (httpcon wikipage &optional pageinfo) 249 | "Creole render a WIKIPAGE back to the HTTPCON." 250 | (if elnode-wiki-page-use-rle 251 | (elnode-wiki-page-rle httpcon wikipage pageinfo) 252 | ;; Otherwise just do it 253 | (elnode-http-start httpcon 200 `("Content-type" . "text/html")) 254 | (with-stdout-to-elnode httpcon 255 | (let ((page-info (or pageinfo (elnode-http-pathinfo httpcon))) 256 | (header elnode-wikiserver-body-header) 257 | (footer (if-elnode-auth httpcon 'elnode-wiki-auth 258 | elnode-wikiserver-body-footer 259 | elnode-wikiserver-body-footer-not-loggedin))) 260 | (creole-wiki 261 | wikipage 262 | :destination t 263 | :variables (list (cons 'page page-info)) 264 | :body-header header 265 | :body-footer footer))))) 266 | 267 | (defun elnode-wiki--text-param (httpcon) 268 | "Get the text param from HTTPCON and convert it." 269 | (replace-regexp-in-string 270 | "\r" "" ; browsers send text in DOS line ending format 271 | (elnode-http-param httpcon "wikitext"))) 272 | 273 | (defun elnode-wiki--save-request (httpcon wikiroot path text) 274 | "Process an update request." 275 | (let* ((page (if path 276 | (save-match-data 277 | (string-match "/wiki/\\(.*\\)$" path) 278 | (match-string 1 path)))) 279 | (comment (elnode-http-param httpcon "comment")) 280 | (file-name (if (equal page "") 281 | (concat wikiroot "index.creole") 282 | (concat (file-name-as-directory wikiroot) page))) 283 | (buffer (find-file-noselect file-name))) 284 | (with-current-buffer buffer 285 | (erase-buffer) 286 | (insert text) 287 | (save-buffer) 288 | (let ((git-buf 289 | (get-buffer-create 290 | (generate-new-buffer-name 291 | "* elnode wiki commit buf *")))) 292 | (shell-command 293 | (format "git commit -m '%s' %s" comment file-name) 294 | git-buf) 295 | (kill-buffer git-buf)) 296 | (elnode-wiki-page httpcon file-name)))) 297 | 298 | (defun elnode-wiki-handler (httpcon wikiroot) 299 | "A low level handler for Wiki operations. 300 | 301 | Send the Wiki page requested, which must be a file existing under 302 | the WIKIROOT, back to the HTTPCON. 303 | 304 | Update operations are protected by authentication." 305 | (elnode-method httpcon 306 | (GET 307 | (elnode-docroot-for wikiroot 308 | with target-path 309 | on httpcon 310 | do 311 | (if (equal target-path (expand-file-name (concat wikiroot "/"))) 312 | (elnode-wiki-page httpcon (concat wikiroot "/index.creole")) 313 | (elnode-wiki-page httpcon target-path)))) 314 | (POST 315 | (with-elnode-auth httpcon 'elnode-wiki-auth 316 | (let* ((path (elnode-http-pathinfo httpcon)) 317 | (text (elnode-wiki--text-param httpcon))) 318 | (if (not (elnode-http-param httpcon "preview")) 319 | ;; A save request in which case save the new text and then 320 | ;; send the wiki text. 321 | (elnode-wiki--save-request httpcon wikiroot path text) 322 | ;; Might be a preview request in which case send back the WIKI 323 | ;; text that's been sent. 324 | (with-temp-file "/tmp/preview" 325 | (insert text)) 326 | (elnode-wiki-send httpcon "/tmp/preview" path))))))) 327 | 328 | ;;;###autoload 329 | (defun elnode-wikiserver-test () 330 | "Test whether we should serve Wiki or not." 331 | (featurep 'creole)) 332 | 333 | ;;;###autoload 334 | (define-elnode-handler elnode-wikiserver (httpcon) 335 | "Serve Wiki pages from `elnode-wikiserver-wikiroot'. 336 | 337 | HTTPCON is the request. 338 | 339 | The Wiki server is only available if the `creole' package is 340 | provided. Otherwise it will just error." 341 | (if (not (elnode-wikiserver-test)) 342 | (elnode-send-500 httpcon "The Emacs feature 'creole is required.") 343 | (elnode-wiki--setup) 344 | (elnode-wiki-handler httpcon elnode-wikiserver-wikiroot))) 345 | 346 | (defvar elnode-wiki-db 347 | (db-make 348 | `(db-hash 349 | :filename 350 | ,(expand-file-name 351 | (concat elnode-config-directory "elnode-wiki-auth"))))) 352 | 353 | ;; Define the authentication scheme for the wiki 354 | (elnode-auth-define-scheme 355 | 'elnode-wiki-auth 356 | :auth-db elnode-wiki-db 357 | :redirect (elnode-auth-make-login-wrapper 358 | 'elnode-wikiserver 359 | :target "/wiki/login/")) 360 | 361 | 362 | ;;; Tests 363 | 364 | (ert-deftest elnode-wiki-page () 365 | "Full stack Wiki test." 366 | (with-elnode-mock-server 367 | ;; The dispatcher function 368 | (lambda (httpcon) 369 | (let ((elnode-wikiserver-wikiroot "/home/elnode/wiki")) 370 | (elnode-hostpath-dispatcher 371 | httpcon 372 | '(("[^/]*//wiki/\\(.*\\)" . elnode-wikiserver))))) t 373 | (fakir-mock-file (fakir-file 374 | :filename "test.creole" 375 | :directory "/home/elnode/wiki" 376 | :content "= Hello World =\nthis is a creole wiki file!\n") 377 | (let* ((elnode--do-error-logging nil) 378 | (elnode--do-access-logging-on-dispatch nil)) 379 | (should-elnode-response 380 | (elnode-test-call "/wiki/test.creole") 381 | :status-code 200 382 | :body-match ".*

    Hello World

    .*"))))) 383 | 384 | (provide 'elnode-wiki) 385 | 386 | ;;; elnode-wiki.el ends here 387 | -------------------------------------------------------------------------------- /elnode-rle.el: -------------------------------------------------------------------------------- 1 | ;;; elnode-rle.el --- Remote Lisp Executiion with Elnode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2012 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Keywords: lisp, hypermedia, processes 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 3 of the License, or 11 | ;; (at your option) 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 this program. If not, see . 20 | 21 | ;;; Commentary: 22 | ;; 23 | ;; This is an elnode handler and tools for doing asynchrous 24 | ;; programming. 25 | ;; 26 | ;; The idea is that you can setup associated child processes and pass 27 | ;; them work to do and receive their output over HTTP. 28 | 29 | ;;; Code: 30 | 31 | (require 'elnode) 32 | (require 'web) 33 | (require 'loadhist) 34 | (require 'server) 35 | 36 | (defun elnode-rle--handler (httpcon) 37 | "Remote Lisp Evaluator handler. 38 | 39 | This can be spawned in a client to allow any lisp code to be 40 | passed over the client-server link." 41 | (let* ((lisp-to-run (elnode-http-param httpcon "lisp")) 42 | (lisp 43 | (if lisp-to-run 44 | (car (read-from-string lisp-to-run)))) 45 | (bindings-to-use (elnode-http-param httpcon "bindings")) 46 | (bindings 47 | (if bindings-to-use 48 | (car (read-from-string bindings-to-use)))) 49 | (to-eval (list 'let bindings lisp))) 50 | (elnode-http-start httpcon 200 '("Content-type" . "text/plain")) 51 | (let ((nomessage t)) 52 | (with-stdout-to-elnode httpcon 53 | (eval to-eval))))) 54 | 55 | (ert-deftest elnode-rle--handler () 56 | "Test the Remote Lisp Evaluator handler." 57 | (flet ((lisp-encode (param lisp) 58 | (cons param (format "%S" lisp))) 59 | (do-test (lisp bindings) 60 | (fakir-mock-process 61 | :httpcon 62 | ((:elnode-http-params (list lisp bindings))) 63 | (elnode-rle--handler :httpcon) 64 | (with-current-buffer (process-buffer :httpcon) 65 | (goto-char (point-min)) 66 | ;; Find the header end. 67 | (re-search-forward "\r\n\r\n" nil 't) 68 | (buffer-substring (point) (point-max)))))) 69 | (should 70 | (equal 71 | ;; Match the content transfer encoded 72 | "c\r\nhello world!\r\n0\r\n\r\n" 73 | (let* 74 | ((lisp (lisp-encode 75 | "lisp" '(let ((a "hello world!")) (princ a)))) 76 | (bindings (lisp-encode 77 | "bindings" '((a 10)(b 20))))) 78 | (do-test lisp bindings)))) 79 | (should 80 | (equal 81 | "2\r\n30\r\n0\r\n\r\n" 82 | (let* 83 | ((lisp (lisp-encode 84 | "lisp" '(let ((a (+ b 10))) (princ a)))) 85 | (bindings (lisp-encode 86 | "bindings" '((a 10)(b 20))))) 87 | (do-test lisp bindings)))))) 88 | 89 | (defvar elnode-rle--servers (make-hash-table :test 'equal) 90 | "The hash of RLE servers available.") 91 | 92 | (defun elnode-rle--load-path-ize (lisp) 93 | "Wrap LISP in the current load-path." 94 | (concat 95 | ;; There is a very strange thing with sending lisp to 96 | ;; (read) over a piped stream... (read) can't cope with 97 | ;; multiple lines; so we encode newline here. 98 | ;;(replace-regexp-in-string 99 | ;; "\n" 100 | ;; "\\\\n" 101 | (format "(progn (setq load-path (quote %S)) %s)" 102 | (append (list default-directory) load-path) 103 | lisp))) 104 | 105 | (defun elnode-rle--handler-lisp (to-require) 106 | "Return a file with Lisp to start Elnode with TO-REQUIRE. 107 | 108 | Used to construct the lisp to send. You're unlikely to need to 109 | override this at all, the function is just here to make the 110 | implementation easier to debug. 111 | 112 | TO-REQUIRE is a list of things to require, currently only 1 is 113 | allowed." 114 | (let ((temp-file 115 | (make-temp-file 116 | (format "elnode-rle-%s" (symbol-name to-require))))) 117 | (with-temp-file temp-file 118 | (insert 119 | (elnode-rle--load-path-ize 120 | (format "(progn 121 | (setq elnode-do-init nil) 122 | (setq elnode--do-error-logging nil) 123 | (require (quote %s)) 124 | (require (quote elnode-rle)) 125 | (toggle-debug-on-error) 126 | (setq elnode-rle-port (elnode-find-free-service)) 127 | (elnode-start 'elnode-rle--handler :port elnode-rle-port) 128 | (print (format \"\\nelnode-port=%%d\\n\" port)))" 129 | to-require)))) 130 | temp-file)) 131 | 132 | (defun elnode-rle--httpcon-mapper (client-header 133 | client-data 134 | elnode-httpcon 135 | &optional end-callback) 136 | "Elnode specific client connection to HTTP connection mapper. 137 | 138 | Maps client async data responses to an elnode server response." 139 | (unless (process-get elnode-httpcon :elnode-rle-header-sent) 140 | (elnode-http-start 141 | elnode-httpcon 142 | (gethash 'status-code client-header)) 143 | (process-put elnode-httpcon :elnode-rle-header-sent t)) 144 | (if (eq client-data :done) 145 | (elnode-http-return elnode-httpcon) ; return if we're done 146 | ;; Else just send the data 147 | (elnode-http-send-string elnode-httpcon client-data))) 148 | 149 | (defun elnode-rle--client-data-mapper (con header data stream end-callback) 150 | "Recevies data from the RLE server and sends it to the STREAM. 151 | 152 | END-CALLBACK is to be called when the client sees EOF." 153 | (cond 154 | ((processp stream) ; this should really elnode-http-p 155 | (elnode-rle--httpcon-mapper header data stream end-callback)) 156 | ((bufferp stream) 157 | (if (not (eq data :done)) 158 | (with-current-buffer stream 159 | (save-excursion 160 | (goto-char (point-max)) 161 | (insert data))) 162 | ;; Process is done. 163 | (and (functionp end-callback) 164 | (funcall end-callback header)))))) 165 | 166 | (defun elnode-rle--call-mapper (data-to-send stream port 167 | &optional end-callback) 168 | "Make a client call to PORT mapping response to STREAM. 169 | 170 | When it finishes, call END-CALLBACK, if present, with the header." 171 | (web-http-post 172 | (lambda (con header data) 173 | (elnode-rle--client-data-mapper 174 | con 175 | header 176 | data 177 | stream 178 | end-callback)) 179 | "/" 180 | :host "localhost" 181 | :port port 182 | :data data-to-send 183 | :mime-type "application/x-elnode" 184 | :mode 'stream)) 185 | 186 | (defun elnode-rle--make-server (to-require) 187 | "Make an RLE server, a child Emacs running the RLE handler. 188 | 189 | Return a proc that represents the child process. The child 190 | process has a property `:exec' which is a function that calls the 191 | RLE handler in the child's Elnode server (waiting for the server 192 | to start first and provide the relevant port) by calling 193 | `elnode-rle-call-mapper' with the stream from the `:exec' call 194 | and the child's remote HTTP port. 195 | 196 | The `:exec' proc will signal `elnode-rle-child-port' if the child 197 | server does not start properly." ; yes. I know it's bloody complicated. 198 | (let* ((proc-buffer 199 | (get-buffer-create 200 | (format "* %s *" "thingy"))) 201 | (emacsrun 202 | "/usr/bin/emacs -Q --daemon=elnode-debugit") 203 | (proc 204 | (start-process-shell-command 205 | "elnode-rle-server" 206 | proc-buffer 207 | emacsrun)) 208 | (file-of-lisp 209 | (elnode-rle--handler-lisp 210 | to-require))) 211 | ;; Start elnode in it 212 | (server-eval-at "elnode-debugit" `(load-file ,file-of-lisp)) 213 | (process-put proc :daemonhandle "elnode-debugit") 214 | (process-put 215 | proc 216 | :port 217 | (server-eval-at 218 | (process-get proc :daemonhandle) 219 | 'elnode-rle-port)) 220 | ;; Collect the port from the remote Emacs 221 | ;; - FIXME this should also collect the secure token 222 | (set-process-filter 223 | proc 224 | (lambda (proc data) 225 | ;; Optional delay for test reasons 226 | (with-current-buffer (process-buffer proc) 227 | (save-excursion 228 | (goto-char (point-max)) 229 | (insert data))))) 230 | ;; Make a handler to call the server 231 | (process-put 232 | proc :exec 233 | (lambda (data stream &optional end-callback) 234 | (let ((ephemeral-port (process-get proc :port))) 235 | (elnode-rle--call-mapper data stream ephemeral-port end-callback)))) 236 | proc)) 237 | 238 | (defun elnode-rle--sender (stream to-require bindings body 239 | &optional end-callback) 240 | "Make a call using a client to the RLE server elsewhere. 241 | 242 | The RLE server is reused over TO-REQUIRE, if it's not already 243 | existing, it is created." 244 | (let ((server (gethash to-require elnode-rle--servers))) 245 | ;; Make the server if we don't have it 246 | (unless server 247 | (setq server 248 | (puthash to-require 249 | (elnode-rle--make-server (car to-require)) 250 | elnode-rle--servers))) 251 | ;; Now make the call to the server 252 | (let ((data (make-hash-table :test 'equal))) 253 | (puthash "bindings" (format "%S" bindings) data) 254 | (puthash "lisp" (format "%S" body) data) 255 | (let ((client-connection 256 | (funcall 257 | (process-get server :exec) 258 | data 259 | stream 260 | end-callback))) 261 | ;; If we're streaming to elnode then we need to mark the connection 262 | (when (processp stream) 263 | (process-put 264 | stream 265 | :elnode-child-process 266 | client-connection)))))) 267 | 268 | (defvar elnode-rle--async-do-end-callback nil 269 | "Used by `elnode-async-do' as the source of an end-callback. 270 | 271 | This is just used by tests for end signalling.") 272 | 273 | (defmacro elnode-async-do (stream 274 | requires requirements 275 | with-environment bindings 276 | do &rest body) 277 | "Execute the BODY in a remote Emacs. 278 | 279 | The STREAM is used to handle any output. 280 | 281 | The REQUIREMENTS is a list of provide symbol names that will be 282 | used to establish the right environment in the remote. 283 | 284 | The BINDINGS are also sent to the remote. 285 | 286 | TODO 287 | 288 | security for the remote using the stored key." 289 | (assert (eq with-environment 'with-environment)) 290 | (assert (eq requires 'requires)) 291 | (assert (eq do 'do)) 292 | (let ((bodyv (make-symbol "body")) 293 | (bindsv (make-symbol "binds")) 294 | (streamv (make-symbol "streamv")) 295 | (requirev (make-symbol "providing"))) 296 | `(let* ((,streamv ,stream) 297 | (,bodyv (quote (progn ,@body))) 298 | (,bindsv (list 299 | ,@(loop for p in bindings 300 | collect 301 | (if (and p (listp p)) 302 | (list 'list `(quote ,(car p)) (cadr p)) 303 | (list 'cons `,p nil))))) 304 | (,requirev (quote ,requirements))) 305 | (elnode-rle--sender 306 | ,streamv ,requirev ,bindsv ,bodyv 307 | elnode-rle--async-do-end-callback)))) 308 | 309 | (defmacro with-elnode-rle-wait (&rest body) 310 | "Simplify the wait for RLE; for testers." 311 | `(unwind-protect 312 | (let (ended) 313 | (progn 314 | ,@body) 315 | (while (not ended) (sit-for 1))) 316 | ;; FIXME - can we get to the name of this? 317 | (server-eval-at "elnode-debugit" '(kill-emacs)))) 318 | 319 | (ert-deftest elnode-rle--make-server () 320 | "Test making an RLE server. 321 | 322 | Do it all 3 ways: directly with the `elnode-rle-make-server', 323 | with the `elnode-rle--sender' function and finally with the user 324 | facing macro `elnode-async-do'. 325 | 326 | The output from the RLE call is collected in a buffer 327 | and tested." 328 | (flet ((make-hash (bindings) 329 | (let ((h (make-hash-table :test 'equal))) 330 | (loop for b in bindings 331 | do (puthash (car b) (cadr b) h)) 332 | h))) 333 | ;; Do it RAW 334 | (should 335 | (equal 336 | "hello" 337 | (with-temp-buffer 338 | (let* ((child-proc (elnode-rle--make-server 'elnode)) 339 | (daemon-handler (process-get child-proc :daemonhandle)) 340 | (collect-buf (current-buffer))) 341 | (with-elnode-rle-wait 342 | (funcall 343 | (process-get child-proc :exec) 344 | (make-hash '(("bindings" "((a \"hello\"))") 345 | ("lisp" "(princ \"hello\")"))) 346 | (current-buffer) 347 | (lambda (hdr) ; the end proc 348 | (setq ended t)))) 349 | (buffer-substring (point-min) (point-max)))))) 350 | ;; Do it via the sender func 351 | (should 352 | (equal 353 | "40" 354 | (with-temp-buffer 355 | (with-elnode-rle-wait 356 | (let ((elnode-rle--servers (make-hash-table :test 'equal))) 357 | (elnode-rle--sender 358 | (current-buffer) 359 | '(elnode) 360 | '((a 10) (b 20)) 361 | '(let ((c 30))(princ (+ c a))) 362 | (lambda (header) 363 | (message "elnode-rle: all done!")(setq ended t))))) 364 | (buffer-substring (point-min) (point-max))))) 365 | ;; Do it with the macro 366 | (should 367 | (equal 368 | "hello" 369 | (with-temp-buffer 370 | (with-elnode-rle-wait 371 | (let ((elnode-rle--servers (make-hash-table :test 'equal)) 372 | (elnode-rle--async-do-end-callback 373 | (lambda (header) 374 | (message "elnode-rle: in the dyn bound callback!") 375 | (setq ended t)))) 376 | (elnode-async-do 377 | (current-buffer) 378 | requires (elnode enode-rle) 379 | with-environment ((a 10)(b 20)) 380 | do (princ "hello")))) 381 | (buffer-substring (point-min) (point-max))))))) 382 | 383 | (provide 'elnode-rle) 384 | 385 | ;; elnode-rle ends here 386 | -------------------------------------------------------------------------------- /examples/chat.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * snack.js (c) Ryan Florence 3 | * https://github.com/rpflorence/snack 4 | * MIT License 5 | * Inspiration and code adapted from 6 | * MooTools (c) Valerio Proietti MIT license 7 | * jQuery (c) John Resig Dual license MIT or GPL Version 2 8 | * contentLoaded (c) Diego Perini MIT License 9 | * Zepto.js (c) Thomas Fuchs MIT License 10 | */ 11 | typeof Object.create!="function"&&(Object.create=function(a){function b(){}b.prototype=a;return new b}),!function(a){var b=a.snack={},c=0,d=Object.prototype.toString,e=[].indexOf,f=[].push;b.extend=function(){if(arguments.length==1)return b.extend(b,arguments[0]);var a=arguments[0];for(var c,d=1,e=arguments.length;d=200&&a.status<300?[!1,a.xhr.responseText||"",a.xhr.responseXML]:[a.status];a.callback.apply(a,f)}},setHeader:function(a,b){this.headers[a]=b;return this},getHeader:function(a){try{return this.xhr.getResponseHeader(a)}catch(b){return null}},send:function(){var b=this,d=b.options;if(b.running)return b;b.running=!0;var e=d.data||"",f=String(d.url),g=d.method.toLowerCase();typeof e!="string"&&(e=a.toQueryString(e));if(d.emulation&&a.indexOf(g,["get","post"])<0){var h="_method="+g;e=e?h+"&"+e:h,g="post"}if(d.urlEncoded&&a.indexOf(g,["post","put"])>-1){var i=d.encoding?"; charset="+d.encoding:"";b.headers["Content-type"]="application/x-www-form-urlencoded"+i}f||(f=c.location.pathname);var j=f.lastIndexOf("/");j>-1&&(j=f.indexOf("#"))>-1&&(f=f.substr(0,j)),e&&g=="get"&&(f+=(f.indexOf("?")>-1?"&":"?")+e,e=null);var k=b.xhr;k.open(g.toUpperCase(),f,open.async,d.user,d.password),d.user&&"withCredentials"in k&&(k.withCredentials=!0),k.onreadystatechange=a.bind(b.onStateChange,b);for(var l in b.headers)try{k.setRequestHeader(l,b.headers[l])}catch(m){d.exception.apply(b,[l,b.headers[l]])}k.send(e),d.async||b.onStateChange();return b},cancel:function(){var a=this;if(!a.running)return a;a.running=!1;var b=a.xhr;b.abort(),b.onreadystatechange=e,a.xhr=new d;return a}}}(snack,window,document),!function(a,b){function d(b,c,d,e){var f=b.data(d);f&&a.each(f,function(a){a[c].apply(b,e)});return b}function c(a){return a.replace(/\s+/g," ").replace(/^\s+|\s+$/g,"")}a.wrap.define({data:function(){var a={};return function(b,c){var d=a[this.id];d||(d=a[this.id]={});if(c===void 1)return d[b];return d[b]=c}}(),each:function(b,c){return a.each(this,b,c)},addClass:function(a){return this.each(function(b){c(b.className).indexOf(a)>-1||(b.className=c(b.className+" "+a))})},removeClass:function(a){return this.each(function(b){b.className=b.className.replace(new RegExp("(^|\\s)"+a+"(?:\\s|$)"),"$1")})},attach:function(b,c,d){var e=b.split("."),f=[];e[1]&&(f=this.data(e[1])||[]),this.each(function(b){var g={node:b,event:e[0]};d&&(g.delegate=d),f.push(a.listener(g,c))}),e[1]&&this.data(e[1],f);return this},detach:function(a){d(this,"detach",a,null,!0),this.data(a,null);return this},fire:function(a,b){return d(this,"fire",a,b)},delegate:function(a,b,c){return this.attach(a,c,b)}})}(snack,document); 12 | 13 | /*! 14 | * @preserve Qwery - A Blazing Fast query selector engine 15 | * https://github.com/ded/qwery 16 | * copyright Dustin Diaz & Jacob Thornton 2012 17 | * MIT License 18 | */ 19 | (function(a,b,c){typeof module!="undefined"&&module.exports?module.exports=b():typeof c["define"]=="function"&&c.define.amd?define(a,b):c[a]=b()})("qwery",function(){function C(){this.c={}}function H(a){return D.g(a)||D.s(a,"(^|\\s+)"+a+"(\\s+|$)",1)}function I(a,b){var c=0,d=a.length;for(;c~+]/,q=/^\s+|\s*([,\s\+\~>]|$)\s*/g,r=/[\s\>\+\~]/,s=/(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/,t=/([.*+?\^=!:${}()|\[\]\/\\])/g,u=/^(\*|[a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,v=/\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,w=/:([\w\-]+)(\(['"]?([^()]+)['"]?\))?/,x=new RegExp(l.source+"|"+n.source+"|"+m.source),y=new RegExp("("+r.source+")"+s.source,"g"),z=new RegExp(r.source+s.source),A=new RegExp(u.source+"("+v.source+")?"+"("+w.source+")?"),B={" ":function(a){return a&&a!==b&&a.parentNode},">":function(a,b){return a&&a.parentNode==b.parentNode&&a.parentNode},"~":function(a){return a&&a.previousSibling},"+":function(a,b,c,d){return a?(c=L(a))&&(d=L(b))&&c==d&&c:!1}};C.prototype={g:function(a){return this.c[a]||undefined},s:function(a,b,c){return b=c?new RegExp(b):b,this.c[a]=b}};var D=new C,E=new C,F=new C,G=new C,$="compareDocumentPosition"in b?function(a,b){return(b.compareDocumentPosition(a)&16)==16}:"contains"in b?function(a,c){return c=c[h]===9||c==window?b:c,c!==a&&c.contains(a)}:function(a,b){while(a=a.parentNode)if(a===b)return 1;return 0},_=function(){var b=a.createElement("p");return(b.innerHTML='x')&&b.firstChild.getAttribute("href")!="#x"?function(a,b){return b==="class"?a.className:b==="href"||b==="src"?a.getAttribute(b,2):a.getAttribute(b)}:function(a,b){return a.getAttribute(b)}}(),ba=!!a[c],bb=a.querySelector&&a[e],bc=function(a,b){var c=[],d,f;try{return b[h]===9||!p.test(a)?K(b[e](a)):(I(d=a.split(","),Z(b,function(a,b){f=a[e](b),f.length==1?c[c.length]=f.item(0):f.length&&(c=c.concat(K(f)))})),d.length>1&&c.length>1?U(c):c)}catch(g){}return bd(a,b)},bd=function(a,b){var c=[],e,f,g,i,j,k;a=a.replace(q,"$1");if(f=a.match(o)){j=H(f[2]),e=b[d](f[1]||"*");for(g=0,i=e.length;g1&&c.length>1?U(c):c},be=function(a){typeof a[f]!="undefined"&&(i=a[f]?bb?bc:bd:bd)};return be({useNativeQSA:!0}),Y.configure=be,Y.uniq=U,Y.is=R,Y.pseudos={},Y},this); 20 | 21 | /*! 22 | * Bonzo: DOM Utility (c) Dustin Diaz 2012 23 | * https://github.com/ded/bonzo 24 | * License MIT 25 | */ 26 | (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=t():typeof n["define"]=="function"&&n.define.amd?define(e,t):n[e]=t()})("bonzo",function(){function M(e){return new RegExp("(^|\\s+)"+e+"(\\s+|$)")}function _(e,t,n,r){var i,s=0,o=e.length;for(;s0?J(o,r):r)},null,r)},this,r),o.length=s,_(u,function(e){o[--s]=e},null,!r),o}function W(e,t,n){var r=Y(e),i=r.css("position"),s=r.offset(),o="relative",u=i==o,a=[parseInt(r.css("left"),10),parseInt(r.css("top"),10)];i=="static"&&(r.css("position",o),i=o),isNaN(a[0])&&(a[0]=u?0:e.offsetLeft),isNaN(a[1])&&(a[1]=u?0:e.offsetTop),t!=null&&(e.style.left=t-s.left+a[0]+E),n!=null&&(e.style.top=n-s.top+a[1]+E)}function X(e,t){return typeof t=="function"?t(e):t}function V(e){this.length=0;if(e){e=typeof e!="string"&&!e.nodeType&&typeof e.length!="undefined"?e:[e],this.length=e.length;for(var t=0;t","",1],a=["","
    ",3],f=["",1],l=["_","",0,1],c={thead:u,tbody:u,tfoot:u,colgroup:u,caption:u,tr:["","
    ",2],th:a,td:a,col:["","
    ",2],fieldset:["
    ","
    ",1],legend:["
    ","
    ",2],option:f,optgroup:f,script:l,style:l,link:l,param:l,base:l},h=/^(checked|selected|disabled)$/,p=/msie/i.test(navigator.userAgent),d,v,m,g={},y=0,b=/^-?[\d\.]+$/,w=/^data-(.+)$/,E="px",S="setAttribute",x="getAttribute",T="getElementsByTagName",N=function(){var e=t.createElement("p");return e.innerHTML='x
    ',{hrefExtended:e[T]("a")[0][x]("href")!="#x",autoTbody:e[T]("tbody").length!==0,computedStyle:t.defaultView&&t.defaultView.getComputedStyle,cssFloat:e[T]("table")[0].style.styleFloat?"styleFloat":"cssFloat",transform:function(){var t=["webkitTransform","MozTransform","OTransform","msTransform","Transform"],n;for(n=0;n]+)/.exec(e),i=t.createElement("div"),s=[],o=n?c[n[1].toLowerCase()]:null,u=o?o[2]+1:1,a=o&&o[3],f=r,l=N.autoTbody&&o&&o[0]==""&&!/0){t=t.split(" ");for(a=t.length;a--;)I(e,t[a],n);return e}i=l&&t.replace(o,""),i&&N[i]&&(i=N[i].type);if(!t||l){if(u=l&&t.replace(s,""))u=u.split(".");f(e,i,n,u)}else if(typeof t=="function")f(e,null,t);else for(r in t)t.hasOwnProperty(r)&&I(e,r,t[r]);return e},q=function(e,t,n,r,i){var s,o,u,a,f=n,l=n&&typeof n=="string";if(t&&!n&&typeof t=="object")for(s in t)t.hasOwnProperty(s)&&q.apply(this,[e,s,t[s]]);else{a=arguments.length>3?y.call(arguments,3):[],o=(l?n:t).split(" "),l&&(n=F(t,f=r,i||O))&&(a=y.call(a,1)),this===x&&(n=H(I,e,t,n,f));for(u=o.length;u--;)j(e,o[u],n,f,a)}return e},R=function(){return q.apply(x,arguments)},U=m?function(e,t,r){var i=d.createEvent(e?"HTMLEvents":"UIEvents");i[e?"initEvent":"initUIEvent"](t,!0,!0,n,1),r.dispatchEvent(i)}:function(e,t,n){n=k(n,e),e?n.fireEvent("on"+t,d.createEventObject()):n["_on"+t]++},z=function(e,t,n){var r,i,u,a,f,l=t.split(" ");for(r=l.length;r--;){t=l[r].replace(o,"");if(a=l[r].replace(s,""))a=a.split(".");if(!a&&!n&&e[g])U(T[t],t,e);else{f=A.get(e,t),n=[!1].concat(n);for(i=0,u=f.length;i" 105 | ); 106 | setTimeout(chatPoll, 1000); 107 | } 108 | ); 109 | } 110 | 111 | setTimeout(function () { chatPoll(); }, 1000); 112 | 113 | /* end chat.js */ 114 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /elnode-tests.el: -------------------------------------------------------------------------------- 1 | ;;; elnode-tests.el --- tests for Elnode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010, 2011, 2012 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Maintainer: Nic Ferrier 7 | ;; Created: 5th October 2010 8 | ;; Keywords: lisp, http, hypermedia 9 | 10 | ;; This file is NOT part of GNU Emacs. 11 | 12 | ;; This program 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 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program 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 this program. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; 27 | ;; This is just the tests for Elnode. 28 | 29 | ;;; Style note 30 | ;; 31 | ;; This codes uses the Emacs style of: 32 | ;; 33 | ;; elnode--private-function 34 | ;; 35 | ;; for private functions. 36 | 37 | ;;; Code: 38 | 39 | (require 'ert) 40 | (require 'fakir) 41 | (require 'elnode) 42 | (require 'kv) 43 | 44 | (ert-deftest elnode-join () 45 | "Test the path joining." 46 | (should 47 | (equal "/la/la/file" 48 | (elnode-join "/la" "la" "file"))) 49 | (should 50 | (equal "/la/la/file/" 51 | (elnode-join "/la" "la" "file/"))) 52 | (should 53 | (equal "/la/la/file/" 54 | (elnode-join "/la" "la/file/" "")))) 55 | 56 | (ert-deftest elnode-url-encode-path () 57 | "Test the path encoding." 58 | (should 59 | (equal 60 | "/path/the%20path" 61 | (elnode-url-encode-path "/path/the path"))) 62 | (should 63 | (equal 64 | "/path/the%20path/" 65 | (elnode-url-encode-path "/path/the path/"))) 66 | (should 67 | (equal 68 | "/path/the%20%27path%27" 69 | (elnode-url-encode-path "/path/the 'path'")))) 70 | 71 | (defun elnode--log-buffer-read-text (buffer) 72 | "Turn the buffer into a list of text. 73 | 74 | Strips off the date format from each text line. Primarily this 75 | is just a test helper." 76 | (let* ((log-line-regex "[0-9]\\{14\\}: \\(.*\\)") 77 | (lines 78 | (split-string 79 | (with-current-buffer buffer 80 | (buffer-substring (point-min) (point-max))) 81 | "\n"))) 82 | (loop for line in lines 83 | if (string-match log-line-regex line) 84 | collect (match-string 1 line)))) 85 | 86 | (ert-deftest elnode-log-buffer-log () 87 | "Test the log buffer stuff." 88 | (let ((tf (make-temp-file "logbufferlog"))) 89 | (with-temp-buffer 90 | (elnode-log-buffer-log "test it" (current-buffer) tf) 91 | (should 92 | (equal 93 | (marker-position elnode-log-buffer-position-written) 94 | (point-max))) 95 | (elnode-log-buffer-log "test again" (current-buffer) tf) 96 | (should 97 | (equal 98 | '("test it" "test again") 99 | (elnode--log-buffer-read-text (current-buffer))))) 100 | ;; Test that we can read it back from the file. 101 | (let* ((log-buf (find-file-noselect tf))) 102 | (should 103 | (equal 104 | '("test it" "test again") 105 | (elnode--log-buffer-read-text log-buf)))))) 106 | 107 | (ert-deftest elnode-log-buffer-log-truncates () 108 | "Test the log buffer gets truncated stuff." 109 | (let ((log-line-regex "[0-9]\\{14\\}: \\(.*\\)") 110 | (tf (make-temp-file "logbufferlog")) 111 | (elnode-log-buffer-max-size 8)) 112 | (with-temp-buffer 113 | (elnode-log-buffer-log "test it" (current-buffer) tf) 114 | (elnode-log-buffer-log "test again" (current-buffer) tf) 115 | (elnode-log-buffer-log "test three" (current-buffer) tf) 116 | (elnode-log-buffer-log "test four" (current-buffer) tf) 117 | (elnode-log-buffer-log "test five" (current-buffer) tf) 118 | (elnode-log-buffer-log "test six" (current-buffer) tf) 119 | (elnode-log-buffer-log "test seven" (current-buffer) tf) 120 | (elnode-log-buffer-log "test eight" (current-buffer) tf) 121 | (elnode-log-buffer-log "test nine" (current-buffer) tf) 122 | (elnode-log-buffer-log "test ten" (current-buffer) tf) 123 | (should 124 | (equal 125 | 8 126 | (length 127 | (loop for i in 128 | (split-string 129 | (buffer-substring 130 | (point-min) 131 | (point-max)) 132 | "\n") 133 | if (not (equal i "")) 134 | collect i))))))) 135 | 136 | (ert-deftest elnode-test-logs-dont-log () 137 | "Test the logs don't log when we turn stuff off." 138 | (let ((elnode-log-files-directory nil)) 139 | ;; FIXME this is not a test. duh. 140 | (elnode-error "test message!"))) 141 | 142 | (ert-deftest elnode-test-error-log () 143 | (let ((err-message "whoops!! something went wrong! %s" ) 144 | (err-include "some included value")) 145 | (with-temp-buffer 146 | (let ((test-log-buf (current-buffer))) 147 | ;; Setup a fake server log buffer 148 | (flet ((elnode--get-error-log-buffer () 149 | test-log-buf)) 150 | (elnode-error err-message err-include)) 151 | ;; Assert the message sent to the log buffer is correctly formatted. 152 | (should (string-match 153 | (format 154 | "^.*: %s\n$" 155 | (apply 'format `(,err-message ,@(list err-include)))) 156 | (buffer-substring (point-min) (point-max)))))))) 157 | 158 | (ert-deftest elnode-test-error-log-list () 159 | (let ((err-message "whoops!! something went wrong! %s %s") 160 | (err-include '("included value 1" "included value 2"))) 161 | (with-temp-buffer 162 | (let ((test-log-buf (current-buffer))) 163 | ;; Setup a fake server log buffer 164 | (flet ((elnode--get-error-log-buffer () 165 | test-log-buf)) 166 | (elnode-error 167 | err-message 168 | "included value 1" "included value 2")) 169 | ;; Assert the message sent to the log buffer is correctly formatted. 170 | (should (string-match 171 | (format 172 | "^.*: %s\n$" 173 | (apply 'format `(,err-message ,@err-include))) 174 | (buffer-substring (point-min) (point-max)))))))) 175 | 176 | (ert-deftest elnode-test-access-log () 177 | "Test the access logging." 178 | (fakir-mock-process 179 | :httpcon 180 | ((:buffer 181 | (elnode--http-make-hdr 182 | 'get "/" 183 | '(host . "localhost") 184 | '(user-agent . "test-agent"))) 185 | (:elnode-httpresponse-status 200) 186 | (:elnode-bytes-written 2048)) 187 | (should 188 | (equal 189 | 'done 190 | (catch 'elnode-parse-http 191 | (elnode--http-parse :httpcon)))) 192 | (let* ((logname "ert-test") 193 | (buffername (format "*%s-elnode-access*" logname))) 194 | (flet ((elnode--log-filename 195 | (log-name) 196 | (make-temp-file "elnode-access"))) 197 | (unwind-protect 198 | (progn 199 | (elnode-log-access logname :httpcon) 200 | (should 201 | (string-match 202 | (concat "^[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" 203 | "-[0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}:[ ]+" 204 | "200[ ]+2048[ ]+GET[ ]+/$") 205 | (with-current-buffer buffername 206 | (buffer-substring (point-min)(point-max)))))) 207 | (kill-buffer buffername)))))) 208 | 209 | (ert-deftest elnode-deferring () 210 | "Testing the defer setup." 211 | (let* ((result :not-done) 212 | (httpcon :fake) 213 | (handler (lambda (httpcon) 214 | (message "here!") 215 | (setq result :done))) 216 | (elnode--deferred (list))) 217 | (fakir-mock-process httpcon () 218 | ;; The queue starts empty 219 | (should (equal 0 (length elnode--deferred))) 220 | ;; Then we add to it... 221 | (elnode--deferred-add httpcon handler) 222 | (should (equal 1 (length elnode--deferred))) 223 | ;; Then we process it... 224 | (flet-overrides (lambda (obj) (eq obj :fake)) 225 | ((process-status proc (proc) 'open)) 226 | (elnode--deferred-processor)) 227 | ;; ... that should have emptied it out... 228 | (should (eq result :done)) 229 | (should (equal 0 (length elnode--deferred))) 230 | ;; Now we add a handler that defers... 231 | (elnode--deferred-add httpcon 232 | (lambda (httpcon) 233 | (elnode-defer-now handler))) 234 | (should (equal 1 (length elnode--deferred))) 235 | ;; Now we process... 236 | (flet-overrides (lambda (obj) (eq obj :fake)) 237 | ((process-status proc (proc) 'open)) 238 | (elnode--deferred-processor)) 239 | ;; ... should still have the deferred handler in it... 240 | (should (equal 1 (length elnode--deferred))) 241 | ;; ... process again ... 242 | (flet-overrides (lambda (obj) (eq obj :fake)) 243 | ((process-status proc (proc) 'open)) 244 | (elnode--deferred-processor)) 245 | (should (equal 0 (length elnode--deferred)))))) 246 | 247 | 248 | (ert-deftest elnode--make-http-hdr () 249 | "Test the construction of headers" 250 | (should 251 | (equal 252 | (elnode--http-make-hdr 253 | 'get "/" 254 | '(host . "host1") 255 | '(user-agent . "test-agent")) 256 | "GET / HTTP/1.1\r 257 | Host: host1\r 258 | User-Agent: test-agent\r 259 | \r 260 | ")) 261 | (should 262 | (equal 263 | (elnode--http-make-hdr 264 | 'get "/" 265 | '(host . "host2") 266 | '(user-agent . "test-agent") 267 | '(body . "my test data")) 268 | "GET / HTTP/1.1\r 269 | Host: host2\r 270 | User-Agent: test-agent\r 271 | \r 272 | my test data"))) 273 | 274 | (ert-deftest elnode--http-parse-header-complete () 275 | "Test the HTTP parsing." 276 | (fakir-mock-process 277 | :httpcon 278 | ((:buffer 279 | (elnode--http-make-hdr 280 | 'get "/" 281 | '(host . "localhost") 282 | '(user-agent . "test-agent")))) 283 | ;; Parse the header 284 | (should 285 | (equal 'done 286 | (catch 'elnode-parse-http 287 | (elnode--http-parse :httpcon)))) 288 | ;; Now check the side effects 289 | (should 290 | (equal 291 | (process-get :httpcon :elnode-http-header) 292 | '(("Host" . "localhost") 293 | ("User-Agent" . "test-agent")))))) 294 | 295 | (ert-deftest elnode--http-parse-header-incomplete () 296 | "Test the HTTP parsing of an incomplete header. 297 | 298 | An HTTP request with an incomplete header is setup and tested, 299 | then we finish the request (fill out the header) and then test 300 | again." 301 | (fakir-mock-process 302 | :httpcon 303 | ((:buffer 304 | "GET / HTTP/1.1\r\nHost: localh")) 305 | ;; Now parse 306 | (should 307 | ;; It fails with incomplete 'header signal 308 | (equal 'header 309 | (catch 'elnode-parse-http 310 | (elnode--http-parse :httpcon)))) 311 | ;; Now put the rest of the header in the buffer 312 | (with-current-buffer (process-buffer :httpcon) 313 | (goto-char (point-max)) 314 | (insert "ost\r\n\r\n")) 315 | (should 316 | ;; Now it succeeds with the 'done signal 317 | (equal 'done 318 | (catch 'elnode-parse-http 319 | (elnode--http-parse :httpcon)))))) 320 | 321 | 322 | (ert-deftest elnode--http-parse-body-incomplete () 323 | "Tests the HTTP parsing of an incomplete body. 324 | 325 | An HTTP request with an incomplete body is setup and tested, then 326 | we finish the request (fill out the content to content-length) 327 | and then test again." 328 | (let ((hdr 329 | (elnode--http-make-hdr 330 | 'get "/" 331 | '(host . "localhost") 332 | '(user-agent . "test-agent") 333 | `(content-length . ,(format "%d" (length "this is not finished"))) 334 | '(body . "this is not fin")))) 335 | (fakir-mock-process 336 | :httpcon 337 | ((:buffer hdr)) 338 | ;; Now parse 339 | (should 340 | (equal 'content 341 | (catch 'elnode-parse-http 342 | (elnode--http-parse :httpcon)))) 343 | ;; Now put the rest of the text in the buffer 344 | (with-current-buffer (process-buffer :httpcon) 345 | (goto-char (point-max)) 346 | (insert "ished")) 347 | ;; And test again 348 | (should 349 | (equal 'done 350 | (catch 'elnode-parse-http 351 | (elnode--http-parse :httpcon))))))) 352 | 353 | 354 | (ert-deftest elnode-http-start () 355 | "Test starting a response. 356 | 357 | Especially tests the mix of header setting techniques." 358 | (fakir-mock-process :httpcon () 359 | (elnode-http-header-set :httpcon "Content-Type" "text/html") 360 | (elnode-http-header-set :httpcon "Accept" "application/javascript") 361 | (elnode-http-start :httpcon 200 '("Content-Type" . "text/plain")) 362 | ;; Test that we have the correct text in the fake process buffer 363 | (with-current-buffer (fakir-get-output-buffer) 364 | (goto-char (point-min)) 365 | (should 366 | (re-search-forward "^Content-Type: text/html\r\n" nil t)) 367 | (goto-char (point-min)) 368 | (should 369 | (re-search-forward "^Accept: application/javascript\r\n" nil t))))) 370 | 371 | 372 | (defun elnode-test-handler (httpcon) 373 | "A simple handler for testing `elnode-test-call'. 374 | 375 | The text spat out is tested, so is the status." 376 | (elnode-http-start 377 | httpcon 200 378 | '("Content-Type" . "text/html") 379 | '("User-Agent" . "elnode-test")) 380 | (let ((params (elnode-http-params httpcon))) 381 | (elnode-http-return 382 | httpcon 383 | (format 384 | "

    Hello World

    %s" 385 | (if params 386 | (format "
    %S
    " params) 387 | ""))))) 388 | 389 | (ert-deftest elnode--make-test-call () 390 | "Test the HTTP request construction." 391 | (should 392 | (equal 393 | "GET / HTTP/1.1\r\n\r\n" 394 | (elnode--make-test-call 395 | "/" "GET" 396 | '() 397 | nil))) 398 | (should 399 | (equal 400 | "GET /?a=1&b=hello HTTP/1.1\r\n\r\n" 401 | (elnode--make-test-call 402 | "/" "GET" 403 | '((a . 1)(b . "hello")) 404 | nil))) 405 | (should 406 | (equal 407 | "POST / HTTP/1.1\r 408 | Content-Type: application/x-www-form-urlencoded\r 409 | Content-Length: 11\r 410 | \r 411 | a=1&b=hello" 412 | (elnode--make-test-call 413 | "/" "POST" 414 | '((a . 1)(b . "hello")) 415 | nil))) 416 | ;; The content type always comes before any other headers 417 | (should 418 | (equal 419 | "POST / HTTP/1.1\r 420 | Content-Type: application/x-www-form-urlencoded\r 421 | Content-Length: 11\r 422 | User-Agent: elnode-test\r 423 | \r 424 | a=1&b=hello" 425 | (elnode--make-test-call 426 | "/" "POST" 427 | '((a . 1)(b . "hello")) 428 | '(("User-agent" . "elnode-test")))))) 429 | 430 | (ert-deftest elnode-test-call-assert () 431 | "Test that we can assert things about elnode test responses." 432 | (with-elnode-mock-server 433 | ;; Test dispatcher 434 | (lambda (httpcon) 435 | (elnode-hostpath-dispatcher 436 | httpcon 437 | '(("[^/]*//test/.*" . elnode-test-handler)))) t 438 | (should-elnode-response 439 | (elnode-test-call "/test/test.something") 440 | :status-code 200 441 | :header-name "Content-Type" 442 | :header-value "text/html" 443 | :body-match ".*

    Hello World

    ") 444 | ;; Success with multiple headers 445 | (should-elnode-response 446 | (elnode-test-call "/test/test.something" 447 | :method "POST" 448 | :parameters '(("a" . 1))) 449 | :status-code 200 450 | :header-list '(("Content-Type" . "text/html") 451 | ("User-Agent" . "elnode-test")) 452 | :body-match ".*
    ((\"a\" . \"1\"))
    ") 453 | ;; Success with multiple header regexes 454 | (should-elnode-response 455 | (elnode-test-call "/test/test.something" 456 | :method "POST" 457 | :parameters '(("a" . 1))) 458 | :status-code 200 459 | :header-list-match '(("Content-Type" . "text/html") 460 | ("User-Agent" . "elnode-.*")) 461 | :body-match ".*
    ((\"a\" . \"1\"))
    ") 462 | ;; With params 463 | (should-elnode-response 464 | (elnode-test-call "/test/test.something" 465 | :method "POST" 466 | :parameters '(("a" . 1))) 467 | :status-code 200 468 | :header-name "Content-Type" 469 | :header-value "text/html" 470 | :body-match ".*
    ((\"a\" . \"1\"))
    "))) 471 | 472 | (ert-deftest elnode-test-call-cookie-store () 473 | "Test the cookie store." 474 | ;; Test with empty cookie store 475 | (with-elnode-mock-server 476 | (lambda (httpcon) 477 | (elnode-http-start 478 | httpcon 200 479 | '("Content-Type" . "text/html") 480 | (elnode-http-cookie-make 481 | "mycookie" 101 482 | :expiry "Mon, Feb 27 2012 22:10:21 GMT")) 483 | (elnode-http-return httpcon "

    HA!

    ")) t 484 | ;; Let-bind empty cookie store 485 | (let ((elnode--cookie-store (make-hash-table :test 'equal))) 486 | (elnode-test-call "/anything") 487 | (should 488 | (equal 489 | (kvhash->alist elnode--cookie-store) 490 | '(("mycookie" . "101")))))) 491 | ;; Test merging cookie store 492 | (with-elnode-mock-server 493 | (lambda (httpcon) 494 | (elnode-http-start 495 | httpcon 200 496 | '("Content-Type" . "text/html") 497 | (elnode-http-cookie-make 498 | "mycookie" 101 499 | :expiry "Mon, Feb 27 2012 22:10:21 GMT")) 500 | (elnode-http-return httpcon "

    HA!

    ")) t 501 | (let ((elnode--cookie-store 502 | (kvalist->hash '(("a" . "1")("b" . "hello!"))))) 503 | (elnode-test-call "/anything") 504 | (should 505 | (equal 506 | (kvalist-sort (kvhash->alist elnode--cookie-store) 'string-lessp) 507 | '(("a" . "1") 508 | ("b" . "hello!") 509 | ("mycookie" . "101"))))))) 510 | 511 | (ert-deftest elnode-http-header () 512 | "Test that we have headers." 513 | (fakir-mock-process 514 | :httpcon 515 | ((:buffer 516 | (elnode--http-make-hdr 517 | 'get "/" 518 | '(host . "localhost") 519 | '(user-agent . "test-agent") 520 | '(if-modified-since . "Mon, Feb 27 2012 22:10:21 GMT") 521 | `(content-length . ,(format "%d" (length "this is finished"))) 522 | '(body . "this is finished")))) 523 | ;; Now parse 524 | (should 525 | (equal 'done 526 | (catch 'elnode-parse-http 527 | (elnode--http-parse :httpcon)))) 528 | (should 529 | (equal "test-agent" 530 | (elnode-http-header :httpcon "User-Agent"))) 531 | (should 532 | (equal "test-agent" 533 | (elnode-http-header :httpcon 'user-agent))) 534 | (should 535 | (equal "test-agent" 536 | (elnode-http-header :httpcon 'User-Agent))) 537 | (should 538 | (equal '(20299 65357) 539 | (elnode-http-header :httpcon 'if-modified-since :time))) 540 | ;; FIXME - add a test for bad time encoding 541 | (should 542 | (equal "Mon, Feb 27 2012 22:10:21 GMT" 543 | (elnode-http-header :httpcon 'if-modified-since))))) 544 | 545 | (ert-deftest elnode-test-cookies () 546 | "Test that we can get all the cookies." 547 | (fakir-mock-process :httpcon 548 | ((:elnode-http-header 549 | '(("Cookie" . "csrf=213u2132%20321412nsfnwlv; username=nicferrier")))) 550 | (should 551 | (equal 552 | (elnode-http-cookies :httpcon) 553 | '(("csrf" . "213u2132 321412nsfnwlv") 554 | ("username" . "nicferrier"))))) 555 | ;; Now with empty header 556 | (fakir-mock-process :httpcon 557 | ((:elnode-http-header 558 | '(("Content-type" . "text/xml")))) 559 | (should-not 560 | (elnode-http-cookies :httpcon)))) 561 | 562 | (ert-deftest elnode-test-cookie () 563 | "Test the cookie retrieval" 564 | ;; First test no cookie header 565 | (fakir-mock-process :httpcon 566 | ((:elnode-http-header 567 | '(("Referer" . "http://somehost.example/com")))) 568 | (should-not 569 | (elnode-http-cookie :httpcon "username"))) 570 | ;; Now do we have a cookie? 571 | (fakir-mock-process :httpcon 572 | ((:elnode-http-header 573 | '(("Cookie" . "csrf=213u21321321412nsfnwlv; username=nicferrier")))) 574 | (should 575 | (equal 576 | (elnode-http-cookie :httpcon "username") 577 | '("username" . "nicferrier"))) 578 | (should 579 | (equal 580 | "nicferrier" 581 | (elnode-http-cookie :httpcon "username" t))))) 582 | 583 | (ert-deftest elnode-test-cookie-list () 584 | "Test that a cookie list property is set on the connection. 585 | 586 | Cookie lists are good fake up values for higher abstraction 587 | testing code so we specifically test that they work." 588 | (fakir-mock-process 589 | :httpcon 590 | ;; Define a cookie with a faked cookie list 591 | ((:elnode-http-cookie-list '(("name" . "value")))) 592 | (should 593 | (equal 594 | '("name" . "value") 595 | (elnode-http-cookie :httpcon "name")))) 596 | ;; Not sure about what the property should contain here... 597 | (fakir-mock-process 598 | :httpcon 599 | ((:elnode-http-header 600 | '(("Cookie" . "name=value; other=hello%20world")))) 601 | (elnode-http-cookie :httpcon "name") 602 | (should 603 | (equal 604 | '(("name" . "value") 605 | ("other" . "hello world")) 606 | (process-get :httpcon :elnode-http-cookie-list))))) 607 | 608 | (ert-deftest elnode-http-cookie-make () 609 | "Test the cookie header maker." 610 | ;; Expiry using a string date 611 | (should 612 | (equal 613 | '("Set-Cookie" . "mycookie=101; Expires=Mon, Feb 27 2012 22:10:21 GMT;") 614 | (elnode-http-cookie-make 615 | "mycookie" 101 616 | :expiry "Mon, Feb 27 2012 22:10:21 GMT")))) 617 | 618 | (ert-deftest elnode--response-header-to-cookie-store () 619 | "Test increasing the cookie store." 620 | (should 621 | (equal 622 | (kvhash->alist 623 | (let ((elnode--cookie-store (make-hash-table :test 'equal))) 624 | (elnode--response-header-to-cookie-store 625 | '(("Cookie" . "a=10; b=20") 626 | ("Content-Type" . "text/html") 627 | ("Set-Cookie" 628 | . "mycookie=101; Expires=Mon, Feb 27 2012 22:10:21 GMT;"))))) 629 | '(("mycookie" . "101")))) 630 | (should 631 | (equal 632 | (kvalist-sort 633 | (kvhash->alist 634 | (let ((elnode--cookie-store 635 | (kvalist->hash '(("a" . "20") 636 | ("b" . "this is it!"))))) 637 | (elnode--response-header-to-cookie-store 638 | '(("Cookie" . "a=10; b=20") 639 | ("Content-Type" . "text/html") 640 | ("Set-Cookie" 641 | . "mycookie=101; Expires=Mon, Feb 27 2012 22:10:21 GMT;"))))) 642 | 'string-lessp) 643 | '(("a" . "20") 644 | ("b" . "this is it!") 645 | ("mycookie" . "101"))))) 646 | 647 | (ert-deftest elnode--cookie-store-to-header-value () 648 | (let ((elnode--cookie-store 649 | (kvalist->hash 650 | '(("a" . "10") 651 | ("b" . "hello world!") 652 | ("mycookie" . "101"))))) 653 | (should 654 | (equal 655 | (elnode--cookie-store-to-header-value) 656 | "a=10; b=hello%20world!; mycookie=101"))) 657 | (let ((elnode--cookie-store (make-hash-table :test 'equal))) 658 | (should-not 659 | (elnode--cookie-store-to-header-value)))) 660 | 661 | (ert-deftest elnode-test-http-get-params () 662 | "Test that the params are ok if they are on the status line. 663 | 664 | Sets ':elnode-http-params' to nil to trigger `elnode-http-params' 665 | parsing. That checks the ':elnode-http-method': 666 | 667 | - for GET it returns the parsed ':elnode-http-query' 668 | 669 | - for POST it returns the merger of the parsed POST body and 670 | ':elnode-http-query'. 671 | 672 | *** WARNING:: This test so far only handles GET ***" 673 | (fakir-mock-process :httpcon 674 | (:elnode-http-params 675 | (:elnode-http-method "GET") 676 | (:elnode-http-query "a=10")) 677 | (should (equal "10" (elnode-http-param :httpcon "a")))) 678 | ;; Test some more complex params 679 | (fakir-mock-process :httpcon 680 | (:elnode-http-params 681 | (:elnode-http-method "GET") 682 | (:elnode-http-query "a=10&b=lah+dee+dah&c+a=blah+blah")) 683 | (should (equal "lah dee dah" (elnode-http-param :httpcon "b"))) 684 | (should (equal "lah dee dah" (elnode-http-param :httpcon 'b))) 685 | (should (equal "blah blah" (elnode-http-param :httpcon "c a")))) 686 | ;; Test the filtering 687 | (fakir-mock-process :httpcon 688 | (:elnode-http-params 689 | (:elnode-http-method "GET") 690 | (:elnode-http-query "a=10&b=lah+dee+dah&d=blah+blah")) 691 | (should (equal "lah dee dah" (elnode-http-param :httpcon "b"))) 692 | (should (equal "lah dee dah" (elnode-http-param :httpcon 'b))) 693 | (should (equal '(("a" . "10")("b" . "lah dee dah")) 694 | (elnode-http-params :httpcon "a" "b"))) 695 | (should (equal '(("a" . "10")("b" . "lah dee dah")) 696 | (elnode-http-params :httpcon 'a "b"))))) 697 | 698 | (ert-deftest elnode-test-http-post-params () 699 | "Test that the params are ok if they are in the body. 700 | 701 | Does a full http parse of a dummy buffer." 702 | (let ((httpcon :httpcon)) 703 | (let ((post-body "a=10&b=20&c=this+is+finished")) 704 | (fakir-mock-process 705 | :httpcon 706 | ((:buffer 707 | (elnode--http-make-hdr 708 | 'post "/" 709 | '(host . "localhost") 710 | '(user-agent . "test-agent") 711 | `(content-length . ,(format "%d" (length post-body))) 712 | `(body . ,post-body)))) 713 | ;; Now parse 714 | (should 715 | (equal 'done 716 | (catch 'elnode-parse-http 717 | (elnode--http-parse httpcon)))) 718 | ;; Now test some params 719 | (should (equal "10" (elnode-http-param httpcon "a"))) 720 | (should (equal "20" (elnode-http-param httpcon "b"))) 721 | (should (equal "this is finished" (elnode-http-param httpcon "c"))))) 722 | ;; Test get of params that aren't there 723 | (fakir-mock-process 724 | :httpcon 725 | ((:buffer 726 | (elnode--http-make-hdr 727 | 'post "/" 728 | '(host . "localhost") 729 | '(user-agent . "test-agent") 730 | `(content-length . "0") 731 | `(body . "")))) 732 | ;; Now parse 733 | (should 734 | (equal 'done 735 | (catch 'elnode-parse-http 736 | (elnode--http-parse httpcon)))) 737 | (should-not (elnode-http-param httpcon "a")) 738 | (should-not (elnode-http-param httpcon "b")) 739 | (should-not (elnode-http-param httpcon "c"))))) 740 | 741 | (ert-deftest elnode-test-http-post-empty-params () 742 | "Test that the params are ok if they are just empty in the body." 743 | (let ((post-body "")) 744 | (fakir-mock-process 745 | :httpcon 746 | ((:buffer 747 | (elnode--http-make-hdr 748 | 'post "/" 749 | '(host . "localhost") 750 | '(user-agent . "test-agent") 751 | `(content-length . ,(format "%d" (length post-body))) 752 | `(body . ,post-body)))) 753 | ;; Now parse 754 | (should 755 | (equal 'done 756 | (catch 'elnode-parse-http 757 | (elnode--http-parse :httpcon)))) 758 | ;; Now test some params 759 | (should-not (elnode-http-param :httpcon "a"))))) 760 | 761 | 762 | (ert-deftest elnode--http-result-header () 763 | "Test that we can make result headers." 764 | (let ((l '((content-type . "text/html")))) 765 | (should 766 | (equal 767 | (elnode--http-result-header l) 768 | "Transfer-Encoding: chunked\r 769 | Content-Type: text/html\r 770 | "))) 771 | (let ((l '())) 772 | (should 773 | (equal 774 | (elnode--http-result-header l) 775 | "Transfer-Encoding: chunked\r 776 | ")))) 777 | 778 | (ert-deftest elnode-http-header-set () 779 | "Test the premature setting of HTTP headers." 780 | (fakir-mock-process 781 | :httpcon 782 | () 783 | (should 784 | (equal nil 785 | (process-get :httpcon :elnode-headers-to-set))) 786 | (elnode-http-header-set :httpcon "Content-Type" "text/html") 787 | (elnode-http-header-set 788 | :httpcon 789 | (elnode-http-cookie-make "mycookie" "value")) 790 | (should 791 | (equal '(("Content-Type" . "text/html") 792 | ("Set-Cookie" . "mycookie=value;")) 793 | (process-get :httpcon :elnode-headers-to-set))))) 794 | 795 | 796 | 797 | (ert-deftest elnode-send-json () 798 | "Test sending JSON." 799 | (let ((httpcon :fake) 800 | (sent-data "")) 801 | (should 802 | (equal 803 | ["a string in a list"] 804 | (json-read-from-string 805 | (flet ((elnode-http-return (con data) 806 | (setq sent-data data))) 807 | (fakir-mock-process 808 | httpcon 809 | () 810 | (elnode-send-json httpcon (list "a string in a list"))) 811 | sent-data)))))) 812 | 813 | (defconst elnode--buffer-template-example 814 | " 815 | 816 | 817 | 818 | <!##E title E##!> 819 | 820 | 821 | 822 | 823 | 824 | 825 | 833 | 834 | " 835 | "Example template source for `elnode--buffer-template' tests.") 836 | 837 | (ert-deftest elnode--buffer-template () 838 | "Test the buffer templating." 839 | (let ((result 840 | (with-temp-buffer 841 | (insert elnode--buffer-template-example) 842 | (elnode--buffer-template 843 | (current-buffer) 844 | '(("title" . "My webpage") 845 | ("username" . "nicferrier") 846 | ("name-html" . "
    you are talking to Caroline
    ")))))) 847 | (should 848 | (string-match ".*My webpage.*" result)) 849 | (should 850 | (string-match ".*nicferrier.*" result)) 851 | (should 852 | (string-match ".*
    you are talking to Caroline
    .*" result)))) 853 | 854 | 855 | (ert-deftest elnode--mapper-find () 856 | "Test the mapper find function." 857 | (fakir-mock-process 858 | :httpcon 859 | ((:nothing)) 860 | (should 861 | (equal 862 | (elnode--mapper-find 863 | :httpcon 864 | "localhost//wiki/somefile.creole" 865 | '(("[^/]+//wiki/\\(.*\\)" . elnode-wikiserver) 866 | ("[^/]+//.*" . elnode-webserver))) 867 | 'elnode-wikiserver)) 868 | (should 869 | (equal 870 | (elnode-http-mapping :httpcon) 871 | "localhost//wiki/somefile.creole")) 872 | (should 873 | (equal 874 | (elnode-http-mapping :httpcon 1) 875 | "somefile.creole")) 876 | (should 877 | (equal 878 | (elnode--mapper-find 879 | :httpcon 880 | "anyhost//wiki/somefile.creole" 881 | '(("[^/]+//wiki/\\(.*\\)" . elnode-wikiserver) 882 | ("[^/]+//.*" . elnode-webserver))) 883 | 'elnode-wikiserver)))) 884 | 885 | (ert-deftest elnode--strip-leading-slash () 886 | "Test slash stripping. 887 | 888 | That sounds more fun than it is." 889 | (should 890 | (equal "blah" 891 | (elnode--strip-leading-slash "/blah"))) 892 | (should 893 | (equal "blah" 894 | (elnode--strip-leading-slash "blah"))) 895 | (should 896 | (equal "blah/" 897 | (elnode--strip-leading-slash "/blah/"))) 898 | (should 899 | (equal "blah/" 900 | (elnode--strip-leading-slash "blah/")))) 901 | 902 | (ert-deftest elnode-get-targetfile () 903 | "Test the target file resolution stuff." 904 | (fakir-mock-process 905 | :httpcon 906 | ((:elnode-http-pathinfo "/wiki/index.creole")) 907 | (should 908 | (equal 909 | 'elnode-wikiserver 910 | (elnode--mapper-find 911 | :httpcon 912 | "localhost//wiki/index.creole" 913 | '(("[^/]+//wiki/\\(.*\\)" . elnode-wikiserver) 914 | ("[^/]+//\\(.*\\)" . elnode-webserver))))) 915 | (fakir-mock-file (fakir-file 916 | :filename "index.creole" 917 | :directory "/home/elnode/wiki") 918 | (should 919 | (equal 920 | (elnode-get-targetfile :httpcon "/home/elnode/wiki") 921 | "/home/elnode/wiki/index.creole")))) 922 | ;; Now alter the mapping to NOT declare the mapped part... 923 | (fakir-mock-process 924 | :httpcon 925 | ((:elnode-http-pathinfo "/blah/thing.txt")) 926 | ;; ... the mapper-find should still work... 927 | (should 928 | (equal 929 | 'elnode-webserver 930 | (elnode--mapper-find 931 | :httpcon 932 | "localhost//blah/thing.txt" 933 | '(("[^/]+//.*" . elnode-webserver))))) 934 | ;; ... but now there is no mapping so it only maps because of path-info 935 | (fakir-mock-file (fakir-file 936 | :filename "thing.txt" 937 | :directory "/home/elnode/www/blah") 938 | (should 939 | (equal 940 | (elnode-get-targetfile :httpcon "/home/elnode/www") 941 | "/home/elnode/www/blah/thing.txt")))) 942 | ;; Test without a mapping 943 | (fakir-mock-process 944 | :httpcon 945 | ((:elnode-http-pathinfo "/index.creole")) 946 | (fakir-mock-file (fakir-file 947 | :filename "index.creole" 948 | :directory "/home/elnode/wiki") 949 | (should 950 | (equal 951 | (elnode-get-targetfile :httpcon "/home/elnode/wiki") 952 | "/home/elnode/wiki/index.creole"))))) 953 | 954 | ;; This stuff is replaced by the new rle stuff 955 | ;; (ert-deftest elnode-worker-elisp () 956 | ;; "Test the `elnode-worker-elisp' macro. 957 | ;; 958 | ;; Runs some lisp in a child Emacs and tests that it outputs the 959 | ;; right thing." 960 | ;; (let* ((bufname (generate-new-buffer-name "elnode-worker-elisp-test")) 961 | ;; (buf (get-buffer-create bufname))) 962 | ;; (elnode-wait-for-exit 963 | ;; ;; Nice simple bit of elisp to run in the child 964 | ;; (elnode-worker-elisp 965 | ;; buf 966 | ;; ((a 10) 967 | ;; (b 20)) 968 | ;; (setq x a) 969 | ;; (princ x))) 970 | ;; (should 971 | ;; (equal 972 | ;; "10" 973 | ;; (let ((output 974 | ;; (with-current-buffer buf 975 | ;; (buffer-substring (point-min) (point-max))))) 976 | ;; (kill-buffer buf) 977 | ;; output))))) 978 | 979 | (ert-deftest elnode-method () 980 | "A quick test for `elnode-method'." 981 | (let ((httpcon :fake) 982 | method) 983 | (flet ((elnode-http-method (http-con) 984 | "GET")) 985 | (elnode-method httpcon 986 | (GET 987 | (setq method "GET")) 988 | (POST 989 | (set method "POST"))) 990 | (should (equal method "GET"))))) 991 | 992 | 993 | (ert-deftest elnode--under-docroot-p () 994 | "Test that the docroot protection works." 995 | (fakir-mock-file (fakir-file 996 | :filename "index.creole" 997 | :directory "/home/elnode/wiki") 998 | (should 999 | (elnode--under-docroot-p 1000 | "/home/elnode/wiki/index.creole" 1001 | "/home/elnode/wiki")) 1002 | (should 1003 | (elnode--under-docroot-p 1004 | "/home/elnode/wiki/index.creole" 1005 | "~/wiki")) 1006 | (should-not 1007 | (elnode--under-docroot-p 1008 | "/home/elnode/wiki/blah/index.creole" 1009 | "/home/elnode/wiki")) 1010 | (should-not 1011 | (elnode--under-docroot-p 1012 | "/home/elnode/wiki/blah.creole" 1013 | "/home/elnode/wiki")) 1014 | (should-not 1015 | (elnode--under-docroot-p 1016 | "/home/elnode/wikiroot/blah.creole" 1017 | "/home/elnode/wiki")) 1018 | (should-not 1019 | (elnode--under-docroot-p 1020 | "/home/elnode/wiki/blah.creole" 1021 | "/home/elnode/wikiroot")))) 1022 | 1023 | (ert-deftest elnode-cached-p () 1024 | "Is a resource cached?" 1025 | (fakir-mock-file (fakir-file 1026 | :filename "page.creole" 1027 | :directory "/home/elnode/wiki" 1028 | :mtime "Mon, Feb 27 2012 22:10:21 GMT") 1029 | (fakir-mock-process 1030 | :httpcon 1031 | ((:elnode-http-header-syms 1032 | '((if-modified-since . "Mon, Feb 27 2012 22:10:24 GMT")))) 1033 | (should 1034 | (elnode-cached-p :httpcon "/home/elnode/wiki/page.creole"))) 1035 | ;; Test the case where there is no header 1036 | (fakir-mock-process 1037 | :httpcon 1038 | ((:elnode-http-header-syms 1039 | '((user-agent . "Elnode test client")))) 1040 | (should-not 1041 | (elnode-cached-p :httpcon "/home/elnode/wiki/page.creole"))))) 1042 | 1043 | (ert-deftest elnode-docroot-for () 1044 | "Test the docroot protection macro." 1045 | (let ((httpcon :fake)) 1046 | (flet ((elnode-send-404 (httpcon) 1047 | (throw :test 404)) 1048 | (elnode-send-status (httpcon status &optional msg) 1049 | (throw :test status)) 1050 | (send-200 (httpcon) 1051 | (throw :test 200))) 1052 | ;; Test straight through 1053 | (should 1054 | (equal 1055 | 200 1056 | (catch :test 1057 | (fakir-mock-process 1058 | :fake 1059 | ((:elnode-http-pathinfo "/wiki/test.creole") 1060 | (:elnode-http-mapping '("/wiki/test.creole" "test.creole"))) 1061 | (fakir-mock-file 1062 | (fakir-file 1063 | :filename "test.creole" 1064 | :directory "/home/elnode/wikiroot") 1065 | (elnode-docroot-for "/home/elnode/wikiroot" 1066 | with target-path 1067 | on httpcon 1068 | do 1069 | (send-200 httpcon))))))) 1070 | ;; Non-existant path 1071 | (should 1072 | (equal 1073 | 404 1074 | (catch :test 1075 | (fakir-mock-process 1076 | :fake 1077 | ((:elnode-http-pathinfo "/wiki/test.creole") 1078 | (:elnode-http-mapping '("/wiki/test.creole" "test.creole"))) 1079 | (fakir-mock-file 1080 | (fakir-file 1081 | :filename "test.creole" 1082 | :directory "/home/elnode/wikiroot") 1083 | (elnode-docroot-for "/home/elnode/wikifiles" 1084 | with target-path 1085 | on httpcon 1086 | do 1087 | (send-200 httpcon))))))) 1088 | ;; Test the cached check 1089 | (should 1090 | (equal 1091 | 304 1092 | (catch :test 1093 | (fakir-mock-process 1094 | :fake 1095 | ((:elnode-http-pathinfo "/wiki/test.creole") 1096 | (:elnode-http-mapping '("/wiki/test.creole" "test.creole")) 1097 | (:elnode-http-header-syms 1098 | '((if-modified-since . "Mon, Feb 27 2012 22:10:24 GMT")))) 1099 | (fakir-mock-file 1100 | (fakir-file 1101 | :filename "test.creole" 1102 | :directory "/home/elnode/wikiroot" 1103 | :mtime "Mon, Feb 27 2012 22:10:20 GMT") 1104 | (elnode-docroot-for "/home/elnode/wikiroot" 1105 | with target-path 1106 | on httpcon 1107 | do 1108 | (send-200 httpcon)))))))))) 1109 | 1110 | (ert-deftest elnode-webserver () 1111 | (with-elnode-mock-server 1112 | ;; The dispatcher function 1113 | (lambda (httpcon) 1114 | (elnode-hostpath-dispatcher 1115 | httpcon 1116 | '(("[^/]*/\\(.*\\)" . elnode-webserver)))) 1117 | ;; Now the actual test 1118 | (fakir-mock-file 1119 | (fakir-file 1120 | :filename "blah.html" 1121 | :directory elnode-webserver-docroot-default 1122 | :content "Fake HTML file") 1123 | (unwind-protect 1124 | ;; Ensure the webserver uses Emacs to open files so fakir can 1125 | ;; override it. 1126 | (let* ((elnode-webserver-visit-file t) 1127 | ;; Turn off logging 1128 | (elnode--do-error-logging nil) 1129 | (elnode--do-access-logging-on-dispatch nil) 1130 | ;; Make the served root the default 1131 | (elnode-webserver-docroot elnode-webserver-docroot-default)) 1132 | (should-elnode-response 1133 | (elnode-test-call "/blah.html") 1134 | :status-code 200 1135 | :body-match "Fake HTML file")) 1136 | ;; Now kill the buffer that was opened to serve the file. 1137 | (if (get-buffer "blah.html") 1138 | (kill-buffer "blah.html")))))) 1139 | 1140 | (ert-deftest elnode-client-with-stdout () 1141 | "Test the stdout macro. 1142 | 1143 | Test that we get the right chunked encoding stuff going on." 1144 | (fakir-mock-process :fake ((:elnode-http-started t)) 1145 | (with-stdout-to-elnode :fake 1146 | (princ "hello!")) 1147 | (should 1148 | (equal 1149 | (let ((str "hello!")) 1150 | (format "%d\r\n%s\r\n0\r\n\r\n" (length str) str)) 1151 | (with-current-buffer (fakir-get-output-buffer) 1152 | (buffer-substring (point-min) (point-max))))))) 1153 | 1154 | (defvar elnode-test-wrapped-handler-counter 0) 1155 | (defvar elnode-test-wrapping-handler-counter 0) 1156 | 1157 | 1158 | ;; Elnode auth tests 1159 | 1160 | (defun elnode--auth-init-user-db (user-alist &optional db) 1161 | "Initialize the auth database. 1162 | 1163 | USER-ALIST is an assoc list of username and passwords. 1164 | 1165 | Optionally allow the database to be specified with DB (the 1166 | default is `elnode-auth-db')." 1167 | (loop for pair in user-alist 1168 | do 1169 | (elnode-db-put 1170 | (car pair) 1171 | (elnode--auth-make-hash 1172 | (car pair) 1173 | (cdr pair)) 1174 | (or db elnode-auth-db)))) 1175 | 1176 | (ert-deftest elnode-auth-user-p () 1177 | "Check the authentication check. 1178 | 1179 | This tests the authentication database check." 1180 | (let* ((elnode-auth-db (elnode-db-make '(elnode-db-hash))) 1181 | ;; auth test 1182 | (auth-test 1183 | (lambda (username) 1184 | (elnode-auth-default-test username 'elnode-auth-db)))) 1185 | ;; The only time we really need clear text passwords is when 1186 | ;; faking records for test 1187 | (elnode--auth-init-user-db '(("nferrier" . "password") 1188 | ("someuser" . "secret"))) 1189 | (should 1190 | (elnode-auth-user-p "someuser" "secret" :auth-test auth-test)))) 1191 | 1192 | (ert-deftest elnode-auth-check-p () 1193 | "Test basic login. 1194 | 1195 | Tess that we can login a user and then assert that they are 1196 | authenticated." 1197 | (let* ((elnode-loggedin-db (make-hash-table :test 'equal)) 1198 | (elnode-auth-db (elnode-db-make '(elnode-db-hash))) 1199 | ;; Make an auth-test function 1200 | (auth-test 1201 | (lambda (username) 1202 | (elnode-auth-default-test username 'elnode-auth-db)))) 1203 | ;; The only time we really need clear text passwords is when 1204 | ;; faking records for test 1205 | (elnode--auth-init-user-db '(("nferrier" . "password") 1206 | ("someuser" "secret"))) 1207 | 1208 | ;; Test a failure 1209 | (should 1210 | (equal "an error occured!" 1211 | (condition-case credentials 1212 | (elnode-auth-login 1213 | "nferrier" "secret" 1214 | :auth-test auth-test) 1215 | (elnode-auth 1216 | "an error occured!")))) 1217 | 1218 | ;; Now test 1219 | (let ((hash (elnode-auth-login 1220 | "nferrier" "password" :auth-test auth-test))) 1221 | (should (elnode-auth-check-p "nferrier" hash))))) 1222 | 1223 | (ert-deftest elnode-auth-cookie-check-p () 1224 | "Check that a cookie can be used for auth." 1225 | (let* ((elnode-loggedin-db (make-hash-table :test 'equal)) 1226 | (elnode-auth-db (elnode-db-make '(elnode-db-hash))) 1227 | ;; auth-test function 1228 | (auth-test 1229 | (lambda (username) 1230 | (elnode-auth-default-test username 'elnode-auth-db)))) 1231 | ;; The only time we really need clear text passwords is when 1232 | ;; faking records for test 1233 | (elnode--auth-init-user-db '(("nferrier" . "password") 1234 | ("someuser" "secret"))) 1235 | ;; Now test 1236 | (let ((hash (elnode-auth-login 1237 | "nferrier" "password" :auth-test auth-test))) 1238 | (fakir-mock-process :httpcon 1239 | ((:elnode-http-header 1240 | `(("Cookie" . ,(concat "elnode-auth=nferrier::" hash))))) 1241 | (should (elnode-auth-cookie-check-p :httpcon)))) 1242 | ;; Test what happens without a cookie 1243 | (let ((hash (elnode-auth-login 1244 | "nferrier" "password" :auth-test auth-test))) 1245 | (fakir-mock-process :httpcon 1246 | ((:elnode-http-header 1247 | `(("Referer" . "http://somehost.example.com")))) 1248 | (should-not 1249 | (condition-case token 1250 | (elnode-auth-cookie-check-p :httpcon) 1251 | (elnode-auth-token (cdr token)))))))) 1252 | 1253 | (ert-deftest elnode-auth-login-sender () 1254 | "Low levelish test of the login page sender." 1255 | (fakir-mock-process 1256 | :httpcon 1257 | () 1258 | (elnode-auth-login-sender :httpcon "/login/" "/myapp/loggedin") 1259 | (with-current-buffer (fakir-get-output-buffer) 1260 | (goto-char (point-min)) 1261 | (should (re-search-forward 1262 | "
    " nil 't)) 1263 | (should (re-search-forward 1264 | "" 1265 | nil 't))))) 1266 | 1267 | ;; (ert-deftest elnode--auth-define-scheme-do-wrap () 1268 | ;; "Test wrapper destructuring. 1269 | 1270 | ;; The function under test uses some slightly exotic destructuring 1271 | ;; so we test it deliberately here." 1272 | ;; ;; First, test with a specified path 1273 | ;; (flet ((elnode--wrap-handler (wrap-target path wrapping-func &rest args) 1274 | ;; (should (equal wrap-target 'blah)) 1275 | ;; (should (equal path "/doit/")))) 1276 | ;; (elnode--auth-define-scheme-do-wrap 1277 | ;; (list 'blah 1278 | ;; (lambda (httpcon) 1279 | ;; (elnode-send-500 httpcon)) 1280 | ;; "/doit/"))) 1281 | ;; ;; Now with the default path 1282 | ;; (flet ((elnode--wrap-handler (wrap-target path wrapping-func &rest args) 1283 | ;; (should (equal wrap-target 'blah)) 1284 | ;; (should (equal path "/login/")))) 1285 | ;; (elnode--auth-define-scheme-do-wrap 1286 | ;; (list 'blah 1287 | ;; (lambda (httpcon) 1288 | ;; (elnode-send-500 httpcon)))))) 1289 | 1290 | ;; this is the thing I did to test the new let-elnode-handlers macro 1291 | 1292 | ;; (let-handlers 1293 | ;; ((my-handler-4 (httpcon) 1294 | ;; (elnode-send-html httpcon "

    hello!

    ")) 1295 | ;; (my-handler-5 (httpcon) 1296 | ;; (elnode-send-html httpcon "

    Goodbye!

    "))) 1297 | ;; (symbol-plist 'my-handler-4) 1298 | ;; ) 1299 | 1300 | 1301 | (defmacro elnode-auth-flets (&rest body) 1302 | "Wrap the BODY with some standard handler flets." 1303 | (declare (debug (&rest form))) 1304 | `(let-elnode-handlers 1305 | ((auth-reqd-handler (httpcon) 1306 | (with-elnode-auth httpcon 'test-auth 1307 | (elnode-dispatcher 1308 | httpcon 1309 | '(("^/someplace/.*" . 1310 | (lambda (httpcon) 1311 | (elnode-send-html 1312 | httpcon 1313 | "You are logged in!"))) 1314 | ("^/$" . 1315 | (lambda (httpcon) 1316 | (elnode-send-html 1317 | httpcon 1318 | "You are logged in too!")))))))) 1319 | ,@body)) 1320 | 1321 | (ert-deftest elnode-with-auth () 1322 | "Test protection of code with authentication. 1323 | 1324 | This tests that the auth protection macro does its job, including 1325 | the wrapping of a specified handler with the login sender." 1326 | ;; Setup the user db 1327 | (let ((elnode-auth-db (elnode-db-make '(elnode-db-hash)))) 1328 | ;; The only time we really need clear text passwords is when 1329 | ;; faking records for test 1330 | (elnode--auth-init-user-db '(("nferrier" . "password") 1331 | ("someuser" . "secret"))) 1332 | ;; Setup handlers to wrap 1333 | (elnode-auth-flets 1334 | ;; Make the auth scheme - since we use fset in 1335 | ;; elnode--wrap-handler this should work ok. 1336 | (elnode-auth-define-scheme 1337 | 'test-auth 1338 | :test :cookie 1339 | :cookie-name "secret" 1340 | :redirect (elnode-auth-make-login-wrapper 1341 | 'auth-reqd-handler 1342 | :target "/my-login/")) 1343 | ;; Test that we are redirected to login when we don't have cookie 1344 | (with-elnode-mock-server 'auth-reqd-handler t 1345 | (should-elnode-response 1346 | (elnode-test-call "/") 1347 | :status-code 302 1348 | :header-name "Location" 1349 | :header-value "/my-login/?redirect=/") 1350 | ;; Test that we get the login page - this tests that the main 1351 | ;; handler was wrapped 1352 | (should-elnode-response 1353 | (elnode-test-call "/my-login/") 1354 | :status-code 200 1355 | :body-match "") 1356 | ;; Do it all again with a different 'to' 1357 | (should-elnode-response 1358 | (elnode-test-call "/somepage/test/") 1359 | :status-code 302 1360 | :header-name "Location" 1361 | :header-value "/my-login/?redirect=/somepage/test/") 1362 | ;; Test that we get the login page - this tests that the main 1363 | ;; handler was wrapped 1364 | (should-elnode-response 1365 | (elnode-test-call "/my-login/") 1366 | :status-code 200 1367 | :body-match ""))))) 1368 | 1369 | (ert-deftest elnode-with-auth-bad-auth () 1370 | "Test bad auth causes login page again." 1371 | ;; Setup the user db 1372 | (let ((elnode-auth-db (elnode-db-make '(elnode-db-hash)))) 1373 | ;; The only time we really need clear text passwords is when 1374 | ;; faking records for test 1375 | (elnode--auth-init-user-db '(("nferrier" . "password") 1376 | ("someuser" . "secret"))) 1377 | ;; Setup handlers to wrap 1378 | (elnode-auth-flets 1379 | (elnode-auth-define-scheme 1380 | 'test-auth 1381 | :test :cookie 1382 | :cookie-name "secret" 1383 | :redirect (elnode-auth-make-login-wrapper 1384 | 'auth-reqd-handler 1385 | :target "/my-login/")) 1386 | ;; Test that we are redirected to login when we don't have cookie 1387 | (with-elnode-mock-server 'auth-reqd-handler 1388 | ;; Test a bad auth 1389 | (should-elnode-response 1390 | (elnode-test-call 1391 | "/my-login/?redirect=/somepage/test/" 1392 | :method "POST" 1393 | :parameters 1394 | '(("username" . "nferrier") 1395 | ("password" . "secret"))) 1396 | ;; The auth will fail - here's what we should get back 1397 | :status-code 302 1398 | :header-name "Location" 1399 | :header-value "/my-login/?redirect=/somepage/test/"))))) 1400 | 1401 | 1402 | (defvar elnode-test--my-db nil 1403 | "Special variable just for specific db test.") 1404 | 1405 | (ert-deftest elnode-with-auth-specific-db () 1406 | "Test using a specific database." 1407 | ;; Setup the user db 1408 | (let ((elnode-auth-db (elnode-db-make '(elnode-db-hash))) 1409 | (elnode-test--my-db (elnode-db-make '(elnode-db-hash)))) 1410 | (elnode--auth-init-user-db 1411 | '(("nferrier" . "password") 1412 | ("someuser" . "secret")) 1413 | elnode-auth-db) 1414 | (elnode--auth-init-user-db 1415 | '(("nic" . "test") 1416 | ("other" . "simple")) 1417 | elnode-test--my-db) 1418 | ;; Setup handlers to wrap 1419 | (elnode-auth-flets 1420 | (elnode-auth-define-scheme 1421 | 'test-auth 1422 | :test :cookie 1423 | :auth-db elnode-test--my-db 1424 | :cookie-name "secret" 1425 | :redirect (elnode-auth-make-login-wrapper 1426 | 'auth-reqd-handler 1427 | :target "/my-login/")) 1428 | ;; Test that we are redirected to login when we don't have cookie 1429 | (with-elnode-mock-server 'auth-reqd-handler t 1430 | (should-elnode-response 1431 | (elnode-test-call 1432 | "/my-login/?redirect=/somepage/test/" 1433 | :method 'POST 1434 | :parameters 1435 | '(("username" . "nic") 1436 | ("password" . "testxx"))) 1437 | ;; The auth fails 1438 | :status-code 302 1439 | :header-name "Location" 1440 | :header-value "/my-login/?redirect=/somepage/test/") 1441 | (should-elnode-response 1442 | (elnode-test-call 1443 | "/my-login/?redirect=/somepage/test/" 1444 | :method 'POST 1445 | :parameters 1446 | '(("username" . "nic") 1447 | ("password" . "test"))) 1448 | ;; The auth succeeds 1449 | :status-code 302 1450 | :header-name "Location" 1451 | :header-value "/somepage/test/"))))) 1452 | 1453 | 1454 | (provide 'elnode-tests) 1455 | 1456 | ;;; elnode-tests.el ends here 1457 | --------------------------------------------------------------------------------
    " 102 | + d[0][1] 103 | + "" + d[0][2] 104 | + "