From fc422f3b58bbecd1173968900641df2dd09b7a0f Mon Sep 17 00:00:00 2001 From: Peter Lemenkov Date: Jun 08 2010 11:52:38 +0000 Subject: Added tests and removed bundled mochiweb --- diff --git a/couchdb-0001-Force-init-script-installation.patch b/couchdb-0001-Force-init-script-installation.patch index d0c12a0..5fbf961 100644 --- a/couchdb-0001-Force-init-script-installation.patch +++ b/couchdb-0001-Force-init-script-installation.patch @@ -1,7 +1,7 @@ -From c63c6662fe59e5c515075b2b9511ccf0438aabc5 Mon Sep 17 00:00:00 2001 +From 4180fb6424162cfb8ee49f108c86fc408ddf640d Mon Sep 17 00:00:00 2001 From: Peter Lemenkov Date: Thu, 27 May 2010 15:17:20 +0400 -Subject: [PATCH 1/4] Force init script installation +Subject: [PATCH 1/7] Force init script installation --- configure.ac | 16 +++------------- diff --git a/couchdb-0002-Install-into-erllibdir-by-default.patch b/couchdb-0002-Install-into-erllibdir-by-default.patch index 350b838..4636da8 100644 --- a/couchdb-0002-Install-into-erllibdir-by-default.patch +++ b/couchdb-0002-Install-into-erllibdir-by-default.patch @@ -1,7 +1,7 @@ -From 63afe8dc321ed5c418e28b12fc831f19bcf532e7 Mon Sep 17 00:00:00 2001 +From e3f247d873c8245c05f573295c1a2e021614f0af Mon Sep 17 00:00:00 2001 From: Peter Lemenkov Date: Thu, 27 May 2010 15:18:29 +0400 -Subject: [PATCH 2/4] Install into erllibdir by default +Subject: [PATCH 2/7] Install into erllibdir by default --- configure | 4 ++-- diff --git a/couchdb-0003-Remove-bundled-erlang-oauth-library.patch b/couchdb-0003-Remove-bundled-erlang-oauth-library.patch index 036a597..ed04057 100644 --- a/couchdb-0003-Remove-bundled-erlang-oauth-library.patch +++ b/couchdb-0003-Remove-bundled-erlang-oauth-library.patch @@ -1,14 +1,35 @@ -From 42a4f19bde9c94685a88b3f3c979de07e39f56ab Mon Sep 17 00:00:00 2001 +From 8d31e5ff75531deecbf9279fe0e819e9c4bdae5c Mon Sep 17 00:00:00 2001 From: Peter Lemenkov Date: Thu, 27 May 2010 15:19:51 +0400 -Subject: [PATCH 3/4] Remove bundled erlang-oauth library +Subject: [PATCH 3/7] Remove bundled erlang-oauth library --- - configure | 3 --- - configure.ac | 1 - - src/Makefile.am | 2 +- - src/Makefile.in | 2 +- - 4 files changed, 2 insertions(+), 6 deletions(-) + configure | 3 - + configure.ac | 1 - + src/Makefile.am | 2 +- + src/Makefile.in | 2 +- + src/erlang-oauth/Makefile.am | 50 ---- + src/erlang-oauth/Makefile.in | 476 ---------------------------------- + src/erlang-oauth/oauth.app.in | 20 -- + src/erlang-oauth/oauth.erl | 107 -------- + src/erlang-oauth/oauth_hmac_sha1.erl | 11 - + src/erlang-oauth/oauth_http.erl | 22 -- + src/erlang-oauth/oauth_plaintext.erl | 10 - + src/erlang-oauth/oauth_rsa_sha1.erl | 30 --- + src/erlang-oauth/oauth_unix.erl | 16 -- + src/erlang-oauth/oauth_uri.erl | 88 ------- + test/etap/test_util.erl.in | 2 +- + 15 files changed, 3 insertions(+), 837 deletions(-) + delete mode 100644 src/erlang-oauth/Makefile.am + delete mode 100644 src/erlang-oauth/Makefile.in + delete mode 100644 src/erlang-oauth/oauth.app.in + delete mode 100644 src/erlang-oauth/oauth.erl + delete mode 100644 src/erlang-oauth/oauth_hmac_sha1.erl + delete mode 100644 src/erlang-oauth/oauth_http.erl + delete mode 100644 src/erlang-oauth/oauth_plaintext.erl + delete mode 100644 src/erlang-oauth/oauth_rsa_sha1.erl + delete mode 100644 src/erlang-oauth/oauth_unix.erl + delete mode 100644 src/erlang-oauth/oauth_uri.erl diff --git a/configure b/configure index 5309998..39dda91 100755 @@ -66,6 +87,909 @@ index 54fdb7b..480da98 100644 all: all-recursive .SUFFIXES: +diff --git a/src/erlang-oauth/Makefile.am b/src/erlang-oauth/Makefile.am +deleted file mode 100644 +index 95e0898..0000000 +--- a/src/erlang-oauth/Makefile.am ++++ /dev/null +@@ -1,50 +0,0 @@ +-## Licensed under the Apache License, Version 2.0 (the "License"); you may not +-## use this file except in compliance with the License. You may obtain a copy +-## of the License at +-## +-## http://www.apache.org/licenses/LICENSE-2.0 +-## +-## Unless required by applicable law or agreed to in writing, software +-## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-## License for the specific language governing permissions and limitations under +-## the License. +- +-oauthebindir = $(localerlanglibdir)/erlang-oauth/ebin +- +-# Removed oauth_rsa_sha1.erl until we require R12B5 or +-# we add a ./configure option to enable it. +- +-oauth_file_collection = \ +- oauth.app.in \ +- oauth.erl \ +- oauth_hmac_sha1.erl \ +- oauth_http.erl \ +- oauth_plaintext.erl \ +- oauth_unix.erl \ +- oauth_uri.erl +- +-oauthebin_make_generated_file_list = \ +- oauth.app \ +- oauth.beam \ +- oauth_hmac_sha1.beam \ +- oauth_http.beam \ +- oauth_plaintext.beam \ +- oauth_unix.beam \ +- oauth_uri.beam +- +-oauthebin_DATA = \ +- $(oauthebin_make_generated_file_list) +- +-EXTRA_DIST = \ +- $(oauth_file_collection) \ +- oauth_rsa_sha1.erl +- +-CLEANFILES = \ +- $(oauthebin_make_generated_file_list) +- +-%.app: %.app.in +- cp $< $@ +- +-%.beam: %.erl +- $(ERLC) $(ERLC_FLAGS) $< +diff --git a/src/erlang-oauth/Makefile.in b/src/erlang-oauth/Makefile.in +deleted file mode 100644 +index 748abe9..0000000 +--- a/src/erlang-oauth/Makefile.in ++++ /dev/null +@@ -1,476 +0,0 @@ +-# Makefile.in generated by automake 1.11 from Makefile.am. +-# @configure_input@ +- +-# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +-# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +-# Inc. +-# This Makefile.in is free software; the Free Software Foundation +-# gives unlimited permission to copy and/or distribute it, +-# with or without modifications, as long as this notice is preserved. +- +-# This program is distributed in the hope that it will be useful, +-# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +-# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +-# PARTICULAR PURPOSE. +- +-@SET_MAKE@ +- +-VPATH = @srcdir@ +-pkgdatadir = $(datadir)/@PACKAGE@ +-pkgincludedir = $(includedir)/@PACKAGE@ +-pkglibdir = $(libdir)/@PACKAGE@ +-pkglibexecdir = $(libexecdir)/@PACKAGE@ +-am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +-install_sh_DATA = $(install_sh) -c -m 644 +-install_sh_PROGRAM = $(install_sh) -c +-install_sh_SCRIPT = $(install_sh) -c +-INSTALL_HEADER = $(INSTALL_DATA) +-transform = $(program_transform_name) +-NORMAL_INSTALL = : +-PRE_INSTALL = : +-POST_INSTALL = : +-NORMAL_UNINSTALL = : +-PRE_UNINSTALL = : +-POST_UNINSTALL = : +-build_triplet = @build@ +-host_triplet = @host@ +-subdir = src/erlang-oauth +-DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +-ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +-am__aclocal_m4_deps = $(top_srcdir)/m4/ac_check_curl.m4 \ +- $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4/ac_check_icu.m4 \ +- $(top_srcdir)/configure.ac +-am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ +- $(ACLOCAL_M4) +-mkinstalldirs = $(install_sh) -d +-CONFIG_HEADER = $(top_builddir)/config.h +-CONFIG_CLEAN_FILES = +-CONFIG_CLEAN_VPATH_FILES = +-SOURCES = +-DIST_SOURCES = +-am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +-am__vpath_adj = case $$p in \ +- $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ +- *) f=$$p;; \ +- esac; +-am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +-am__install_max = 40 +-am__nobase_strip_setup = \ +- srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +-am__nobase_strip = \ +- for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +-am__nobase_list = $(am__nobase_strip_setup); \ +- for p in $$list; do echo "$$p $$p"; done | \ +- sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ +- $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ +- if (++n[$$2] == $(am__install_max)) \ +- { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ +- END { for (dir in files) print dir, files[dir] }' +-am__base_list = \ +- sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ +- sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +-am__installdirs = "$(DESTDIR)$(oauthebindir)" +-DATA = $(oauthebin_DATA) +-DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +-ACLOCAL = @ACLOCAL@ +-AMTAR = @AMTAR@ +-AR = @AR@ +-AUTOCONF = @AUTOCONF@ +-AUTOHEADER = @AUTOHEADER@ +-AUTOMAKE = @AUTOMAKE@ +-AWK = @AWK@ +-CC = @CC@ +-CCDEPMODE = @CCDEPMODE@ +-CFLAGS = @CFLAGS@ +-CPP = @CPP@ +-CPPFLAGS = @CPPFLAGS@ +-CURL_CFLAGS = @CURL_CFLAGS@ +-CURL_CONFIG = @CURL_CONFIG@ +-CURL_LDFLAGS = @CURL_LDFLAGS@ +-CURL_LIBS = @CURL_LIBS@ +-CYGPATH_W = @CYGPATH_W@ +-DEFS = @DEFS@ +-DEPDIR = @DEPDIR@ +-DSYMUTIL = @DSYMUTIL@ +-DUMPBIN = @DUMPBIN@ +-ECHO_C = @ECHO_C@ +-ECHO_N = @ECHO_N@ +-ECHO_T = @ECHO_T@ +-EGREP = @EGREP@ +-ERL = @ERL@ +-ERLC = @ERLC@ +-ERLC_FLAGS = @ERLC_FLAGS@ +-EXEEXT = @EXEEXT@ +-FGREP = @FGREP@ +-FLAGS = @FLAGS@ +-GREP = @GREP@ +-HELP2MAN_EXECUTABLE = @HELP2MAN_EXECUTABLE@ +-ICU_CFLAGS = @ICU_CFLAGS@ +-ICU_CONFIG = @ICU_CONFIG@ +-ICU_CXXFLAGS = @ICU_CXXFLAGS@ +-ICU_LIBS = @ICU_LIBS@ +-ICU_LOCAL_BIN = @ICU_LOCAL_BIN@ +-ICU_LOCAL_CFLAGS = @ICU_LOCAL_CFLAGS@ +-ICU_LOCAL_LDFLAGS = @ICU_LOCAL_LDFLAGS@ +-INSTALL = @INSTALL@ +-INSTALL_DATA = @INSTALL_DATA@ +-INSTALL_PROGRAM = @INSTALL_PROGRAM@ +-INSTALL_SCRIPT = @INSTALL_SCRIPT@ +-INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +-JSLIB = @JSLIB@ +-LD = @LD@ +-LDFLAGS = @LDFLAGS@ +-LIBOBJS = @LIBOBJS@ +-LIBS = @LIBS@ +-LIBTOOL = @LIBTOOL@ +-LIPO = @LIPO@ +-LN_S = @LN_S@ +-LTLIBOBJS = @LTLIBOBJS@ +-MAKEINFO = @MAKEINFO@ +-MKDIR_P = @MKDIR_P@ +-NM = @NM@ +-NMEDIT = @NMEDIT@ +-OBJDUMP = @OBJDUMP@ +-OBJEXT = @OBJEXT@ +-OTOOL = @OTOOL@ +-OTOOL64 = @OTOOL64@ +-PACKAGE = @PACKAGE@ +-PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +-PACKAGE_NAME = @PACKAGE_NAME@ +-PACKAGE_STRING = @PACKAGE_STRING@ +-PACKAGE_TARNAME = @PACKAGE_TARNAME@ +-PACKAGE_URL = @PACKAGE_URL@ +-PACKAGE_VERSION = @PACKAGE_VERSION@ +-PATH_SEPARATOR = @PATH_SEPARATOR@ +-RANLIB = @RANLIB@ +-SED = @SED@ +-SET_MAKE = @SET_MAKE@ +-SHELL = @SHELL@ +-STRIP = @STRIP@ +-VERSION = @VERSION@ +-abs_builddir = @abs_builddir@ +-abs_srcdir = @abs_srcdir@ +-abs_top_builddir = @abs_top_builddir@ +-abs_top_srcdir = @abs_top_srcdir@ +-ac_ct_CC = @ac_ct_CC@ +-ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +-am__include = @am__include@ +-am__leading_dot = @am__leading_dot@ +-am__quote = @am__quote@ +-am__tar = @am__tar@ +-am__untar = @am__untar@ +-bindir = @bindir@ +-bug_uri = @bug_uri@ +-build = @build@ +-build_alias = @build_alias@ +-build_cpu = @build_cpu@ +-build_os = @build_os@ +-build_vendor = @build_vendor@ +-builddir = @builddir@ +-datadir = @datadir@ +-datarootdir = @datarootdir@ +-docdir = @docdir@ +-dvidir = @dvidir@ +-exec_prefix = @exec_prefix@ +-host = @host@ +-host_alias = @host_alias@ +-host_cpu = @host_cpu@ +-host_os = @host_os@ +-host_vendor = @host_vendor@ +-htmldir = @htmldir@ +-includedir = @includedir@ +-infodir = @infodir@ +-initdir = @initdir@ +-install_sh = @install_sh@ +-launchddir = @launchddir@ +-libdir = @libdir@ +-libexecdir = @libexecdir@ +-localconfdir = @localconfdir@ +-localdatadir = @localdatadir@ +-localdocdir = @localdocdir@ +-localedir = @localedir@ +-localerlanglibdir = @localerlanglibdir@ +-locallibbindir = @locallibbindir@ +-locallibdir = @locallibdir@ +-localstatedir = @localstatedir@ +-localstatelibdir = @localstatelibdir@ +-localstatelogdir = @localstatelogdir@ +-localstaterundir = @localstaterundir@ +-lt_ECHO = @lt_ECHO@ +-mandir = @mandir@ +-mkdir_p = @mkdir_p@ +-oldincludedir = @oldincludedir@ +-package_author_address = @package_author_address@ +-package_author_name = @package_author_name@ +-package_identifier = @package_identifier@ +-package_name = @package_name@ +-package_tarname = @package_tarname@ +-pdfdir = @pdfdir@ +-prefix = @prefix@ +-program_transform_name = @program_transform_name@ +-psdir = @psdir@ +-sbindir = @sbindir@ +-sharedstatedir = @sharedstatedir@ +-srcdir = @srcdir@ +-sysconfdir = @sysconfdir@ +-target_alias = @target_alias@ +-top_build_prefix = @top_build_prefix@ +-top_builddir = @top_builddir@ +-top_srcdir = @top_srcdir@ +-version = @version@ +-version_major = @version_major@ +-version_minor = @version_minor@ +-version_release = @version_release@ +-version_revision = @version_revision@ +-version_stage = @version_stage@ +-oauthebindir = $(localerlanglibdir)/erlang-oauth/ebin +- +-# Removed oauth_rsa_sha1.erl until we require R12B5 or +-# we add a ./configure option to enable it. +-oauth_file_collection = \ +- oauth.app.in \ +- oauth.erl \ +- oauth_hmac_sha1.erl \ +- oauth_http.erl \ +- oauth_plaintext.erl \ +- oauth_unix.erl \ +- oauth_uri.erl +- +-oauthebin_make_generated_file_list = \ +- oauth.app \ +- oauth.beam \ +- oauth_hmac_sha1.beam \ +- oauth_http.beam \ +- oauth_plaintext.beam \ +- oauth_unix.beam \ +- oauth_uri.beam +- +-oauthebin_DATA = \ +- $(oauthebin_make_generated_file_list) +- +-EXTRA_DIST = \ +- $(oauth_file_collection) \ +- oauth_rsa_sha1.erl +- +-CLEANFILES = \ +- $(oauthebin_make_generated_file_list) +- +-all: all-am +- +-.SUFFIXES: +-$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) +- @for dep in $?; do \ +- case '$(am__configure_deps)' in \ +- *$$dep*) \ +- ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ +- && { if test -f $@; then exit 0; else break; fi; }; \ +- exit 1;; \ +- esac; \ +- done; \ +- echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/erlang-oauth/Makefile'; \ +- $(am__cd) $(top_srcdir) && \ +- $(AUTOMAKE) --foreign src/erlang-oauth/Makefile +-.PRECIOUS: Makefile +-Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status +- @case '$?' in \ +- *config.status*) \ +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ +- *) \ +- echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ +- cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ +- esac; +- +-$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +- +-$(top_srcdir)/configure: $(am__configure_deps) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +-$(ACLOCAL_M4): $(am__aclocal_m4_deps) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +-$(am__aclocal_m4_deps): +- +-mostlyclean-libtool: +- -rm -f *.lo +- +-clean-libtool: +- -rm -rf .libs _libs +-install-oauthebinDATA: $(oauthebin_DATA) +- @$(NORMAL_INSTALL) +- test -z "$(oauthebindir)" || $(MKDIR_P) "$(DESTDIR)$(oauthebindir)" +- @list='$(oauthebin_DATA)'; test -n "$(oauthebindir)" || list=; \ +- for p in $$list; do \ +- if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ +- echo "$$d$$p"; \ +- done | $(am__base_list) | \ +- while read files; do \ +- echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(oauthebindir)'"; \ +- $(INSTALL_DATA) $$files "$(DESTDIR)$(oauthebindir)" || exit $$?; \ +- done +- +-uninstall-oauthebinDATA: +- @$(NORMAL_UNINSTALL) +- @list='$(oauthebin_DATA)'; test -n "$(oauthebindir)" || list=; \ +- files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ +- test -n "$$files" || exit 0; \ +- echo " ( cd '$(DESTDIR)$(oauthebindir)' && rm -f" $$files ")"; \ +- cd "$(DESTDIR)$(oauthebindir)" && rm -f $$files +-tags: TAGS +-TAGS: +- +-ctags: CTAGS +-CTAGS: +- +- +-distdir: $(DISTFILES) +- @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +- topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +- list='$(DISTFILES)'; \ +- dist_files=`for file in $$list; do echo $$file; done | \ +- sed -e "s|^$$srcdirstrip/||;t" \ +- -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ +- case $$dist_files in \ +- */*) $(MKDIR_P) `echo "$$dist_files" | \ +- sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ +- sort -u` ;; \ +- esac; \ +- for file in $$dist_files; do \ +- if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ +- if test -d $$d/$$file; then \ +- dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ +- if test -d "$(distdir)/$$file"; then \ +- find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +- fi; \ +- if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ +- cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ +- find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +- fi; \ +- cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ +- else \ +- test -f "$(distdir)/$$file" \ +- || cp -p $$d/$$file "$(distdir)/$$file" \ +- || exit 1; \ +- fi; \ +- done +-check-am: all-am +-check: check-am +-all-am: Makefile $(DATA) +-installdirs: +- for dir in "$(DESTDIR)$(oauthebindir)"; do \ +- test -z "$$dir" || $(MKDIR_P) "$$dir"; \ +- done +-install: install-am +-install-exec: install-exec-am +-install-data: install-data-am +-uninstall: uninstall-am +- +-install-am: all-am +- @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am +- +-installcheck: installcheck-am +-install-strip: +- $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ +- install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ +- `test -z '$(STRIP)' || \ +- echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +-mostlyclean-generic: +- +-clean-generic: +- -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) +- +-distclean-generic: +- -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) +- -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) +- +-maintainer-clean-generic: +- @echo "This command is intended for maintainers to use" +- @echo "it deletes files that may require special tools to rebuild." +-clean: clean-am +- +-clean-am: clean-generic clean-libtool mostlyclean-am +- +-distclean: distclean-am +- -rm -f Makefile +-distclean-am: clean-am distclean-generic +- +-dvi: dvi-am +- +-dvi-am: +- +-html: html-am +- +-html-am: +- +-info: info-am +- +-info-am: +- +-install-data-am: install-oauthebinDATA +- +-install-dvi: install-dvi-am +- +-install-dvi-am: +- +-install-exec-am: +- +-install-html: install-html-am +- +-install-html-am: +- +-install-info: install-info-am +- +-install-info-am: +- +-install-man: +- +-install-pdf: install-pdf-am +- +-install-pdf-am: +- +-install-ps: install-ps-am +- +-install-ps-am: +- +-installcheck-am: +- +-maintainer-clean: maintainer-clean-am +- -rm -f Makefile +-maintainer-clean-am: distclean-am maintainer-clean-generic +- +-mostlyclean: mostlyclean-am +- +-mostlyclean-am: mostlyclean-generic mostlyclean-libtool +- +-pdf: pdf-am +- +-pdf-am: +- +-ps: ps-am +- +-ps-am: +- +-uninstall-am: uninstall-oauthebinDATA +- +-.MAKE: install-am install-strip +- +-.PHONY: all all-am check check-am clean clean-generic clean-libtool \ +- distclean distclean-generic distclean-libtool distdir dvi \ +- dvi-am html html-am info info-am install install-am \ +- install-data install-data-am install-dvi install-dvi-am \ +- install-exec install-exec-am install-html install-html-am \ +- install-info install-info-am install-man install-oauthebinDATA \ +- install-pdf install-pdf-am install-ps install-ps-am \ +- install-strip installcheck installcheck-am installdirs \ +- maintainer-clean maintainer-clean-generic mostlyclean \ +- mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ +- uninstall uninstall-am uninstall-oauthebinDATA +- +- +-%.app: %.app.in +- cp $< $@ +- +-%.beam: %.erl +- $(ERLC) $(ERLC_FLAGS) $< +- +-# Tell versions [3.59,3.63) of GNU make to not export all variables. +-# Otherwise a system limit (for SysV at least) may be exceeded. +-.NOEXPORT: +diff --git a/src/erlang-oauth/oauth.app.in b/src/erlang-oauth/oauth.app.in +deleted file mode 100644 +index 6357b9b..0000000 +--- a/src/erlang-oauth/oauth.app.in ++++ /dev/null +@@ -1,20 +0,0 @@ +-{application, oauth, [ +- {description, "Erlang OAuth implementation"}, +- {vsn, "dev"}, +- {modules, [ +- oauth, +- oauth_hmac_sha1, +- oauth_http, +- oauth_plaintext, +- oauth_rsa_sha1, +- oauth_unix, +- oauth_uri +- ]}, +- {registered, []}, +- {applications, [ +- kernel, +- stdlib, +- crypto, +- inets +- ]} +-]}. +diff --git a/src/erlang-oauth/oauth.erl b/src/erlang-oauth/oauth.erl +deleted file mode 100644 +index 866655c..0000000 +--- a/src/erlang-oauth/oauth.erl ++++ /dev/null +@@ -1,107 +0,0 @@ +--module(oauth). +- +--export( +- [ get/5 +- , header/1 +- , post/5 +- , signature/5 +- , signature_base_string/3 +- , signed_params/6 +- , token/1 +- , token_secret/1 +- , uri/2 +- , verify/6 +- ]). +- +- +-get(URL, ExtraParams, Consumer, Token, TokenSecret) -> +- SignedParams = signed_params("GET", URL, ExtraParams, Consumer, Token, TokenSecret), +- oauth_http:get(uri(URL, SignedParams)). +- +-post(URL, ExtraParams, Consumer, Token, TokenSecret) -> +- SignedParams = signed_params("POST", URL, ExtraParams, Consumer, Token, TokenSecret), +- oauth_http:post(URL, oauth_uri:params_to_string(SignedParams)). +- +-uri(Base, []) -> +- Base; +-uri(Base, Params) -> +- lists:concat([Base, "?", oauth_uri:params_to_string(Params)]). +- +-header(Params) -> +- {"Authorization", "OAuth " ++ oauth_uri:params_to_header_string(Params)}. +- +-token(Params) -> +- proplists:get_value("oauth_token", Params). +- +-token_secret(Params) -> +- proplists:get_value("oauth_token_secret", Params). +- +-verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret) -> +- case signature_method(Consumer) of +- plaintext -> +- oauth_plaintext:verify(Signature, consumer_secret(Consumer), TokenSecret); +- hmac_sha1 -> +- BaseString = signature_base_string(HttpMethod, URL, Params), +- oauth_hmac_sha1:verify(Signature, BaseString, consumer_secret(Consumer), TokenSecret); +- rsa_sha1 -> +- BaseString = signature_base_string(HttpMethod, URL, Params), +- oauth_rsa_sha1:verify(Signature, BaseString, consumer_secret(Consumer)) +- end. +- +-signed_params(HttpMethod, URL, ExtraParams, Consumer, Token, TokenSecret) -> +- Params = token_param(Token, params(Consumer, ExtraParams)), +- [{"oauth_signature", signature(HttpMethod, URL, Params, Consumer, TokenSecret)}|Params]. +- +-signature(HttpMethod, URL, Params, Consumer, TokenSecret) -> +- case signature_method(Consumer) of +- plaintext -> +- oauth_plaintext:signature(consumer_secret(Consumer), TokenSecret); +- hmac_sha1 -> +- BaseString = signature_base_string(HttpMethod, URL, Params), +- oauth_hmac_sha1:signature(BaseString, consumer_secret(Consumer), TokenSecret); +- rsa_sha1 -> +- BaseString = signature_base_string(HttpMethod, URL, Params), +- oauth_rsa_sha1:signature(BaseString, consumer_secret(Consumer)) +- end. +- +-signature_base_string(HttpMethod, URL, Params) -> +- NormalizedURL = oauth_uri:normalize(URL), +- NormalizedParams = oauth_uri:params_to_string(lists:sort(Params)), +- oauth_uri:calate("&", [HttpMethod, NormalizedURL, NormalizedParams]). +- +-token_param("", Params) -> +- Params; +-token_param(Token, Params) -> +- [{"oauth_token", Token}|Params]. +- +-params(Consumer, Params) -> +- Nonce = base64:encode_to_string(crypto:rand_bytes(32)), % cf. ruby-oauth +- params(Consumer, oauth_unix:timestamp(), Nonce, Params). +- +-params(Consumer, Timestamp, Nonce, Params) -> +- [ {"oauth_version", "1.0"} +- , {"oauth_nonce", Nonce} +- , {"oauth_timestamp", integer_to_list(Timestamp)} +- , {"oauth_signature_method", signature_method_string(Consumer)} +- , {"oauth_consumer_key", consumer_key(Consumer)} +- | Params +- ]. +- +-signature_method_string(Consumer) -> +- case signature_method(Consumer) of +- plaintext -> +- "PLAINTEXT"; +- hmac_sha1 -> +- "HMAC-SHA1"; +- rsa_sha1 -> +- "RSA-SHA1" +- end. +- +-signature_method(_Consumer={_, _, Method}) -> +- Method. +- +-consumer_secret(_Consumer={_, Secret, _}) -> +- Secret. +- +-consumer_key(_Consumer={Key, _, _}) -> +- Key. +diff --git a/src/erlang-oauth/oauth_hmac_sha1.erl b/src/erlang-oauth/oauth_hmac_sha1.erl +deleted file mode 100644 +index 79d59f3..0000000 +--- a/src/erlang-oauth/oauth_hmac_sha1.erl ++++ /dev/null +@@ -1,11 +0,0 @@ +--module(oauth_hmac_sha1). +- +--export([signature/3, verify/4]). +- +- +-signature(BaseString, CS, TS) -> +- Key = oauth_uri:calate("&", [CS, TS]), +- base64:encode_to_string(crypto:sha_mac(Key, BaseString)). +- +-verify(Signature, BaseString, CS, TS) -> +- couch_util:verify(signature(BaseString, CS, TS), Signature). +diff --git a/src/erlang-oauth/oauth_http.erl b/src/erlang-oauth/oauth_http.erl +deleted file mode 100644 +index bf5a4ba..0000000 +--- a/src/erlang-oauth/oauth_http.erl ++++ /dev/null +@@ -1,22 +0,0 @@ +--module(oauth_http). +- +--export([get/1, post/2, response_params/1, response_body/1, response_code/1]). +- +- +-get(URL) -> +- request(get, {URL, []}). +- +-post(URL, Data) -> +- request(post, {URL, [], "application/x-www-form-urlencoded", Data}). +- +-request(Method, Request) -> +- http:request(Method, Request, [{autoredirect, false}], []). +- +-response_params(Response) -> +- oauth_uri:params_from_string(response_body(Response)). +- +-response_body({{_, _, _}, _, Body}) -> +- Body. +- +-response_code({{_, Code, _}, _, _}) -> +- Code. +diff --git a/src/erlang-oauth/oauth_plaintext.erl b/src/erlang-oauth/oauth_plaintext.erl +deleted file mode 100644 +index 41a1e9b..0000000 +--- a/src/erlang-oauth/oauth_plaintext.erl ++++ /dev/null +@@ -1,10 +0,0 @@ +--module(oauth_plaintext). +- +--export([signature/2, verify/3]). +- +- +-signature(CS, TS) -> +- oauth_uri:calate("&", [CS, TS]). +- +-verify(Signature, CS, TS) -> +- couch_util:verify(signature(CS, TS), Signature). +diff --git a/src/erlang-oauth/oauth_rsa_sha1.erl b/src/erlang-oauth/oauth_rsa_sha1.erl +deleted file mode 100644 +index 6f4828e..0000000 +--- a/src/erlang-oauth/oauth_rsa_sha1.erl ++++ /dev/null +@@ -1,30 +0,0 @@ +--module(oauth_rsa_sha1). +- +--export([signature/2, verify/3]). +- +--include_lib("public_key/include/public_key.hrl"). +- +- +-signature(BaseString, PrivateKeyPath) -> +- {ok, [Info]} = public_key:pem_to_der(PrivateKeyPath), +- {ok, PrivateKey} = public_key:decode_private_key(Info), +- base64:encode_to_string(public_key:sign(list_to_binary(BaseString), PrivateKey)). +- +-verify(Signature, BaseString, PublicKey) -> +- public_key:verify_signature(to_binary(BaseString), sha, base64:decode(Signature), public_key(PublicKey)). +- +-to_binary(Term) when is_list(Term) -> +- list_to_binary(Term); +-to_binary(Term) when is_binary(Term) -> +- Term. +- +-public_key(Path) when is_list(Path) -> +- {ok, [{cert, DerCert, not_encrypted}]} = public_key:pem_to_der(Path), +- {ok, Cert} = public_key:pkix_decode_cert(DerCert, otp), +- public_key(Cert); +-public_key(#'OTPCertificate'{tbsCertificate=Cert}) -> +- public_key(Cert); +-public_key(#'OTPTBSCertificate'{subjectPublicKeyInfo=Info}) -> +- public_key(Info); +-public_key(#'OTPSubjectPublicKeyInfo'{subjectPublicKey=Key}) -> +- Key. +diff --git a/src/erlang-oauth/oauth_unix.erl b/src/erlang-oauth/oauth_unix.erl +deleted file mode 100644 +index 73ca314..0000000 +--- a/src/erlang-oauth/oauth_unix.erl ++++ /dev/null +@@ -1,16 +0,0 @@ +--module(oauth_unix). +- +--export([timestamp/0]). +- +- +-timestamp() -> +- timestamp(calendar:universal_time()). +- +-timestamp(DateTime) -> +- seconds(DateTime) - epoch(). +- +-epoch() -> +- seconds({{1970,1,1},{00,00,00}}). +- +-seconds(DateTime) -> +- calendar:datetime_to_gregorian_seconds(DateTime). +diff --git a/src/erlang-oauth/oauth_uri.erl b/src/erlang-oauth/oauth_uri.erl +deleted file mode 100644 +index fb27ae7..0000000 +--- a/src/erlang-oauth/oauth_uri.erl ++++ /dev/null +@@ -1,88 +0,0 @@ +--module(oauth_uri). +- +--export([normalize/1, calate/2, encode/1]). +--export([params_from_string/1, params_to_string/1, +- params_from_header_string/1, params_to_header_string/1]). +- +--import(lists, [concat/1]). +- +--define(is_uppercase_alpha(C), C >= $A, C =< $Z). +--define(is_lowercase_alpha(C), C >= $a, C =< $z). +--define(is_alpha(C), ?is_uppercase_alpha(C); ?is_lowercase_alpha(C)). +--define(is_digit(C), C >= $0, C =< $9). +--define(is_alphanumeric(C), ?is_alpha(C); ?is_digit(C)). +--define(is_unreserved(C), ?is_alphanumeric(C); C =:= $-; C =:= $_; C =:= $.; C =:= $~). +--define(is_hex(C), ?is_digit(C); C >= $A, C =< $F). +- +- +-normalize(URI) -> +- case http_uri:parse(URI) of +- {Scheme, UserInfo, Host, Port, Path, _Query} -> +- normalize(Scheme, UserInfo, string:to_lower(Host), Port, [Path]); +- Else -> +- Else +- end. +- +-normalize(http, UserInfo, Host, 80, Acc) -> +- normalize(http, UserInfo, [Host|Acc]); +-normalize(https, UserInfo, Host, 443, Acc) -> +- normalize(https, UserInfo, [Host|Acc]); +-normalize(Scheme, UserInfo, Host, Port, Acc) -> +- normalize(Scheme, UserInfo, [Host, ":", Port|Acc]). +- +-normalize(Scheme, [], Acc) -> +- concat([Scheme, "://"|Acc]); +-normalize(Scheme, UserInfo, Acc) -> +- concat([Scheme, "://", UserInfo, "@"|Acc]). +- +-params_to_header_string(Params) -> +- intercalate(", ", [concat([encode(K), "=\"", encode(V), "\""]) || {K, V} <- Params]). +- +-params_from_header_string(String) -> +- [param_from_header_string(Param) || Param <- re:split(String, ",\\s*", [{return, list}]), Param =/= ""]. +- +-param_from_header_string(Param) -> +- [Key, QuotedValue] = string:tokens(Param, "="), +- Value = string:substr(QuotedValue, 2, length(QuotedValue) - 2), +- {decode(Key), decode(Value)}. +- +-params_from_string(Params) -> +- [param_from_string(Param) || Param <- string:tokens(Params, "&")]. +- +-param_from_string(Param) -> +- list_to_tuple([decode(Value) || Value <- string:tokens(Param, "=")]). +- +-params_to_string(Params) -> +- intercalate("&", [calate("=", [K, V]) || {K, V} <- Params]). +- +-calate(Sep, Xs) -> +- intercalate(Sep, [encode(X) || X <- Xs]). +- +-intercalate(Sep, Xs) -> +- concat(intersperse(Sep, Xs)). +- +-intersperse(_, []) -> []; +-intersperse(_, [X]) -> [X]; +-intersperse(Sep, [X|Xs]) -> +- [X, Sep|intersperse(Sep, Xs)]. +- +-decode(Chars) -> +- decode(Chars, []). +- +-decode([], Decoded) -> +- lists:reverse(Decoded); +-decode([$%,A,B|Etc], Decoded) when ?is_hex(A), ?is_hex(B) -> +- decode(Etc, [erlang:list_to_integer([A,B], 16)|Decoded]); +-decode([C|Etc], Decoded) when ?is_unreserved(C) -> +- decode(Etc, [C|Decoded]). +- +-encode(Chars) -> +- encode(Chars, []). +- +-encode([], Encoded) -> +- lists:flatten(lists:reverse(Encoded)); +-encode([C|Etc], Encoded) when ?is_unreserved(C) -> +- encode(Etc, [C|Encoded]); +-encode([C|Etc], Encoded) -> +- Value = io_lib:format("%~2.1.0s", [erlang:integer_to_list(C, 16)]), +- encode(Etc, [Value|Encoded]). +diff --git a/test/etap/test_util.erl.in b/test/etap/test_util.erl.in +index 4c42edb..79b0417 100644 +--- a/test/etap/test_util.erl.in ++++ b/test/etap/test_util.erl.in +@@ -22,7 +22,7 @@ builddir() -> + "@abs_top_builddir@". + + init_code_path() -> +- Paths = ["etap", "couchdb", "erlang-oauth", "ibrowse", "mochiweb"], ++ Paths = ["etap", "couchdb", "ibrowse", "mochiweb"], + lists:foreach(fun(Name) -> + code:add_pathz(filename:join([builddir(), "src", Name])) + end, Paths). -- 1.6.6.1 diff --git a/couchdb-0004-Remove-bundled-erlang-etap-library.patch b/couchdb-0004-Remove-bundled-erlang-etap-library.patch index bfdce85..68f3b4d 100644 --- a/couchdb-0004-Remove-bundled-erlang-etap-library.patch +++ b/couchdb-0004-Remove-bundled-erlang-etap-library.patch @@ -1,14 +1,37 @@ -From c2946e7f2cd60daf7cd75491caf5108161db45be Mon Sep 17 00:00:00 2001 +From 4312a2e8f45586ea2d6f2794854100da6f4092b7 Mon Sep 17 00:00:00 2001 From: Peter Lemenkov Date: Thu, 27 May 2010 15:22:37 +0400 -Subject: [PATCH 4/4] Remove bundled erlang-etap library +Subject: [PATCH 4/7] Remove bundled erlang-etap library --- - configure | 3 --- - configure.ac | 1 - - src/Makefile.am | 2 +- - src/Makefile.in | 2 +- - 4 files changed, 2 insertions(+), 6 deletions(-) + configure | 3 - + configure.ac | 1 - + src/Makefile.am | 2 +- + src/Makefile.in | 2 +- + src/etap/Makefile.am | 44 ---- + src/etap/Makefile.in | 467 ----------------------------------------- + src/etap/etap.erl | 416 ------------------------------------ + src/etap/etap_application.erl | 72 ------- + src/etap/etap_can.erl | 79 ------- + src/etap/etap_exception.erl | 66 ------ + src/etap/etap_process.erl | 42 ---- + src/etap/etap_report.erl | 343 ------------------------------ + src/etap/etap_request.erl | 89 -------- + src/etap/etap_string.erl | 47 ---- + src/etap/etap_web.erl | 65 ------ + test/etap/test_util.erl.in | 2 +- + 16 files changed, 3 insertions(+), 1737 deletions(-) + delete mode 100644 src/etap/Makefile.am + delete mode 100644 src/etap/Makefile.in + delete mode 100644 src/etap/etap.erl + delete mode 100644 src/etap/etap_application.erl + delete mode 100644 src/etap/etap_can.erl + delete mode 100644 src/etap/etap_exception.erl + delete mode 100644 src/etap/etap_process.erl + delete mode 100644 src/etap/etap_report.erl + delete mode 100644 src/etap/etap_request.erl + delete mode 100644 src/etap/etap_string.erl + delete mode 100644 src/etap/etap_web.erl diff --git a/configure b/configure index 39dda91..ca7732f 100755 @@ -66,6 +89,1815 @@ index 480da98..2422e39 100644 all: all-recursive .SUFFIXES: +diff --git a/src/etap/Makefile.am b/src/etap/Makefile.am +deleted file mode 100644 +index 732347b..0000000 +--- a/src/etap/Makefile.am ++++ /dev/null +@@ -1,44 +0,0 @@ +-## Licensed under the Apache License, Version 2.0 (the "License"); you may not +-## use this file except in compliance with the License. You may obtain a copy +-## of the License at +-## +-## http://www.apache.org/licenses/LICENSE-2.0 +-## +-## Unless required by applicable law or agreed to in writing, software +-## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-## License for the specific language governing permissions and limitations under +-## the License. +- +-etapebindir = $(localerlanglibdir)/etap/ebin +- +-etap_file_collection = \ +- etap.erl \ +- etap_application.erl \ +- etap_can.erl \ +- etap_exception.erl \ +- etap_process.erl \ +- etap_report.erl \ +- etap_request.erl \ +- etap_string.erl \ +- etap_web.erl +- +-etapebin_make_generated_file_list = \ +- etap.beam \ +- etap_application.beam \ +- etap_can.beam \ +- etap_exception.beam \ +- etap_process.beam \ +- etap_report.beam \ +- etap_request.beam \ +- etap_string.beam \ +- etap_web.beam +- +-etapebin_DATA = $(etapebin_make_generated_file_list) +- +-EXTRA_DIST = $(etap_file_collection) +- +-CLEANFILES = $(etapebin_make_generated_file_list) +- +-%.beam: %.erl +- $(ERLC) $(ERLC_FLAGS) $< +diff --git a/src/etap/Makefile.in b/src/etap/Makefile.in +deleted file mode 100644 +index 2c752b3..0000000 +--- a/src/etap/Makefile.in ++++ /dev/null +@@ -1,467 +0,0 @@ +-# Makefile.in generated by automake 1.11 from Makefile.am. +-# @configure_input@ +- +-# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +-# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +-# Inc. +-# This Makefile.in is free software; the Free Software Foundation +-# gives unlimited permission to copy and/or distribute it, +-# with or without modifications, as long as this notice is preserved. +- +-# This program is distributed in the hope that it will be useful, +-# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +-# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +-# PARTICULAR PURPOSE. +- +-@SET_MAKE@ +- +-VPATH = @srcdir@ +-pkgdatadir = $(datadir)/@PACKAGE@ +-pkgincludedir = $(includedir)/@PACKAGE@ +-pkglibdir = $(libdir)/@PACKAGE@ +-pkglibexecdir = $(libexecdir)/@PACKAGE@ +-am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +-install_sh_DATA = $(install_sh) -c -m 644 +-install_sh_PROGRAM = $(install_sh) -c +-install_sh_SCRIPT = $(install_sh) -c +-INSTALL_HEADER = $(INSTALL_DATA) +-transform = $(program_transform_name) +-NORMAL_INSTALL = : +-PRE_INSTALL = : +-POST_INSTALL = : +-NORMAL_UNINSTALL = : +-PRE_UNINSTALL = : +-POST_UNINSTALL = : +-build_triplet = @build@ +-host_triplet = @host@ +-subdir = src/etap +-DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +-ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +-am__aclocal_m4_deps = $(top_srcdir)/m4/ac_check_curl.m4 \ +- $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4/ac_check_icu.m4 \ +- $(top_srcdir)/configure.ac +-am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ +- $(ACLOCAL_M4) +-mkinstalldirs = $(install_sh) -d +-CONFIG_HEADER = $(top_builddir)/config.h +-CONFIG_CLEAN_FILES = +-CONFIG_CLEAN_VPATH_FILES = +-SOURCES = +-DIST_SOURCES = +-am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +-am__vpath_adj = case $$p in \ +- $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ +- *) f=$$p;; \ +- esac; +-am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +-am__install_max = 40 +-am__nobase_strip_setup = \ +- srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +-am__nobase_strip = \ +- for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +-am__nobase_list = $(am__nobase_strip_setup); \ +- for p in $$list; do echo "$$p $$p"; done | \ +- sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ +- $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ +- if (++n[$$2] == $(am__install_max)) \ +- { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ +- END { for (dir in files) print dir, files[dir] }' +-am__base_list = \ +- sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ +- sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +-am__installdirs = "$(DESTDIR)$(etapebindir)" +-DATA = $(etapebin_DATA) +-DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +-ACLOCAL = @ACLOCAL@ +-AMTAR = @AMTAR@ +-AR = @AR@ +-AUTOCONF = @AUTOCONF@ +-AUTOHEADER = @AUTOHEADER@ +-AUTOMAKE = @AUTOMAKE@ +-AWK = @AWK@ +-CC = @CC@ +-CCDEPMODE = @CCDEPMODE@ +-CFLAGS = @CFLAGS@ +-CPP = @CPP@ +-CPPFLAGS = @CPPFLAGS@ +-CURL_CFLAGS = @CURL_CFLAGS@ +-CURL_CONFIG = @CURL_CONFIG@ +-CURL_LDFLAGS = @CURL_LDFLAGS@ +-CURL_LIBS = @CURL_LIBS@ +-CYGPATH_W = @CYGPATH_W@ +-DEFS = @DEFS@ +-DEPDIR = @DEPDIR@ +-DSYMUTIL = @DSYMUTIL@ +-DUMPBIN = @DUMPBIN@ +-ECHO_C = @ECHO_C@ +-ECHO_N = @ECHO_N@ +-ECHO_T = @ECHO_T@ +-EGREP = @EGREP@ +-ERL = @ERL@ +-ERLC = @ERLC@ +-ERLC_FLAGS = @ERLC_FLAGS@ +-EXEEXT = @EXEEXT@ +-FGREP = @FGREP@ +-FLAGS = @FLAGS@ +-GREP = @GREP@ +-HELP2MAN_EXECUTABLE = @HELP2MAN_EXECUTABLE@ +-ICU_CFLAGS = @ICU_CFLAGS@ +-ICU_CONFIG = @ICU_CONFIG@ +-ICU_CXXFLAGS = @ICU_CXXFLAGS@ +-ICU_LIBS = @ICU_LIBS@ +-ICU_LOCAL_BIN = @ICU_LOCAL_BIN@ +-ICU_LOCAL_CFLAGS = @ICU_LOCAL_CFLAGS@ +-ICU_LOCAL_LDFLAGS = @ICU_LOCAL_LDFLAGS@ +-INSTALL = @INSTALL@ +-INSTALL_DATA = @INSTALL_DATA@ +-INSTALL_PROGRAM = @INSTALL_PROGRAM@ +-INSTALL_SCRIPT = @INSTALL_SCRIPT@ +-INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +-JSLIB = @JSLIB@ +-LD = @LD@ +-LDFLAGS = @LDFLAGS@ +-LIBOBJS = @LIBOBJS@ +-LIBS = @LIBS@ +-LIBTOOL = @LIBTOOL@ +-LIPO = @LIPO@ +-LN_S = @LN_S@ +-LTLIBOBJS = @LTLIBOBJS@ +-MAKEINFO = @MAKEINFO@ +-MKDIR_P = @MKDIR_P@ +-NM = @NM@ +-NMEDIT = @NMEDIT@ +-OBJDUMP = @OBJDUMP@ +-OBJEXT = @OBJEXT@ +-OTOOL = @OTOOL@ +-OTOOL64 = @OTOOL64@ +-PACKAGE = @PACKAGE@ +-PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +-PACKAGE_NAME = @PACKAGE_NAME@ +-PACKAGE_STRING = @PACKAGE_STRING@ +-PACKAGE_TARNAME = @PACKAGE_TARNAME@ +-PACKAGE_URL = @PACKAGE_URL@ +-PACKAGE_VERSION = @PACKAGE_VERSION@ +-PATH_SEPARATOR = @PATH_SEPARATOR@ +-RANLIB = @RANLIB@ +-SED = @SED@ +-SET_MAKE = @SET_MAKE@ +-SHELL = @SHELL@ +-STRIP = @STRIP@ +-VERSION = @VERSION@ +-abs_builddir = @abs_builddir@ +-abs_srcdir = @abs_srcdir@ +-abs_top_builddir = @abs_top_builddir@ +-abs_top_srcdir = @abs_top_srcdir@ +-ac_ct_CC = @ac_ct_CC@ +-ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +-am__include = @am__include@ +-am__leading_dot = @am__leading_dot@ +-am__quote = @am__quote@ +-am__tar = @am__tar@ +-am__untar = @am__untar@ +-bindir = @bindir@ +-bug_uri = @bug_uri@ +-build = @build@ +-build_alias = @build_alias@ +-build_cpu = @build_cpu@ +-build_os = @build_os@ +-build_vendor = @build_vendor@ +-builddir = @builddir@ +-datadir = @datadir@ +-datarootdir = @datarootdir@ +-docdir = @docdir@ +-dvidir = @dvidir@ +-exec_prefix = @exec_prefix@ +-host = @host@ +-host_alias = @host_alias@ +-host_cpu = @host_cpu@ +-host_os = @host_os@ +-host_vendor = @host_vendor@ +-htmldir = @htmldir@ +-includedir = @includedir@ +-infodir = @infodir@ +-initdir = @initdir@ +-install_sh = @install_sh@ +-launchddir = @launchddir@ +-libdir = @libdir@ +-libexecdir = @libexecdir@ +-localconfdir = @localconfdir@ +-localdatadir = @localdatadir@ +-localdocdir = @localdocdir@ +-localedir = @localedir@ +-localerlanglibdir = @localerlanglibdir@ +-locallibbindir = @locallibbindir@ +-locallibdir = @locallibdir@ +-localstatedir = @localstatedir@ +-localstatelibdir = @localstatelibdir@ +-localstatelogdir = @localstatelogdir@ +-localstaterundir = @localstaterundir@ +-lt_ECHO = @lt_ECHO@ +-mandir = @mandir@ +-mkdir_p = @mkdir_p@ +-oldincludedir = @oldincludedir@ +-package_author_address = @package_author_address@ +-package_author_name = @package_author_name@ +-package_identifier = @package_identifier@ +-package_name = @package_name@ +-package_tarname = @package_tarname@ +-pdfdir = @pdfdir@ +-prefix = @prefix@ +-program_transform_name = @program_transform_name@ +-psdir = @psdir@ +-sbindir = @sbindir@ +-sharedstatedir = @sharedstatedir@ +-srcdir = @srcdir@ +-sysconfdir = @sysconfdir@ +-target_alias = @target_alias@ +-top_build_prefix = @top_build_prefix@ +-top_builddir = @top_builddir@ +-top_srcdir = @top_srcdir@ +-version = @version@ +-version_major = @version_major@ +-version_minor = @version_minor@ +-version_release = @version_release@ +-version_revision = @version_revision@ +-version_stage = @version_stage@ +-etapebindir = $(localerlanglibdir)/etap/ebin +-etap_file_collection = \ +- etap.erl \ +- etap_application.erl \ +- etap_can.erl \ +- etap_exception.erl \ +- etap_process.erl \ +- etap_report.erl \ +- etap_request.erl \ +- etap_string.erl \ +- etap_web.erl +- +-etapebin_make_generated_file_list = \ +- etap.beam \ +- etap_application.beam \ +- etap_can.beam \ +- etap_exception.beam \ +- etap_process.beam \ +- etap_report.beam \ +- etap_request.beam \ +- etap_string.beam \ +- etap_web.beam +- +-etapebin_DATA = $(etapebin_make_generated_file_list) +-EXTRA_DIST = $(etap_file_collection) +-CLEANFILES = $(etapebin_make_generated_file_list) +-all: all-am +- +-.SUFFIXES: +-$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) +- @for dep in $?; do \ +- case '$(am__configure_deps)' in \ +- *$$dep*) \ +- ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ +- && { if test -f $@; then exit 0; else break; fi; }; \ +- exit 1;; \ +- esac; \ +- done; \ +- echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/etap/Makefile'; \ +- $(am__cd) $(top_srcdir) && \ +- $(AUTOMAKE) --foreign src/etap/Makefile +-.PRECIOUS: Makefile +-Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status +- @case '$?' in \ +- *config.status*) \ +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ +- *) \ +- echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ +- cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ +- esac; +- +-$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +- +-$(top_srcdir)/configure: $(am__configure_deps) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +-$(ACLOCAL_M4): $(am__aclocal_m4_deps) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +-$(am__aclocal_m4_deps): +- +-mostlyclean-libtool: +- -rm -f *.lo +- +-clean-libtool: +- -rm -rf .libs _libs +-install-etapebinDATA: $(etapebin_DATA) +- @$(NORMAL_INSTALL) +- test -z "$(etapebindir)" || $(MKDIR_P) "$(DESTDIR)$(etapebindir)" +- @list='$(etapebin_DATA)'; test -n "$(etapebindir)" || list=; \ +- for p in $$list; do \ +- if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ +- echo "$$d$$p"; \ +- done | $(am__base_list) | \ +- while read files; do \ +- echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(etapebindir)'"; \ +- $(INSTALL_DATA) $$files "$(DESTDIR)$(etapebindir)" || exit $$?; \ +- done +- +-uninstall-etapebinDATA: +- @$(NORMAL_UNINSTALL) +- @list='$(etapebin_DATA)'; test -n "$(etapebindir)" || list=; \ +- files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ +- test -n "$$files" || exit 0; \ +- echo " ( cd '$(DESTDIR)$(etapebindir)' && rm -f" $$files ")"; \ +- cd "$(DESTDIR)$(etapebindir)" && rm -f $$files +-tags: TAGS +-TAGS: +- +-ctags: CTAGS +-CTAGS: +- +- +-distdir: $(DISTFILES) +- @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +- topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +- list='$(DISTFILES)'; \ +- dist_files=`for file in $$list; do echo $$file; done | \ +- sed -e "s|^$$srcdirstrip/||;t" \ +- -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ +- case $$dist_files in \ +- */*) $(MKDIR_P) `echo "$$dist_files" | \ +- sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ +- sort -u` ;; \ +- esac; \ +- for file in $$dist_files; do \ +- if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ +- if test -d $$d/$$file; then \ +- dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ +- if test -d "$(distdir)/$$file"; then \ +- find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +- fi; \ +- if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ +- cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ +- find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +- fi; \ +- cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ +- else \ +- test -f "$(distdir)/$$file" \ +- || cp -p $$d/$$file "$(distdir)/$$file" \ +- || exit 1; \ +- fi; \ +- done +-check-am: all-am +-check: check-am +-all-am: Makefile $(DATA) +-installdirs: +- for dir in "$(DESTDIR)$(etapebindir)"; do \ +- test -z "$$dir" || $(MKDIR_P) "$$dir"; \ +- done +-install: install-am +-install-exec: install-exec-am +-install-data: install-data-am +-uninstall: uninstall-am +- +-install-am: all-am +- @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am +- +-installcheck: installcheck-am +-install-strip: +- $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ +- install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ +- `test -z '$(STRIP)' || \ +- echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +-mostlyclean-generic: +- +-clean-generic: +- -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) +- +-distclean-generic: +- -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) +- -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) +- +-maintainer-clean-generic: +- @echo "This command is intended for maintainers to use" +- @echo "it deletes files that may require special tools to rebuild." +-clean: clean-am +- +-clean-am: clean-generic clean-libtool mostlyclean-am +- +-distclean: distclean-am +- -rm -f Makefile +-distclean-am: clean-am distclean-generic +- +-dvi: dvi-am +- +-dvi-am: +- +-html: html-am +- +-html-am: +- +-info: info-am +- +-info-am: +- +-install-data-am: install-etapebinDATA +- +-install-dvi: install-dvi-am +- +-install-dvi-am: +- +-install-exec-am: +- +-install-html: install-html-am +- +-install-html-am: +- +-install-info: install-info-am +- +-install-info-am: +- +-install-man: +- +-install-pdf: install-pdf-am +- +-install-pdf-am: +- +-install-ps: install-ps-am +- +-install-ps-am: +- +-installcheck-am: +- +-maintainer-clean: maintainer-clean-am +- -rm -f Makefile +-maintainer-clean-am: distclean-am maintainer-clean-generic +- +-mostlyclean: mostlyclean-am +- +-mostlyclean-am: mostlyclean-generic mostlyclean-libtool +- +-pdf: pdf-am +- +-pdf-am: +- +-ps: ps-am +- +-ps-am: +- +-uninstall-am: uninstall-etapebinDATA +- +-.MAKE: install-am install-strip +- +-.PHONY: all all-am check check-am clean clean-generic clean-libtool \ +- distclean distclean-generic distclean-libtool distdir dvi \ +- dvi-am html html-am info info-am install install-am \ +- install-data install-data-am install-dvi install-dvi-am \ +- install-etapebinDATA install-exec install-exec-am install-html \ +- install-html-am install-info install-info-am install-man \ +- install-pdf install-pdf-am install-ps install-ps-am \ +- install-strip installcheck installcheck-am installdirs \ +- maintainer-clean maintainer-clean-generic mostlyclean \ +- mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ +- uninstall uninstall-am uninstall-etapebinDATA +- +- +-%.beam: %.erl +- $(ERLC) $(ERLC_FLAGS) $< +- +-# Tell versions [3.59,3.63) of GNU make to not export all variables. +-# Otherwise a system limit (for SysV at least) may be exceeded. +-.NOEXPORT: +diff --git a/src/etap/etap.erl b/src/etap/etap.erl +deleted file mode 100644 +index 5ad5dba..0000000 +--- a/src/etap/etap.erl ++++ /dev/null +@@ -1,416 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @author Nick Gerakines [http://socklabs.com/] +-%% @author Jeremy Wall +-%% @version 0.3.4 +-%% @copyright 2007-2008 Jeremy Wall, 2008-2009 Nick Gerakines +-%% @reference http://testanything.org/wiki/index.php/Main_Page +-%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol +-%% @todo Finish implementing the skip directive. +-%% @todo Document the messages handled by this receive loop. +-%% @todo Explain in documentation why we use a process to handle test input. +-%% @doc etap is a TAP testing module for Erlang components and applications. +-%% This module allows developers to test their software using the TAP method. +-%% +-%%

+-%% TAP, the Test Anything Protocol, is a simple text-based interface between +-%% testing modules in a test harness. TAP started life as part of the test +-%% harness for Perl but now has implementations in C/C++, Python, PHP, Perl +-%% and probably others by the time you read this. +-%%

+-%% +-%% The testing process begins by defining a plan using etap:plan/1, running +-%% a number of etap tests and then calling eta:end_tests/0. Please refer to +-%% the Erlang modules in the t directory of this project for example tests. +--module(etap). +--export([ +- ensure_test_server/0, start_etap_server/0, test_server/1, +- diag/1, diag/2, plan/1, end_tests/0, not_ok/2, ok/2, is/3, isnt/3, +- any/3, none/3, fun_is/3, is_greater/3, skip/1, skip/2, +- ensure_coverage_starts/0, ensure_coverage_ends/0, coverage_report/0, +- datetime/1, skip/3, bail/0, bail/1 +-]). +--record(test_state, {planned = 0, count = 0, pass = 0, fail = 0, skip = 0, skip_reason = ""}). +--vsn("0.3.4"). +- +-%% @spec plan(N) -> Result +-%% N = unknown | skip | {skip, string()} | integer() +-%% Result = ok +-%% @doc Create a test plan and boot strap the test server. +-plan(unknown) -> +- ensure_coverage_starts(), +- ensure_test_server(), +- etap_server ! {self(), plan, unknown}, +- ok; +-plan(skip) -> +- io:format("1..0 # skip~n"); +-plan({skip, Reason}) -> +- io:format("1..0 # skip ~s~n", [Reason]); +-plan(N) when is_integer(N), N > 0 -> +- ensure_coverage_starts(), +- ensure_test_server(), +- etap_server ! {self(), plan, N}, +- ok. +- +-%% @spec end_tests() -> ok +-%% @doc End the current test plan and output test results. +-%% @todo This should probably be done in the test_server process. +-end_tests() -> +- ensure_coverage_ends(), +- etap_server ! {self(), state}, +- State = receive X -> X end, +- if +- State#test_state.planned == -1 -> +- io:format("1..~p~n", [State#test_state.count]); +- true -> +- ok +- end, +- case whereis(etap_server) of +- undefined -> ok; +- _ -> etap_server ! done, ok +- end. +- +-%% @private +-ensure_coverage_starts() -> +- case os:getenv("COVER") of +- false -> ok; +- _ -> +- BeamDir = case os:getenv("COVER_BIN") of false -> "ebin"; X -> X end, +- cover:compile_beam_directory(BeamDir) +- end. +- +-%% @private +-%% @doc Attempts to write out any collected coverage data to the cover/ +-%% directory. This function should not be called externally, but it could be. +-ensure_coverage_ends() -> +- case os:getenv("COVER") of +- false -> ok; +- _ -> +- filelib:ensure_dir("cover/"), +- Name = lists:flatten([ +- io_lib:format("~.16b", [X]) || X <- binary_to_list(erlang:md5( +- term_to_binary({make_ref(), now()}) +- )) +- ]), +- cover:export("cover/" ++ Name ++ ".coverdata") +- end. +- +-%% @spec coverage_report() -> ok +-%% @doc Use the cover module's covreage report builder to create code coverage +-%% reports from recently created coverdata files. +-coverage_report() -> +- [cover:import(File) || File <- filelib:wildcard("cover/*.coverdata")], +- lists:foreach( +- fun(Mod) -> +- cover:analyse_to_file(Mod, atom_to_list(Mod) ++ "_coverage.txt", []) +- end, +- cover:imported_modules() +- ), +- ok. +- +-bail() -> +- bail(""). +- +-bail(Reason) -> +- etap_server ! {self(), diag, "Bail out! " ++ Reason}, +- ensure_coverage_ends(), +- etap_server ! done, ok, +- ok. +- +- +-%% @spec diag(S) -> ok +-%% S = string() +-%% @doc Print a debug/status message related to the test suite. +-diag(S) -> etap_server ! {self(), diag, "# " ++ S}, ok. +- +-%% @spec diag(Format, Data) -> ok +-%% Format = atom() | string() | binary() +-%% Data = [term()] +-%% UnicodeList = [Unicode] +-%% Unicode = int() +-%% @doc Print a debug/status message related to the test suite. +-%% Function arguments are passed through io_lib:format/2. +-diag(Format, Data) -> diag(io_lib:format(Format, Data)). +- +-%% @spec ok(Expr, Desc) -> Result +-%% Expr = true | false +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that a statement is true. +-ok(Expr, Desc) -> mk_tap(Expr == true, Desc). +- +-%% @spec not_ok(Expr, Desc) -> Result +-%% Expr = true | false +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that a statement is false. +-not_ok(Expr, Desc) -> mk_tap(Expr == false, Desc). +- +-%% @spec is(Got, Expected, Desc) -> Result +-%% Got = any() +-%% Expected = any() +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that two values are the same. +-is(Got, Expected, Desc) -> +- case mk_tap(Got == Expected, Desc) of +- false -> +- etap_server ! {self(), diag, " ---"}, +- etap_server ! {self(), diag, io_lib:format(" description: ~p", [Desc])}, +- etap_server ! {self(), diag, io_lib:format(" found: ~p", [Got])}, +- etap_server ! {self(), diag, io_lib:format(" wanted: ~p", [Expected])}, +- etap_server ! {self(), diag, " ..."}, +- false; +- true -> true +- end. +- +-%% @spec isnt(Got, Expected, Desc) -> Result +-%% Got = any() +-%% Expected = any() +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that two values are not the same. +-isnt(Got, Expected, Desc) -> mk_tap(Got /= Expected, Desc). +- +-%% @spec is_greater(ValueA, ValueB, Desc) -> Result +-%% ValueA = number() +-%% ValueB = number() +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that an integer is greater than another. +-is_greater(ValueA, ValueB, Desc) when is_integer(ValueA), is_integer(ValueB) -> +- mk_tap(ValueA > ValueB, Desc). +- +-%% @spec any(Got, Items, Desc) -> Result +-%% Got = any() +-%% Items = [any()] +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that an item is in a list. +-any(Got, Items, Desc) -> +- is(lists:member(Got, Items), true, Desc). +- +-%% @spec none(Got, Items, Desc) -> Result +-%% Got = any() +-%% Items = [any()] +-%% Desc = string() +-%% Result = true | false +-%% @doc Assert that an item is not in a list. +-none(Got, Items, Desc) -> +- is(lists:member(Got, Items), false, Desc). +- +-%% @spec fun_is(Fun, Expected, Desc) -> Result +-%% Fun = function() +-%% Expected = any() +-%% Desc = string() +-%% Result = true | false +-%% @doc Use an anonymous function to assert a pattern match. +-fun_is(Fun, Expected, Desc) when is_function(Fun) -> +- is(Fun(Expected), true, Desc). +- +-%% @equiv skip(TestFun, "") +-skip(TestFun) when is_function(TestFun) -> +- skip(TestFun, ""). +- +-%% @spec skip(TestFun, Reason) -> ok +-%% TestFun = function() +-%% Reason = string() +-%% @doc Skip a test. +-skip(TestFun, Reason) when is_function(TestFun), is_list(Reason) -> +- begin_skip(Reason), +- catch TestFun(), +- end_skip(), +- ok. +- +-%% @spec skip(Q, TestFun, Reason) -> ok +-%% Q = true | false | function() +-%% TestFun = function() +-%% Reason = string() +-%% @doc Skips a test conditionally. The first argument to this function can +-%% either be the 'true' or 'false' atoms or a function that returns 'true' or +-%% 'false'. +-skip(QFun, TestFun, Reason) when is_function(QFun), is_function(TestFun), is_list(Reason) -> +- case QFun() of +- true -> begin_skip(Reason), TestFun(), end_skip(); +- _ -> TestFun() +- end, +- ok; +- +-skip(Q, TestFun, Reason) when is_function(TestFun), is_list(Reason), Q == true -> +- begin_skip(Reason), +- TestFun(), +- end_skip(), +- ok; +- +-skip(_, TestFun, Reason) when is_function(TestFun), is_list(Reason) -> +- TestFun(), +- ok. +- +-%% @private +-begin_skip(Reason) -> +- etap_server ! {self(), begin_skip, Reason}. +- +-%% @private +-end_skip() -> +- etap_server ! {self(), end_skip}. +- +-% --- +-% Internal / Private functions +- +-%% @private +-%% @doc Start the etap_server process if it is not running already. +-ensure_test_server() -> +- case whereis(etap_server) of +- undefined -> +- proc_lib:start(?MODULE, start_etap_server,[]); +- _ -> +- diag("The test server is already running.") +- end. +- +-%% @private +-%% @doc Start the etap_server loop and register itself as the etap_server +-%% process. +-start_etap_server() -> +- catch register(etap_server, self()), +- proc_lib:init_ack(ok), +- etap:test_server(#test_state{ +- planned = 0, +- count = 0, +- pass = 0, +- fail = 0, +- skip = 0, +- skip_reason = "" +- }). +- +- +-%% @private +-%% @doc The main etap_server receive/run loop. The etap_server receive loop +-%% responds to seven messages apperatining to failure or passing of tests. +-%% It is also used to initiate the testing process with the {_, plan, _} +-%% message that clears the current test state. +-test_server(State) -> +- NewState = receive +- {_From, plan, unknown} -> +- io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), +- io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), +- State#test_state{ +- planned = -1, +- count = 0, +- pass = 0, +- fail = 0, +- skip = 0, +- skip_reason = "" +- }; +- {_From, plan, N} -> +- io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), +- io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), +- io:format("1..~p~n", [N]), +- State#test_state{ +- planned = N, +- count = 0, +- pass = 0, +- fail = 0, +- skip = 0, +- skip_reason = "" +- }; +- {_From, begin_skip, Reason} -> +- State#test_state{ +- skip = 1, +- skip_reason = Reason +- }; +- {_From, end_skip} -> +- State#test_state{ +- skip = 0, +- skip_reason = "" +- }; +- {_From, pass, Desc} -> +- FullMessage = skip_diag( +- " - " ++ Desc, +- State#test_state.skip, +- State#test_state.skip_reason +- ), +- io:format("ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), +- State#test_state{ +- count = State#test_state.count + 1, +- pass = State#test_state.pass + 1 +- }; +- +- {_From, fail, Desc} -> +- FullMessage = skip_diag( +- " - " ++ Desc, +- State#test_state.skip, +- State#test_state.skip_reason +- ), +- io:format("not ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), +- State#test_state{ +- count = State#test_state.count + 1, +- fail = State#test_state.fail + 1 +- }; +- {From, state} -> +- From ! State, +- State; +- {_From, diag, Message} -> +- io:format("~s~n", [Message]), +- State; +- {From, count} -> +- From ! State#test_state.count, +- State; +- {From, is_skip} -> +- From ! State#test_state.skip, +- State; +- done -> +- exit(normal) +- end, +- test_server(NewState). +- +-%% @private +-%% @doc Process the result of a test and send it to the etap_server process. +-mk_tap(Result, Desc) -> +- IsSkip = lib:sendw(etap_server, is_skip), +- case [IsSkip, Result] of +- [_, true] -> +- etap_server ! {self(), pass, Desc}, +- true; +- [1, _] -> +- etap_server ! {self(), pass, Desc}, +- true; +- _ -> +- etap_server ! {self(), fail, Desc}, +- false +- end. +- +-%% @private +-%% @doc Format a date/time string. +-datetime(DateTime) -> +- {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, +- io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B", [Year, Month, Day, Hour, Min, Sec]). +- +-%% @private +-%% @doc Craft an output message taking skip/todo into consideration. +-skip_diag(Message, 0, _) -> +- Message; +-skip_diag(_Message, 1, "") -> +- " # SKIP"; +-skip_diag(_Message, 1, Reason) -> +- " # SKIP : " ++ Reason. +diff --git a/src/etap/etap_application.erl b/src/etap/etap_application.erl +deleted file mode 100644 +index 98b5275..0000000 +--- a/src/etap/etap_application.erl ++++ /dev/null +@@ -1,72 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @author Nick Gerakines [http://socklabs.com/] +-%% @copyright 2008 Nick Gerakines +-%% @reference http://testanything.org/wiki/index.php/Main_Page +-%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol +-%% @todo Explain in documentation why we use a process to handle test input. +-%% @todo Add test to verify the number of members in a pg2 group. +-%% @doc Provide test functionality to the application and related behaviors. +--module(etap_application). +--export([ +- start_ok/2, ensure_loaded/3, load_ok/2, +- pg2_group_exists/2, pg2_group_doesntexist/2 +-]). +- +-%% @spec load_ok(string(), string()) -> true | false +-%% @doc Assert that an application can be loaded successfully. +-load_ok(AppName, Desc) -> +- etap:ok(application:load(AppName) == ok, Desc). +- +-%% @spec start_ok(string(), string()) -> true | false +-%% @doc Assert that an application can be started successfully. +-start_ok(AppName, Desc) -> +- etap:ok(application:start(AppName) == ok, Desc). +- +-%% @spec ensure_loaded(string(), string(), string()) -> true | false +-%% @doc Assert that an application has been loaded successfully. +-ensure_loaded(AppName, AppVsn, Desc) -> +- etap:any( +- fun(Match) -> case Match of {AppName, _, AppVsn} -> true; _ -> false end end, +- application:loaded_applications(), +- Desc +- ). +- +-%% @spec pg2_group_exists(string(), string()) -> true | false +-%% @doc Assert that a pg2 group exists. +-pg2_group_exists(GroupName, Desc) -> +- etap:any( +- fun(Match) -> Match == GroupName end, +- pg2:which_groups(), +- Desc +- ). +- +-%% @spec pg2_group_doesntexist(string(), string()) -> true | false +-%% @doc Assert that a pg2 group does not exists. +-pg2_group_doesntexist(GroupName, Desc) -> +- etap:none( +- fun(Match) -> Match == GroupName end, +- pg2:which_groups(), +- Desc +- ). +diff --git a/src/etap/etap_can.erl b/src/etap/etap_can.erl +deleted file mode 100644 +index 552b717..0000000 +--- a/src/etap/etap_can.erl ++++ /dev/null +@@ -1,79 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @reference http://testanything.org/wiki/index.php/Main_Page +-%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol +-%% @doc Provide test functionality modules +--module(etap_can). +- +--export([ +- loaded_ok/2, can_ok/2, can_ok/3, +- has_attrib/2, is_attrib/3, is_behaviour/2 +-]). +- +-%% @spec loaded_ok(atom(), string()) -> true | false +-%% @doc Assert that a module has been loaded successfully. +-loaded_ok(M, Desc) when is_atom(M) -> +- etap:fun_is(fun({module, _}) -> true; (_) -> false end, code:load_file(M), Desc). +- +-%% @spec can_ok(atom(), atom()) -> true | false +-%% @doc Assert that a module exports a given function. +-can_ok(M, F) when is_atom(M), is_atom(F) -> +- Matches = [X || {X, _} <- M:module_info(exports), X == F], +- etap:ok(Matches > 0, lists:concat([M, " can ", F])). +- +-%% @spec can_ok(atom(), atom(), integer()) -> true | false +-%% @doc Assert that a module exports a given function with a given arity. +-can_ok(M, F, A) when is_atom(M); is_atom(F), is_number(A) -> +- Matches = [X || X <- M:module_info(exports), X == {F, A}], +- etap:ok(Matches > 0, lists:concat([M, " can ", F, "/", A])). +- +-%% @spec has_attrib(M, A) -> true | false +-%% M = atom() +-%% A = atom() +-%% @doc Asserts that a module has a given attribute. +-has_attrib(M, A) when is_atom(M), is_atom(A) -> +- etap:isnt( +- proplists:get_value(A, M:module_info(attributes), 'asdlkjasdlkads'), +- 'asdlkjasdlkads', +- lists:concat([M, " has attribute ", A]) +- ). +- +-%% @spec has_attrib(M, A. V) -> true | false +-%% M = atom() +-%% A = atom() +-%% V = any() +-%% @doc Asserts that a module has a given attribute with a given value. +-is_attrib(M, A, V) when is_atom(M) andalso is_atom(A) -> +- etap:is( +- proplists:get_value(A, M:module_info(attributes)), +- [V], +- lists:concat([M, "'s ", A, " is ", V]) +- ). +- +-%% @spec is_behavior(M, B) -> true | false +-%% M = atom() +-%% B = atom() +-%% @doc Asserts that a given module has a specific behavior. +-is_behaviour(M, B) when is_atom(M) andalso is_atom(B) -> +- is_attrib(M, behaviour, B). +diff --git a/src/etap/etap_exception.erl b/src/etap/etap_exception.erl +deleted file mode 100644 +index ba66072..0000000 +--- a/src/etap/etap_exception.erl ++++ /dev/null +@@ -1,66 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @reference http://testanything.org/wiki/index.php/Main_Page +-%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol +-%% @doc Adds exception based testing to the etap suite. +--module(etap_exception). +- +--export([dies_ok/2, lives_ok/2, throws_ok/3]). +- +-% --- +-% External / Public functions +- +-%% @doc Assert that an exception is raised when running a given function. +-dies_ok(F, Desc) -> +- case (catch F()) of +- {'EXIT', _} -> etap:ok(true, Desc); +- _ -> etap:ok(false, Desc) +- end. +- +-%% @doc Assert that an exception is not raised when running a given function. +-lives_ok(F, Desc) -> +- etap:is(try_this(F), success, Desc). +- +-%% @doc Assert that the exception thrown by a function matches the given exception. +-throws_ok(F, Exception, Desc) -> +- try F() of +- _ -> etap:ok(nok, Desc) +- catch +- _:E -> +- etap:is(E, Exception, Desc) +- end. +- +-% --- +-% Internal / Private functions +- +-%% @private +-%% @doc Run a function and catch any exceptions. +-try_this(F) when is_function(F, 0) -> +- try F() of +- _ -> success +- catch +- throw:E -> {throw, E}; +- error:E -> {error, E}; +- exit:E -> {exit, E} +- end. +diff --git a/src/etap/etap_process.erl b/src/etap/etap_process.erl +deleted file mode 100644 +index 69f5ba0..0000000 +--- a/src/etap/etap_process.erl ++++ /dev/null +@@ -1,42 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @doc Adds process/pid testing to the etap suite. +--module(etap_process). +- +--export([is_pid/2, is_alive/2, is_mfa/3]). +- +-% --- +-% External / Public functions +- +-%% @doc Assert that a given variable is a pid. +-is_pid(Pid, Desc) when is_pid(Pid) -> etap:ok(true, Desc); +-is_pid(_, Desc) -> etap:ok(false, Desc). +- +-%% @doc Assert that a given process/pid is alive. +-is_alive(Pid, Desc) -> +- etap:ok(erlang:is_process_alive(Pid), Desc). +- +-%% @doc Assert that the current function of a pid is a given {M, F, A} tuple. +-is_mfa(Pid, MFA, Desc) -> +- etap:is({current_function, MFA}, erlang:process_info(Pid, current_function), Desc). +diff --git a/src/etap/etap_report.erl b/src/etap/etap_report.erl +deleted file mode 100644 +index 6d692fb..0000000 +--- a/src/etap/etap_report.erl ++++ /dev/null +@@ -1,343 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @doc A module for creating nice looking code coverage reports. +--module(etap_report). +--export([create/0]). +- +-%% @spec create() -> ok +-%% @doc Create html code coverage reports for each module that code coverage +-%% data exists for. +-create() -> +- [cover:import(File) || File <- filelib:wildcard("cover/*.coverdata")], +- Modules = lists:foldl( +- fun(Module, Acc) -> +- [{Module, file_report(Module)} | Acc] +- end, +- [], +- cover:imported_modules() +- ), +- index(Modules). +- +-%% @private +-index(Modules) -> +- {ok, IndexFD} = file:open("cover/index.html", [write]), +- io:format(IndexFD, "", []), +- io:format(IndexFD, "", []), +- lists:foldl( +- fun({Module, {Good, Bad, Source}}, LastRow) -> +- case {Good + Bad, Source} of +- {0, _} -> LastRow; +- {_, none} -> LastRow; +- _ -> +- CovPer = round((Good / (Good + Bad)) * 100), +- UnCovPer = round((Bad / (Good + Bad)) * 100), +- RowClass = case LastRow of 1 -> "odd"; _ -> "even" end, +- io:format(IndexFD, "
", [RowClass]), +- io:format(IndexFD, "~s", [atom_to_list(Module) ++ "_report.html", atom_to_list(Module)]), +- io:format(IndexFD, " +- +- +- +- +-
~p%  +- +- +-
+-
+- ", [CovPer, CovPer, UnCovPer]), +- io:format(IndexFD, "
", []), +- case LastRow of +- 1 -> 0; +- 0 -> 1 +- end +- end +- end, +- 0, +- lists:sort(Modules) +- ), +- {TotalGood, TotalBad} = lists:foldl( +- fun({_, {Good, Bad, Source}}, {TGood, TBad}) -> +- case Source of none -> {TGood, TBad}; _ -> {TGood + Good, TBad + Bad} end +- end, +- {0, 0}, +- Modules +- ), +- io:format(IndexFD, "

Generated on ~s.

~n", [etap:datetime({date(), time()})]), +- case TotalGood + TotalBad of +- 0 -> ok; +- _ -> +- TotalCovPer = round((TotalGood / (TotalGood + TotalBad)) * 100), +- TotalUnCovPer = round((TotalBad / (TotalGood + TotalBad)) * 100), +- io:format(IndexFD, "
", []), +- io:format(IndexFD, "Total +- +- +- +- +-
~p%  +- +- +-
+-
+- ", [TotalCovPer, TotalCovPer, TotalUnCovPer]), +- io:format(IndexFD, "
", []) +- end, +- io:format(IndexFD, "", []), +- file:close(IndexFD), +- ok. +- +-%% @private +-file_report(Module) -> +- {ok, Data} = cover:analyse(Module, calls, line), +- Source = find_source(Module), +- {Good, Bad} = collect_coverage(Data, {0, 0}), +- case {Source, Good + Bad} of +- {none, _} -> ok; +- {_, 0} -> ok; +- _ -> +- {ok, SourceFD} = file:open(Source, [read]), +- {ok, WriteFD} = file:open("cover/" ++ atom_to_list(Module) ++ "_report.html", [write]), +- io:format(WriteFD, "~s", [header(Module, Good, Bad)]), +- output_lines(Data, WriteFD, SourceFD, 1), +- io:format(WriteFD, "~s", [footer()]), +- file:close(WriteFD), +- file:close(SourceFD), +- ok +- end, +- {Good, Bad, Source}. +- +-%% @private +-collect_coverage([], Acc) -> Acc; +-collect_coverage([{{_, _}, 0} | Data], {Good, Bad}) -> +- collect_coverage(Data, {Good, Bad + 1}); +-collect_coverage([_ | Data], {Good, Bad}) -> +- collect_coverage(Data, {Good + 1, Bad}). +- +-%% @private +-output_lines(Data, WriteFD, SourceFD, LineNumber) -> +- {Match, NextData} = datas_match(Data, LineNumber), +- case io:get_line(SourceFD, '') of +- eof -> ok; +- Line = "%% @todo" ++ _ -> +- io:format(WriteFD, "~s", [out_line(LineNumber, highlight, Line)]), +- output_lines(NextData, WriteFD, SourceFD, LineNumber + 1); +- Line = "% " ++ _ -> +- io:format(WriteFD, "~s", [out_line(LineNumber, none, Line)]), +- output_lines(NextData, WriteFD, SourceFD, LineNumber + 1); +- Line -> +- case Match of +- {true, CC} -> +- io:format(WriteFD, "~s", [out_line(LineNumber, CC, Line)]), +- output_lines(NextData, WriteFD, SourceFD, LineNumber + 1); +- false -> +- io:format(WriteFD, "~s", [out_line(LineNumber, none, Line)]), +- output_lines(NextData, WriteFD, SourceFD, LineNumber + 1) +- end +- end. +- +-%% @private +-out_line(Number, none, Line) -> +- PadNu = string:right(integer_to_list(Number), 5, $.), +- io_lib:format("~s ~s", [Number, PadNu, Line]); +-out_line(Number, highlight, Line) -> +- PadNu = string:right(integer_to_list(Number), 5, $.), +- io_lib:format("~s ~s", [Number, PadNu, Line]); +-out_line(Number, 0, Line) -> +- PadNu = string:right(integer_to_list(Number), 5, $.), +- io_lib:format("~s ~s", [Number, PadNu, Line]); +-out_line(Number, _, Line) -> +- PadNu = string:right(integer_to_list(Number), 5, $.), +- io_lib:format("~s ~s", [Number, PadNu, Line]). +- +-%% @private +-datas_match([], _) -> {false, []}; +-datas_match([{{_, Line}, CC} | Datas], LineNumber) when Line == LineNumber -> {{true, CC}, Datas}; +-datas_match(Data, _) -> {false, Data}. +- +-%% @private +-find_source(Module) when is_atom(Module) -> +- Root = filename:rootname(Module), +- Dir = filename:dirname(Root), +- XDir = case os:getenv("SRC") of false -> "src"; X -> X end, +- find_source([ +- filename:join([Dir, Root ++ ".erl"]), +- filename:join([Dir, "..", "src", Root ++ ".erl"]), +- filename:join([Dir, "src", Root ++ ".erl"]), +- filename:join([Dir, "elibs", Root ++ ".erl"]), +- filename:join([Dir, "..", "elibs", Root ++ ".erl"]), +- filename:join([Dir, XDir, Root ++ ".erl"]) +- ]); +-find_source([]) -> none; +-find_source([Test | Tests]) -> +- case filelib:is_file(Test) of +- true -> Test; +- false -> find_source(Tests) +- end. +- +-%% @private +-header(Module, Good, Bad) -> +- io:format("Good ~p~n", [Good]), +- io:format("Bad ~p~n", [Bad]), +- CovPer = round((Good / (Good + Bad)) * 100), +- UnCovPer = round((Bad / (Good + Bad)) * 100), +- io:format("CovPer ~p~n", [CovPer]), +- io_lib:format(" +- +- +- ~s - C0 code coverage information +- +- +- +- +-

C0 code coverage information

+-

Generated on ~s with etap 0.3.4. +-

+- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +-
NameTotal linesLines of codeTotal coverageCode coverage
+- ~s +- +- ?? +- +- ?? +- +- ?? +- +- +- +- +- +-
~p%  +- +- +-
+-
+-
", [Module, etap:datetime({date(), time()}), atom_to_list(Module) ++ "_report.html", Module, CovPer, CovPer, UnCovPer]).
+-
+-%% @private
+-footer() ->
+-    "

Generated using etap 0.3.4.

+- +- +- ". +diff --git a/src/etap/etap_request.erl b/src/etap/etap_request.erl +deleted file mode 100644 +index 9fd23ac..0000000 +--- a/src/etap/etap_request.erl ++++ /dev/null +@@ -1,89 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @doc Provides test functionality against a specific web request. Many of +-%% the exported methods can be used to build your own more complex tests. +--module(etap_request, [Method, Url, InHeaders, InBody, Status, OutHeaders, OutBody]). +- +--export([status_is/2]). +- +--export([ +- method/0, url/0, status/0, status_code/0, status_line/0, rheaders/0, +- has_rheader/1, rheader/1, rbody/0, header_is/3, body_is/2, +- body_has_string/2 +-]). +- +-% --- +-% Tests +- +-%% @doc Assert that response status code is the given status code. +-status_is(Code, Desc) -> +- etap:is(status_code(), Code, Desc). +- +-header_is(Name, Value, Desc) -> +- etap:is(rheader(Name), Value, Desc). +- +-body_is(Value, Desc) -> +- etap:is(rbody(), Value, Desc). +- +-body_has_string(String, Desc) when is_list(OutBody), is_list(String) -> +- etap_string:contains_ok(OutBody, String, Desc). +- +-% --- +-% Accessor functions +- +-%% @doc Access a request's method. +-method() -> Method. +- +-%% @doc Access a request's URL. +-url() -> Url. +- +-%% @doc Access a request's status. +-status() -> Status. +- +-%% @doc Access a request's status code. +-status_code() -> +- {_, Code, _} = Status, +- Code. +- +-%% @doc Access a request's status line. +-status_line() -> +- {_, _, Line} = Status, +- Line. +- +-%% @doc Access a request's headers. +-rheaders() -> OutHeaders. +- +-%% @doc Dertermine if a specific request header exists. +-has_rheader(Key) -> +- lists:keymember(Key, 1, OutHeaders). +- +-%% @doc Return a specific request header. +-rheader(Key) -> +- case lists:keysearch(Key, 1, OutHeaders) of +- false -> undefined; +- {value, {Key, Value}} -> Value +- end. +- +-%% @doc Access the request's body. +-rbody() -> OutBody. +diff --git a/src/etap/etap_string.erl b/src/etap/etap_string.erl +deleted file mode 100644 +index 67aa3d5..0000000 +--- a/src/etap/etap_string.erl ++++ /dev/null +@@ -1,47 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @author Nick Gerakines [http://socklabs.com/] +-%% @copyright 2008 Nick Gerakines +-%% @doc Provide testing functionality for strings. +--module(etap_string). +- +--export([contains_ok/3, is_before/4]). +- +-%% @spec contains_ok(string(), string(), string()) -> true | false +-%% @doc Assert that a string is contained in another string. +-contains_ok(Source, String, Desc) -> +- etap:isnt( +- string:str(Source, String), +- 0, +- Desc +- ). +- +-%% @spec is_before(string(), string(), string(), string()) -> true | false +-%% @doc Assert that a string comes before another string within a larger body. +-is_before(Source, StringA, StringB, Desc) -> +- etap:is_greater( +- string:str(Source, StringB), +- string:str(Source, StringA), +- Desc +- ). +diff --git a/src/etap/etap_web.erl b/src/etap/etap_web.erl +deleted file mode 100644 +index fb7aee1..0000000 +--- a/src/etap/etap_web.erl ++++ /dev/null +@@ -1,65 +0,0 @@ +-%% Copyright (c) 2008-2009 Nick Gerakines +-%% +-%% Permission is hereby granted, free of charge, to any person +-%% obtaining a copy of this software and associated documentation +-%% files (the "Software"), to deal in the Software without +-%% restriction, including without limitation the rights to use, +-%% copy, modify, merge, publish, distribute, sublicense, and/or sell +-%% copies of the Software, and to permit persons to whom the +-%% Software is furnished to do so, subject to the following +-%% conditions: +-%% +-%% The above copyright notice and this permission notice shall be +-%% included in all copies or substantial portions of the Software. +-%% +-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-%% OTHER DEALINGS IN THE SOFTWARE. +-%% +-%% @author Nick Gerakines [http://socklabs.com/] +-%% @copyright 2008 Nick Gerakines +-%% @todo Support cookies. +-%% @doc Provide testing functionality for web requests. +--module(etap_web). +- +--export([simple_200/2, simple_404/2, build_request/4]). +- +-%% @doc Fetch a url and verify that it returned a 200 status. +-simple_200(Url, Desc) -> +- Request = build_request(get, Url, [], []), +- Request:status_is(200, Desc). +- +-%% @doc Fetch a url and verify that it returned a 404 status. +-simple_404(Url, Desc) -> +- Request = build_request(get, Url, [], []), +- Request:status_is(404, Desc). +- +-%% @doc Create and return a request structure. +-build_request(Method, Url, Headers, Body) +- when Method==options;Method==get;Method==head;Method==delete;Method==trace -> +- try http:request(Method, {Url, Headers}, [{autoredirect, false}], []) of +- {ok, {OutStatus, OutHeaders, OutBody}} -> +- etap_request:new(Method, Url, Headers, Body, OutStatus, OutHeaders, OutBody); +- _ -> error +- catch +- _:_ -> error +- end; +- +-%% @doc Create and return a request structure. +-build_request(Method, Url, Headers, Body) when Method == post; Method == put -> +- ContentType = case lists:keysearch("Content-Type", 1, Headers) of +- {value, {"Content-Type", X}} -> X; +- _ -> [] +- end, +- try http:request(Method, {Url, Headers, ContentType, Body}, [{autoredirect, false}], []) of +- {ok, {OutStatus, OutHeaders, OutBody}} -> +- etap_request:new(Method, Url, Headers, Body, OutStatus, OutHeaders, OutBody); +- _ -> error +- catch +- _:_ -> error +- end. +diff --git a/test/etap/test_util.erl.in b/test/etap/test_util.erl.in +index 79b0417..c57d7a8 100644 +--- a/test/etap/test_util.erl.in ++++ b/test/etap/test_util.erl.in +@@ -22,7 +22,7 @@ builddir() -> + "@abs_top_builddir@". + + init_code_path() -> +- Paths = ["etap", "couchdb", "ibrowse", "mochiweb"], ++ Paths = ["couchdb", "ibrowse", "mochiweb"], + lists:foreach(fun(Name) -> + code:add_pathz(filename:join([builddir(), "src", Name])) + end, Paths). -- 1.6.6.1 diff --git a/couchdb-0005-Remove-bundled-mochiweb-library.patch b/couchdb-0005-Remove-bundled-mochiweb-library.patch new file mode 100644 index 0000000..47421d6 --- /dev/null +++ b/couchdb-0005-Remove-bundled-mochiweb-library.patch @@ -0,0 +1,7205 @@ +From 29bc14d67186bc0b7fc82b31ca1bae9354a439a2 Mon Sep 17 00:00:00 2001 +From: Peter Lemenkov +Date: Mon, 7 Jun 2010 15:08:06 +0400 +Subject: [PATCH 5/7] Remove bundled mochiweb library + +--- + configure | 3 - + configure.ac | 1 - + src/Makefile.am | 2 +- + src/Makefile.in | 2 +- + src/mochiweb/Makefile.am | 80 --- + src/mochiweb/Makefile.in | 506 ------------------ + src/mochiweb/mochifmt.erl | 426 --------------- + src/mochiweb/mochifmt_records.erl | 30 - + src/mochiweb/mochifmt_std.erl | 23 - + src/mochiweb/mochihex.erl | 75 --- + src/mochiweb/mochijson.erl | 525 ------------------ + src/mochiweb/mochijson2.erl | 621 ---------------------- + src/mochiweb/mochinum.erl | 289 ---------- + src/mochiweb/mochiweb.app.in | 32 -- + src/mochiweb/mochiweb.erl | 110 ---- + src/mochiweb/mochiweb_app.erl | 20 - + src/mochiweb/mochiweb_charref.erl | 295 ----------- + src/mochiweb/mochiweb_cookies.erl | 257 --------- + src/mochiweb/mochiweb_echo.erl | 31 -- + src/mochiweb/mochiweb_headers.erl | 186 ------- + src/mochiweb/mochiweb_html.erl | 880 ------------------------------- + src/mochiweb/mochiweb_http.erl | 142 ----- + src/mochiweb/mochiweb_multipart.erl | 429 --------------- + src/mochiweb/mochiweb_request.erl | 790 --------------------------- + src/mochiweb/mochiweb_response.erl | 56 -- + src/mochiweb/mochiweb_skel.erl | 71 --- + src/mochiweb/mochiweb_socket_server.erl | 248 --------- + src/mochiweb/mochiweb_sup.erl | 34 -- + src/mochiweb/mochiweb_util.erl | 579 -------------------- + src/mochiweb/reloader.erl | 124 ----- + test/etap/030-doc-from-json.t | 2 +- + test/etap/031-doc-to-json.t | 2 +- + test/etap/test_util.erl.in | 2 +- + test/run_native_process.es | 1 - + test/runner.sh | 2 +- + 35 files changed, 6 insertions(+), 6870 deletions(-) + delete mode 100644 src/mochiweb/Makefile.am + delete mode 100644 src/mochiweb/Makefile.in + delete mode 100644 src/mochiweb/mochifmt.erl + delete mode 100644 src/mochiweb/mochifmt_records.erl + delete mode 100644 src/mochiweb/mochifmt_std.erl + delete mode 100644 src/mochiweb/mochihex.erl + delete mode 100644 src/mochiweb/mochijson.erl + delete mode 100644 src/mochiweb/mochijson2.erl + delete mode 100644 src/mochiweb/mochinum.erl + delete mode 100644 src/mochiweb/mochiweb.app.in + delete mode 100644 src/mochiweb/mochiweb.erl + delete mode 100644 src/mochiweb/mochiweb_app.erl + delete mode 100644 src/mochiweb/mochiweb_charref.erl + delete mode 100644 src/mochiweb/mochiweb_cookies.erl + delete mode 100644 src/mochiweb/mochiweb_echo.erl + delete mode 100644 src/mochiweb/mochiweb_headers.erl + delete mode 100644 src/mochiweb/mochiweb_html.erl + delete mode 100644 src/mochiweb/mochiweb_http.erl + delete mode 100644 src/mochiweb/mochiweb_multipart.erl + delete mode 100644 src/mochiweb/mochiweb_request.erl + delete mode 100644 src/mochiweb/mochiweb_response.erl + delete mode 100644 src/mochiweb/mochiweb_skel.erl + delete mode 100644 src/mochiweb/mochiweb_socket_server.erl + delete mode 100644 src/mochiweb/mochiweb_sup.erl + delete mode 100644 src/mochiweb/mochiweb_util.erl + delete mode 100644 src/mochiweb/reloader.erl + +diff --git a/configure b/configure +index ca7732f..2d728fe 100755 +--- a/configure ++++ b/configure +@@ -12139,8 +12139,6 @@ ac_config_files="$ac_config_files src/couchdb/priv/Makefile" + + ac_config_files="$ac_config_files src/ibrowse/Makefile" + +-ac_config_files="$ac_config_files src/mochiweb/Makefile" +- + ac_config_files="$ac_config_files test/Makefile" + + ac_config_files="$ac_config_files test/etap/Makefile" +@@ -13157,7 +13155,6 @@ do + "src/couchdb/Makefile") CONFIG_FILES="$CONFIG_FILES src/couchdb/Makefile" ;; + "src/couchdb/priv/Makefile") CONFIG_FILES="$CONFIG_FILES src/couchdb/priv/Makefile" ;; + "src/ibrowse/Makefile") CONFIG_FILES="$CONFIG_FILES src/ibrowse/Makefile" ;; +- "src/mochiweb/Makefile") CONFIG_FILES="$CONFIG_FILES src/mochiweb/Makefile" ;; + "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; + "test/etap/Makefile") CONFIG_FILES="$CONFIG_FILES test/etap/Makefile" ;; + "test/etap/test_util.erl") CONFIG_FILES="$CONFIG_FILES test/etap/test_util.erl" ;; +diff --git a/configure.ac b/configure.ac +index 0478b4f..a53df9b 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -336,7 +336,6 @@ AC_CONFIG_FILES([src/couchdb/couch.app.tpl]) + AC_CONFIG_FILES([src/couchdb/Makefile]) + AC_CONFIG_FILES([src/couchdb/priv/Makefile]) + AC_CONFIG_FILES([src/ibrowse/Makefile]) +-AC_CONFIG_FILES([src/mochiweb/Makefile]) + AC_CONFIG_FILES([test/Makefile]) + AC_CONFIG_FILES([test/etap/Makefile]) + AC_CONFIG_FILES([test/etap/test_util.erl]) +diff --git a/src/Makefile.am b/src/Makefile.am +index 19a5d20..5a6646f 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -10,4 +10,4 @@ + ## License for the specific language governing permissions and limitations under + ## the License. + +-SUBDIRS = couchdb ibrowse mochiweb ++SUBDIRS = couchdb ibrowse +diff --git a/src/Makefile.in b/src/Makefile.in +index 2422e39..d1e6ba5 100644 +--- a/src/Makefile.in ++++ b/src/Makefile.in +@@ -239,7 +239,7 @@ version_minor = @version_minor@ + version_release = @version_release@ + version_revision = @version_revision@ + version_stage = @version_stage@ +-SUBDIRS = couchdb ibrowse mochiweb ++SUBDIRS = couchdb ibrowse + all: all-recursive + + .SUFFIXES: +diff --git a/src/mochiweb/Makefile.am b/src/mochiweb/Makefile.am +deleted file mode 100644 +index 608d4dc..0000000 +--- a/src/mochiweb/Makefile.am ++++ /dev/null +@@ -1,80 +0,0 @@ +-## Licensed under the Apache License, Version 2.0 (the "License"); you may not +-## use this file except in compliance with the License. You may obtain a copy of +-## the License at +-## +-## http://www.apache.org/licenses/LICENSE-2.0 +-## +-## Unless required by applicable law or agreed to in writing, software +-## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-## License for the specific language governing permissions and limitations under +-## the License. +- +-mochiwebebindir = $(localerlanglibdir)/mochiweb-r97/ebin +- +-mochiweb_file_collection = \ +- mochifmt.erl \ +- mochifmt_records.erl \ +- mochifmt_std.erl \ +- mochihex.erl \ +- mochijson.erl \ +- mochijson2.erl \ +- mochinum.erl \ +- mochiweb.app.in \ +- mochiweb.erl \ +- mochiweb_app.erl \ +- mochiweb_charref.erl \ +- mochiweb_cookies.erl \ +- mochiweb_echo.erl \ +- mochiweb_headers.erl \ +- mochiweb_html.erl \ +- mochiweb_http.erl \ +- mochiweb_multipart.erl \ +- mochiweb_request.erl \ +- mochiweb_response.erl \ +- mochiweb_skel.erl \ +- mochiweb_socket_server.erl \ +- mochiweb_sup.erl \ +- mochiweb_util.erl \ +- reloader.erl +- +-mochiwebebin_make_generated_file_list = \ +- mochifmt.beam \ +- mochifmt_records.beam \ +- mochifmt_std.beam \ +- mochihex.beam \ +- mochijson.beam \ +- mochijson2.beam \ +- mochinum.beam \ +- mochiweb.app \ +- mochiweb.beam \ +- mochiweb_app.beam \ +- mochiweb_charref.beam \ +- mochiweb_cookies.beam \ +- mochiweb_echo.beam \ +- mochiweb_headers.beam \ +- mochiweb_html.beam \ +- mochiweb_http.beam \ +- mochiweb_multipart.beam \ +- mochiweb_request.beam \ +- mochiweb_response.beam \ +- mochiweb_skel.beam \ +- mochiweb_socket_server.beam \ +- mochiweb_sup.beam \ +- mochiweb_util.beam \ +- reloader.beam +- +-mochiwebebin_DATA = \ +- $(mochiwebebin_make_generated_file_list) +- +-EXTRA_DIST = \ +- $(mochiweb_file_collection) +- +-CLEANFILES = \ +- $(mochiwebebin_make_generated_file_list) +- +-%.app: %.app.in +- cp $< $@ +- +-%.beam: %.erl +- $(ERLC) $(ERLC_FLAGS) $< +diff --git a/src/mochiweb/Makefile.in b/src/mochiweb/Makefile.in +deleted file mode 100644 +index df1c548..0000000 +--- a/src/mochiweb/Makefile.in ++++ /dev/null +@@ -1,506 +0,0 @@ +-# Makefile.in generated by automake 1.11 from Makefile.am. +-# @configure_input@ +- +-# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +-# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +-# Inc. +-# This Makefile.in is free software; the Free Software Foundation +-# gives unlimited permission to copy and/or distribute it, +-# with or without modifications, as long as this notice is preserved. +- +-# This program is distributed in the hope that it will be useful, +-# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +-# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +-# PARTICULAR PURPOSE. +- +-@SET_MAKE@ +- +-VPATH = @srcdir@ +-pkgdatadir = $(datadir)/@PACKAGE@ +-pkgincludedir = $(includedir)/@PACKAGE@ +-pkglibdir = $(libdir)/@PACKAGE@ +-pkglibexecdir = $(libexecdir)/@PACKAGE@ +-am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +-install_sh_DATA = $(install_sh) -c -m 644 +-install_sh_PROGRAM = $(install_sh) -c +-install_sh_SCRIPT = $(install_sh) -c +-INSTALL_HEADER = $(INSTALL_DATA) +-transform = $(program_transform_name) +-NORMAL_INSTALL = : +-PRE_INSTALL = : +-POST_INSTALL = : +-NORMAL_UNINSTALL = : +-PRE_UNINSTALL = : +-POST_UNINSTALL = : +-build_triplet = @build@ +-host_triplet = @host@ +-subdir = src/mochiweb +-DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +-ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +-am__aclocal_m4_deps = $(top_srcdir)/m4/ac_check_curl.m4 \ +- $(top_srcdir)/acinclude.m4 $(top_srcdir)/m4/ac_check_icu.m4 \ +- $(top_srcdir)/configure.ac +-am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ +- $(ACLOCAL_M4) +-mkinstalldirs = $(install_sh) -d +-CONFIG_HEADER = $(top_builddir)/config.h +-CONFIG_CLEAN_FILES = +-CONFIG_CLEAN_VPATH_FILES = +-SOURCES = +-DIST_SOURCES = +-am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +-am__vpath_adj = case $$p in \ +- $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ +- *) f=$$p;; \ +- esac; +-am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +-am__install_max = 40 +-am__nobase_strip_setup = \ +- srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +-am__nobase_strip = \ +- for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +-am__nobase_list = $(am__nobase_strip_setup); \ +- for p in $$list; do echo "$$p $$p"; done | \ +- sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ +- $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ +- if (++n[$$2] == $(am__install_max)) \ +- { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ +- END { for (dir in files) print dir, files[dir] }' +-am__base_list = \ +- sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ +- sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +-am__installdirs = "$(DESTDIR)$(mochiwebebindir)" +-DATA = $(mochiwebebin_DATA) +-DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +-ACLOCAL = @ACLOCAL@ +-AMTAR = @AMTAR@ +-AR = @AR@ +-AUTOCONF = @AUTOCONF@ +-AUTOHEADER = @AUTOHEADER@ +-AUTOMAKE = @AUTOMAKE@ +-AWK = @AWK@ +-CC = @CC@ +-CCDEPMODE = @CCDEPMODE@ +-CFLAGS = @CFLAGS@ +-CPP = @CPP@ +-CPPFLAGS = @CPPFLAGS@ +-CURL_CFLAGS = @CURL_CFLAGS@ +-CURL_CONFIG = @CURL_CONFIG@ +-CURL_LDFLAGS = @CURL_LDFLAGS@ +-CURL_LIBS = @CURL_LIBS@ +-CYGPATH_W = @CYGPATH_W@ +-DEFS = @DEFS@ +-DEPDIR = @DEPDIR@ +-DSYMUTIL = @DSYMUTIL@ +-DUMPBIN = @DUMPBIN@ +-ECHO_C = @ECHO_C@ +-ECHO_N = @ECHO_N@ +-ECHO_T = @ECHO_T@ +-EGREP = @EGREP@ +-ERL = @ERL@ +-ERLC = @ERLC@ +-ERLC_FLAGS = @ERLC_FLAGS@ +-EXEEXT = @EXEEXT@ +-FGREP = @FGREP@ +-FLAGS = @FLAGS@ +-GREP = @GREP@ +-HELP2MAN_EXECUTABLE = @HELP2MAN_EXECUTABLE@ +-ICU_CFLAGS = @ICU_CFLAGS@ +-ICU_CONFIG = @ICU_CONFIG@ +-ICU_CXXFLAGS = @ICU_CXXFLAGS@ +-ICU_LIBS = @ICU_LIBS@ +-ICU_LOCAL_BIN = @ICU_LOCAL_BIN@ +-ICU_LOCAL_CFLAGS = @ICU_LOCAL_CFLAGS@ +-ICU_LOCAL_LDFLAGS = @ICU_LOCAL_LDFLAGS@ +-INSTALL = @INSTALL@ +-INSTALL_DATA = @INSTALL_DATA@ +-INSTALL_PROGRAM = @INSTALL_PROGRAM@ +-INSTALL_SCRIPT = @INSTALL_SCRIPT@ +-INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +-JSLIB = @JSLIB@ +-LD = @LD@ +-LDFLAGS = @LDFLAGS@ +-LIBOBJS = @LIBOBJS@ +-LIBS = @LIBS@ +-LIBTOOL = @LIBTOOL@ +-LIPO = @LIPO@ +-LN_S = @LN_S@ +-LTLIBOBJS = @LTLIBOBJS@ +-MAKEINFO = @MAKEINFO@ +-MKDIR_P = @MKDIR_P@ +-NM = @NM@ +-NMEDIT = @NMEDIT@ +-OBJDUMP = @OBJDUMP@ +-OBJEXT = @OBJEXT@ +-OTOOL = @OTOOL@ +-OTOOL64 = @OTOOL64@ +-PACKAGE = @PACKAGE@ +-PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +-PACKAGE_NAME = @PACKAGE_NAME@ +-PACKAGE_STRING = @PACKAGE_STRING@ +-PACKAGE_TARNAME = @PACKAGE_TARNAME@ +-PACKAGE_URL = @PACKAGE_URL@ +-PACKAGE_VERSION = @PACKAGE_VERSION@ +-PATH_SEPARATOR = @PATH_SEPARATOR@ +-RANLIB = @RANLIB@ +-SED = @SED@ +-SET_MAKE = @SET_MAKE@ +-SHELL = @SHELL@ +-STRIP = @STRIP@ +-VERSION = @VERSION@ +-abs_builddir = @abs_builddir@ +-abs_srcdir = @abs_srcdir@ +-abs_top_builddir = @abs_top_builddir@ +-abs_top_srcdir = @abs_top_srcdir@ +-ac_ct_CC = @ac_ct_CC@ +-ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +-am__include = @am__include@ +-am__leading_dot = @am__leading_dot@ +-am__quote = @am__quote@ +-am__tar = @am__tar@ +-am__untar = @am__untar@ +-bindir = @bindir@ +-bug_uri = @bug_uri@ +-build = @build@ +-build_alias = @build_alias@ +-build_cpu = @build_cpu@ +-build_os = @build_os@ +-build_vendor = @build_vendor@ +-builddir = @builddir@ +-datadir = @datadir@ +-datarootdir = @datarootdir@ +-docdir = @docdir@ +-dvidir = @dvidir@ +-exec_prefix = @exec_prefix@ +-host = @host@ +-host_alias = @host_alias@ +-host_cpu = @host_cpu@ +-host_os = @host_os@ +-host_vendor = @host_vendor@ +-htmldir = @htmldir@ +-includedir = @includedir@ +-infodir = @infodir@ +-initdir = @initdir@ +-install_sh = @install_sh@ +-launchddir = @launchddir@ +-libdir = @libdir@ +-libexecdir = @libexecdir@ +-localconfdir = @localconfdir@ +-localdatadir = @localdatadir@ +-localdocdir = @localdocdir@ +-localedir = @localedir@ +-localerlanglibdir = @localerlanglibdir@ +-locallibbindir = @locallibbindir@ +-locallibdir = @locallibdir@ +-localstatedir = @localstatedir@ +-localstatelibdir = @localstatelibdir@ +-localstatelogdir = @localstatelogdir@ +-localstaterundir = @localstaterundir@ +-lt_ECHO = @lt_ECHO@ +-mandir = @mandir@ +-mkdir_p = @mkdir_p@ +-oldincludedir = @oldincludedir@ +-package_author_address = @package_author_address@ +-package_author_name = @package_author_name@ +-package_identifier = @package_identifier@ +-package_name = @package_name@ +-package_tarname = @package_tarname@ +-pdfdir = @pdfdir@ +-prefix = @prefix@ +-program_transform_name = @program_transform_name@ +-psdir = @psdir@ +-sbindir = @sbindir@ +-sharedstatedir = @sharedstatedir@ +-srcdir = @srcdir@ +-sysconfdir = @sysconfdir@ +-target_alias = @target_alias@ +-top_build_prefix = @top_build_prefix@ +-top_builddir = @top_builddir@ +-top_srcdir = @top_srcdir@ +-version = @version@ +-version_major = @version_major@ +-version_minor = @version_minor@ +-version_release = @version_release@ +-version_revision = @version_revision@ +-version_stage = @version_stage@ +-mochiwebebindir = $(localerlanglibdir)/mochiweb-r97/ebin +-mochiweb_file_collection = \ +- mochifmt.erl \ +- mochifmt_records.erl \ +- mochifmt_std.erl \ +- mochihex.erl \ +- mochijson.erl \ +- mochijson2.erl \ +- mochinum.erl \ +- mochiweb.app.in \ +- mochiweb.erl \ +- mochiweb_app.erl \ +- mochiweb_charref.erl \ +- mochiweb_cookies.erl \ +- mochiweb_echo.erl \ +- mochiweb_headers.erl \ +- mochiweb_html.erl \ +- mochiweb_http.erl \ +- mochiweb_multipart.erl \ +- mochiweb_request.erl \ +- mochiweb_response.erl \ +- mochiweb_skel.erl \ +- mochiweb_socket_server.erl \ +- mochiweb_sup.erl \ +- mochiweb_util.erl \ +- reloader.erl +- +-mochiwebebin_make_generated_file_list = \ +- mochifmt.beam \ +- mochifmt_records.beam \ +- mochifmt_std.beam \ +- mochihex.beam \ +- mochijson.beam \ +- mochijson2.beam \ +- mochinum.beam \ +- mochiweb.app \ +- mochiweb.beam \ +- mochiweb_app.beam \ +- mochiweb_charref.beam \ +- mochiweb_cookies.beam \ +- mochiweb_echo.beam \ +- mochiweb_headers.beam \ +- mochiweb_html.beam \ +- mochiweb_http.beam \ +- mochiweb_multipart.beam \ +- mochiweb_request.beam \ +- mochiweb_response.beam \ +- mochiweb_skel.beam \ +- mochiweb_socket_server.beam \ +- mochiweb_sup.beam \ +- mochiweb_util.beam \ +- reloader.beam +- +-mochiwebebin_DATA = \ +- $(mochiwebebin_make_generated_file_list) +- +-EXTRA_DIST = \ +- $(mochiweb_file_collection) +- +-CLEANFILES = \ +- $(mochiwebebin_make_generated_file_list) +- +-all: all-am +- +-.SUFFIXES: +-$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) +- @for dep in $?; do \ +- case '$(am__configure_deps)' in \ +- *$$dep*) \ +- ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ +- && { if test -f $@; then exit 0; else break; fi; }; \ +- exit 1;; \ +- esac; \ +- done; \ +- echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/mochiweb/Makefile'; \ +- $(am__cd) $(top_srcdir) && \ +- $(AUTOMAKE) --foreign src/mochiweb/Makefile +-.PRECIOUS: Makefile +-Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status +- @case '$?' in \ +- *config.status*) \ +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ +- *) \ +- echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ +- cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ +- esac; +- +-$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +- +-$(top_srcdir)/configure: $(am__configure_deps) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +-$(ACLOCAL_M4): $(am__aclocal_m4_deps) +- cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +-$(am__aclocal_m4_deps): +- +-mostlyclean-libtool: +- -rm -f *.lo +- +-clean-libtool: +- -rm -rf .libs _libs +-install-mochiwebebinDATA: $(mochiwebebin_DATA) +- @$(NORMAL_INSTALL) +- test -z "$(mochiwebebindir)" || $(MKDIR_P) "$(DESTDIR)$(mochiwebebindir)" +- @list='$(mochiwebebin_DATA)'; test -n "$(mochiwebebindir)" || list=; \ +- for p in $$list; do \ +- if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ +- echo "$$d$$p"; \ +- done | $(am__base_list) | \ +- while read files; do \ +- echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(mochiwebebindir)'"; \ +- $(INSTALL_DATA) $$files "$(DESTDIR)$(mochiwebebindir)" || exit $$?; \ +- done +- +-uninstall-mochiwebebinDATA: +- @$(NORMAL_UNINSTALL) +- @list='$(mochiwebebin_DATA)'; test -n "$(mochiwebebindir)" || list=; \ +- files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ +- test -n "$$files" || exit 0; \ +- echo " ( cd '$(DESTDIR)$(mochiwebebindir)' && rm -f" $$files ")"; \ +- cd "$(DESTDIR)$(mochiwebebindir)" && rm -f $$files +-tags: TAGS +-TAGS: +- +-ctags: CTAGS +-CTAGS: +- +- +-distdir: $(DISTFILES) +- @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +- topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ +- list='$(DISTFILES)'; \ +- dist_files=`for file in $$list; do echo $$file; done | \ +- sed -e "s|^$$srcdirstrip/||;t" \ +- -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ +- case $$dist_files in \ +- */*) $(MKDIR_P) `echo "$$dist_files" | \ +- sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ +- sort -u` ;; \ +- esac; \ +- for file in $$dist_files; do \ +- if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ +- if test -d $$d/$$file; then \ +- dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ +- if test -d "$(distdir)/$$file"; then \ +- find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +- fi; \ +- if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ +- cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ +- find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ +- fi; \ +- cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ +- else \ +- test -f "$(distdir)/$$file" \ +- || cp -p $$d/$$file "$(distdir)/$$file" \ +- || exit 1; \ +- fi; \ +- done +-check-am: all-am +-check: check-am +-all-am: Makefile $(DATA) +-installdirs: +- for dir in "$(DESTDIR)$(mochiwebebindir)"; do \ +- test -z "$$dir" || $(MKDIR_P) "$$dir"; \ +- done +-install: install-am +-install-exec: install-exec-am +-install-data: install-data-am +-uninstall: uninstall-am +- +-install-am: all-am +- @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am +- +-installcheck: installcheck-am +-install-strip: +- $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ +- install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ +- `test -z '$(STRIP)' || \ +- echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +-mostlyclean-generic: +- +-clean-generic: +- -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) +- +-distclean-generic: +- -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) +- -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) +- +-maintainer-clean-generic: +- @echo "This command is intended for maintainers to use" +- @echo "it deletes files that may require special tools to rebuild." +-clean: clean-am +- +-clean-am: clean-generic clean-libtool mostlyclean-am +- +-distclean: distclean-am +- -rm -f Makefile +-distclean-am: clean-am distclean-generic +- +-dvi: dvi-am +- +-dvi-am: +- +-html: html-am +- +-html-am: +- +-info: info-am +- +-info-am: +- +-install-data-am: install-mochiwebebinDATA +- +-install-dvi: install-dvi-am +- +-install-dvi-am: +- +-install-exec-am: +- +-install-html: install-html-am +- +-install-html-am: +- +-install-info: install-info-am +- +-install-info-am: +- +-install-man: +- +-install-pdf: install-pdf-am +- +-install-pdf-am: +- +-install-ps: install-ps-am +- +-install-ps-am: +- +-installcheck-am: +- +-maintainer-clean: maintainer-clean-am +- -rm -f Makefile +-maintainer-clean-am: distclean-am maintainer-clean-generic +- +-mostlyclean: mostlyclean-am +- +-mostlyclean-am: mostlyclean-generic mostlyclean-libtool +- +-pdf: pdf-am +- +-pdf-am: +- +-ps: ps-am +- +-ps-am: +- +-uninstall-am: uninstall-mochiwebebinDATA +- +-.MAKE: install-am install-strip +- +-.PHONY: all all-am check check-am clean clean-generic clean-libtool \ +- distclean distclean-generic distclean-libtool distdir dvi \ +- dvi-am html html-am info info-am install install-am \ +- install-data install-data-am install-dvi install-dvi-am \ +- install-exec install-exec-am install-html install-html-am \ +- install-info install-info-am install-man \ +- install-mochiwebebinDATA install-pdf install-pdf-am install-ps \ +- install-ps-am install-strip installcheck installcheck-am \ +- installdirs maintainer-clean maintainer-clean-generic \ +- mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ +- ps ps-am uninstall uninstall-am uninstall-mochiwebebinDATA +- +- +-%.app: %.app.in +- cp $< $@ +- +-%.beam: %.erl +- $(ERLC) $(ERLC_FLAGS) $< +- +-# Tell versions [3.59,3.63) of GNU make to not export all variables. +-# Otherwise a system limit (for SysV at least) may be exceeded. +-.NOEXPORT: +diff --git a/src/mochiweb/mochifmt.erl b/src/mochiweb/mochifmt.erl +deleted file mode 100644 +index da0a133..0000000 +--- a/src/mochiweb/mochifmt.erl ++++ /dev/null +@@ -1,426 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2008 Mochi Media, Inc. +- +-%% @doc String Formatting for Erlang, inspired by Python 2.6 +-%% (PEP 3101). +-%% +--module(mochifmt). +--author('bob@mochimedia.com'). +--export([format/2, format_field/2, convert_field/2, get_value/2, get_field/2]). +--export([tokenize/1, format/3, get_field/3, format_field/3]). +--export([bformat/2, bformat/3]). +--export([f/2, f/3]). +--export([test/0]). +- +--record(conversion, {length, precision, ctype, align, fill_char, sign}). +- +-%% @spec tokenize(S::string()) -> tokens() +-%% @doc Tokenize a format string into mochifmt's internal format. +-tokenize(S) -> +- {?MODULE, tokenize(S, "", [])}. +- +-%% @spec convert_field(Arg, Conversion::conversion()) -> term() +-%% @doc Process Arg according to the given explicit conversion specifier. +-convert_field(Arg, "") -> +- Arg; +-convert_field(Arg, "r") -> +- repr(Arg); +-convert_field(Arg, "s") -> +- str(Arg). +- +-%% @spec get_value(Key::string(), Args::args()) -> term() +-%% @doc Get the Key from Args. If Args is a tuple then convert Key to +-%% an integer and get element(1 + Key, Args). If Args is a list and Key +-%% can be parsed as an integer then use lists:nth(1 + Key, Args), +-%% otherwise try and look for Key in Args as a proplist, converting +-%% Key to an atom or binary if necessary. +-get_value(Key, Args) when is_tuple(Args) -> +- element(1 + list_to_integer(Key), Args); +-get_value(Key, Args) when is_list(Args) -> +- try lists:nth(1 + list_to_integer(Key), Args) +- catch error:_ -> +- {_K, V} = proplist_lookup(Key, Args), +- V +- end. +- +-%% @spec get_field(Key::string(), Args) -> term() +-%% @doc Consecutively call get_value/2 on parts of Key delimited by ".", +-%% replacing Args with the result of the previous get_value. This +-%% is used to implement formats such as {0.0}. +-get_field(Key, Args) -> +- get_field(Key, Args, ?MODULE). +- +-%% @spec get_field(Key::string(), Args, Module) -> term() +-%% @doc Consecutively call Module:get_value/2 on parts of Key delimited by ".", +-%% replacing Args with the result of the previous get_value. This +-%% is used to implement formats such as {0.0}. +-get_field(Key, Args, Module) -> +- {Name, Next} = lists:splitwith(fun (C) -> C =/= $. end, Key), +- Res = try Module:get_value(Name, Args) +- catch error:undef -> get_value(Name, Args) end, +- case Next of +- "" -> +- Res; +- "." ++ S1 -> +- get_field(S1, Res, Module) +- end. +- +-%% @spec format(Format::string(), Args) -> iolist() +-%% @doc Format Args with Format. +-format(Format, Args) -> +- format(Format, Args, ?MODULE). +- +-%% @spec format(Format::string(), Args, Module) -> iolist() +-%% @doc Format Args with Format using Module. +-format({?MODULE, Parts}, Args, Module) -> +- format2(Parts, Args, Module, []); +-format(S, Args, Module) -> +- format(tokenize(S), Args, Module). +- +-%% @spec format_field(Arg, Format) -> iolist() +-%% @doc Format Arg with Format. +-format_field(Arg, Format) -> +- format_field(Arg, Format, ?MODULE). +- +-%% @spec format_field(Arg, Format, _Module) -> iolist() +-%% @doc Format Arg with Format. +-format_field(Arg, Format, _Module) -> +- F = default_ctype(Arg, parse_std_conversion(Format)), +- fix_padding(fix_sign(convert2(Arg, F), F), F). +- +-%% @spec f(Format::string(), Args) -> string() +-%% @doc Format Args with Format and return a string(). +-f(Format, Args) -> +- f(Format, Args, ?MODULE). +- +-%% @spec f(Format::string(), Args, Module) -> string() +-%% @doc Format Args with Format using Module and return a string(). +-f(Format, Args, Module) -> +- case lists:member(${, Format) of +- true -> +- binary_to_list(bformat(Format, Args, Module)); +- false -> +- Format +- end. +- +-%% @spec bformat(Format::string(), Args) -> binary() +-%% @doc Format Args with Format and return a binary(). +-bformat(Format, Args) -> +- iolist_to_binary(format(Format, Args)). +- +-%% @spec bformat(Format::string(), Args, Module) -> binary() +-%% @doc Format Args with Format using Module and return a binary(). +-bformat(Format, Args, Module) -> +- iolist_to_binary(format(Format, Args, Module)). +- +-%% @spec test() -> ok +-%% @doc Run tests. +-test() -> +- ok = test_tokenize(), +- ok = test_format(), +- ok = test_std(), +- ok = test_records(), +- ok. +- +-%% Internal API +- +-add_raw("", Acc) -> +- Acc; +-add_raw(S, Acc) -> +- [{raw, lists:reverse(S)} | Acc]. +- +-tokenize([], S, Acc) -> +- lists:reverse(add_raw(S, Acc)); +-tokenize("{{" ++ Rest, S, Acc) -> +- tokenize(Rest, "{" ++ S, Acc); +-tokenize("{" ++ Rest, S, Acc) -> +- {Format, Rest1} = tokenize_format(Rest), +- tokenize(Rest1, "", [{format, make_format(Format)} | add_raw(S, Acc)]); +-tokenize("}}" ++ Rest, S, Acc) -> +- tokenize(Rest, "}" ++ S, Acc); +-tokenize([C | Rest], S, Acc) -> +- tokenize(Rest, [C | S], Acc). +- +-tokenize_format(S) -> +- tokenize_format(S, 1, []). +- +-tokenize_format("}" ++ Rest, 1, Acc) -> +- {lists:reverse(Acc), Rest}; +-tokenize_format("}" ++ Rest, N, Acc) -> +- tokenize_format(Rest, N - 1, "}" ++ Acc); +-tokenize_format("{" ++ Rest, N, Acc) -> +- tokenize_format(Rest, 1 + N, "{" ++ Acc); +-tokenize_format([C | Rest], N, Acc) -> +- tokenize_format(Rest, N, [C | Acc]). +- +-make_format(S) -> +- {Name0, Spec} = case lists:splitwith(fun (C) -> C =/= $: end, S) of +- {_, ""} -> +- {S, ""}; +- {SN, ":" ++ SS} -> +- {SN, SS} +- end, +- {Name, Transform} = case lists:splitwith(fun (C) -> C =/= $! end, Name0) of +- {_, ""} -> +- {Name0, ""}; +- {TN, "!" ++ TT} -> +- {TN, TT} +- end, +- {Name, Transform, Spec}. +- +-proplist_lookup(S, P) -> +- A = try list_to_existing_atom(S) +- catch error:_ -> make_ref() end, +- B = try list_to_binary(S) +- catch error:_ -> make_ref() end, +- proplist_lookup2({S, A, B}, P). +- +-proplist_lookup2({KS, KA, KB}, [{K, V} | _]) +- when KS =:= K orelse KA =:= K orelse KB =:= K -> +- {K, V}; +-proplist_lookup2(Keys, [_ | Rest]) -> +- proplist_lookup2(Keys, Rest). +- +-format2([], _Args, _Module, Acc) -> +- lists:reverse(Acc); +-format2([{raw, S} | Rest], Args, Module, Acc) -> +- format2(Rest, Args, Module, [S | Acc]); +-format2([{format, {Key, Convert, Format0}} | Rest], Args, Module, Acc) -> +- Format = f(Format0, Args, Module), +- V = case Module of +- ?MODULE -> +- V0 = get_field(Key, Args), +- V1 = convert_field(V0, Convert), +- format_field(V1, Format); +- _ -> +- V0 = try Module:get_field(Key, Args) +- catch error:undef -> get_field(Key, Args, Module) end, +- V1 = try Module:convert_field(V0, Convert) +- catch error:undef -> convert_field(V0, Convert) end, +- try Module:format_field(V1, Format) +- catch error:undef -> format_field(V1, Format, Module) end +- end, +- format2(Rest, Args, Module, [V | Acc]). +- +-default_ctype(_Arg, C=#conversion{ctype=N}) when N =/= undefined -> +- C; +-default_ctype(Arg, C) when is_integer(Arg) -> +- C#conversion{ctype=decimal}; +-default_ctype(Arg, C) when is_float(Arg) -> +- C#conversion{ctype=general}; +-default_ctype(_Arg, C) -> +- C#conversion{ctype=string}. +- +-fix_padding(Arg, #conversion{length=undefined}) -> +- Arg; +-fix_padding(Arg, F=#conversion{length=Length, fill_char=Fill0, align=Align0, +- ctype=Type}) -> +- Padding = Length - iolist_size(Arg), +- Fill = case Fill0 of +- undefined -> +- $\s; +- _ -> +- Fill0 +- end, +- Align = case Align0 of +- undefined -> +- case Type of +- string -> +- left; +- _ -> +- right +- end; +- _ -> +- Align0 +- end, +- case Padding > 0 of +- true -> +- do_padding(Arg, Padding, Fill, Align, F); +- false -> +- Arg +- end. +- +-do_padding(Arg, Padding, Fill, right, _F) -> +- [lists:duplicate(Padding, Fill), Arg]; +-do_padding(Arg, Padding, Fill, center, _F) -> +- LPadding = lists:duplicate(Padding div 2, Fill), +- RPadding = case Padding band 1 of +- 1 -> +- [Fill | LPadding]; +- _ -> +- LPadding +- end, +- [LPadding, Arg, RPadding]; +-do_padding([$- | Arg], Padding, Fill, sign_right, _F) -> +- [[$- | lists:duplicate(Padding, Fill)], Arg]; +-do_padding(Arg, Padding, Fill, sign_right, #conversion{sign=$-}) -> +- [lists:duplicate(Padding, Fill), Arg]; +-do_padding([S | Arg], Padding, Fill, sign_right, #conversion{sign=S}) -> +- [[S | lists:duplicate(Padding, Fill)], Arg]; +-do_padding(Arg, Padding, Fill, sign_right, #conversion{sign=undefined}) -> +- [lists:duplicate(Padding, Fill), Arg]; +-do_padding(Arg, Padding, Fill, left, _F) -> +- [Arg | lists:duplicate(Padding, Fill)]. +- +-fix_sign(Arg, #conversion{sign=$+}) when Arg >= 0 -> +- [$+, Arg]; +-fix_sign(Arg, #conversion{sign=$\s}) when Arg >= 0 -> +- [$\s, Arg]; +-fix_sign(Arg, _F) -> +- Arg. +- +-ctype($\%) -> percent; +-ctype($s) -> string; +-ctype($b) -> bin; +-ctype($o) -> oct; +-ctype($X) -> upper_hex; +-ctype($x) -> hex; +-ctype($c) -> char; +-ctype($d) -> decimal; +-ctype($g) -> general; +-ctype($f) -> fixed; +-ctype($e) -> exp. +- +-align($<) -> left; +-align($>) -> right; +-align($^) -> center; +-align($=) -> sign_right. +- +-convert2(Arg, F=#conversion{ctype=percent}) -> +- [convert2(100.0 * Arg, F#conversion{ctype=fixed}), $\%]; +-convert2(Arg, #conversion{ctype=string}) -> +- str(Arg); +-convert2(Arg, #conversion{ctype=bin}) -> +- erlang:integer_to_list(Arg, 2); +-convert2(Arg, #conversion{ctype=oct}) -> +- erlang:integer_to_list(Arg, 8); +-convert2(Arg, #conversion{ctype=upper_hex}) -> +- erlang:integer_to_list(Arg, 16); +-convert2(Arg, #conversion{ctype=hex}) -> +- string:to_lower(erlang:integer_to_list(Arg, 16)); +-convert2(Arg, #conversion{ctype=char}) when Arg < 16#80 -> +- [Arg]; +-convert2(Arg, #conversion{ctype=char}) -> +- xmerl_ucs:to_utf8(Arg); +-convert2(Arg, #conversion{ctype=decimal}) -> +- integer_to_list(Arg); +-convert2(Arg, #conversion{ctype=general, precision=undefined}) -> +- try mochinum:digits(Arg) +- catch error:undef -> io_lib:format("~g", [Arg]) end; +-convert2(Arg, #conversion{ctype=fixed, precision=undefined}) -> +- io_lib:format("~f", [Arg]); +-convert2(Arg, #conversion{ctype=exp, precision=undefined}) -> +- io_lib:format("~e", [Arg]); +-convert2(Arg, #conversion{ctype=general, precision=P}) -> +- io_lib:format("~." ++ integer_to_list(P) ++ "g", [Arg]); +-convert2(Arg, #conversion{ctype=fixed, precision=P}) -> +- io_lib:format("~." ++ integer_to_list(P) ++ "f", [Arg]); +-convert2(Arg, #conversion{ctype=exp, precision=P}) -> +- io_lib:format("~." ++ integer_to_list(P) ++ "e", [Arg]). +- +-str(A) when is_atom(A) -> +- atom_to_list(A); +-str(I) when is_integer(I) -> +- integer_to_list(I); +-str(F) when is_float(F) -> +- try mochinum:digits(F) +- catch error:undef -> io_lib:format("~g", [F]) end; +-str(L) when is_list(L) -> +- L; +-str(B) when is_binary(B) -> +- B; +-str(P) -> +- repr(P). +- +-repr(P) when is_float(P) -> +- try mochinum:digits(P) +- catch error:undef -> float_to_list(P) end; +-repr(P) -> +- io_lib:format("~p", [P]). +- +-parse_std_conversion(S) -> +- parse_std_conversion(S, #conversion{}). +- +-parse_std_conversion("", Acc) -> +- Acc; +-parse_std_conversion([Fill, Align | Spec], Acc) +- when Align =:= $< orelse Align =:= $> orelse Align =:= $= orelse Align =:= $^ -> +- parse_std_conversion(Spec, Acc#conversion{fill_char=Fill, +- align=align(Align)}); +-parse_std_conversion([Align | Spec], Acc) +- when Align =:= $< orelse Align =:= $> orelse Align =:= $= orelse Align =:= $^ -> +- parse_std_conversion(Spec, Acc#conversion{align=align(Align)}); +-parse_std_conversion([Sign | Spec], Acc) +- when Sign =:= $+ orelse Sign =:= $- orelse Sign =:= $\s -> +- parse_std_conversion(Spec, Acc#conversion{sign=Sign}); +-parse_std_conversion("0" ++ Spec, Acc) -> +- Align = case Acc#conversion.align of +- undefined -> +- sign_right; +- A -> +- A +- end, +- parse_std_conversion(Spec, Acc#conversion{fill_char=$0, align=Align}); +-parse_std_conversion(Spec=[D|_], Acc) when D >= $0 andalso D =< $9 -> +- {W, Spec1} = lists:splitwith(fun (C) -> C >= $0 andalso C =< $9 end, Spec), +- parse_std_conversion(Spec1, Acc#conversion{length=list_to_integer(W)}); +-parse_std_conversion([$. | Spec], Acc) -> +- case lists:splitwith(fun (C) -> C >= $0 andalso C =< $9 end, Spec) of +- {"", Spec1} -> +- parse_std_conversion(Spec1, Acc); +- {P, Spec1} -> +- parse_std_conversion(Spec1, +- Acc#conversion{precision=list_to_integer(P)}) +- end; +-parse_std_conversion([Type], Acc) -> +- parse_std_conversion("", Acc#conversion{ctype=ctype(Type)}). +- +-test_tokenize() -> +- {?MODULE, [{raw, "ABC"}]} = tokenize("ABC"), +- {?MODULE, [{format, {"0", "", ""}}]} = tokenize("{0}"), +- {?MODULE, [{raw, "ABC"}, {format, {"1", "", ""}}, {raw, "DEF"}]} = +- tokenize("ABC{1}DEF"), +- ok. +- +-test_format() -> +- <<" -4">> = bformat("{0:4}", [-4]), +- <<" 4">> = bformat("{0:4}", [4]), +- <<" 4">> = bformat("{0:{0}}", [4]), +- <<"4 ">> = bformat("{0:4}", ["4"]), +- <<"4 ">> = bformat("{0:{0}}", ["4"]), +- <<"1.2yoDEF">> = bformat("{2}{0}{1}{3}", {yo, "DE", 1.2, <<"F">>}), +- <<"cafebabe">> = bformat("{0:x}", {16#cafebabe}), +- <<"CAFEBABE">> = bformat("{0:X}", {16#cafebabe}), +- <<"CAFEBABE">> = bformat("{0:X}", {16#cafebabe}), +- <<"755">> = bformat("{0:o}", {8#755}), +- <<"a">> = bformat("{0:c}", {97}), +- %% Horizontal ellipsis +- <<226, 128, 166>> = bformat("{0:c}", {16#2026}), +- <<"11">> = bformat("{0:b}", {3}), +- <<"11">> = bformat("{0:b}", [3]), +- <<"11">> = bformat("{three:b}", [{three, 3}]), +- <<"11">> = bformat("{three:b}", [{"three", 3}]), +- <<"11">> = bformat("{three:b}", [{<<"three">>, 3}]), +- <<"\"foo\"">> = bformat("{0!r}", {"foo"}), +- <<"2008-5-4">> = bformat("{0.0}-{0.1}-{0.2}", {{2008,5,4}}), +- <<"2008-05-04">> = bformat("{0.0:04}-{0.1:02}-{0.2:02}", {{2008,5,4}}), +- <<"foo6bar-6">> = bformat("foo{1}{0}-{1}", {bar, 6}), +- <<"-'atom test'-">> = bformat("-{arg!r}-", [{arg, 'atom test'}]), +- <<"2008-05-04">> = bformat("{0.0:0{1.0}}-{0.1:0{1.1}}-{0.2:0{1.2}}", +- {{2008,5,4}, {4, 2, 2}}), +- ok. +- +-test_std() -> +- M = mochifmt_std:new(), +- <<"01">> = bformat("{0}{1}", [0, 1], M), +- ok. +- +-test_records() -> +- M = mochifmt_records:new([{conversion, record_info(fields, conversion)}]), +- R = #conversion{length=long, precision=hard, sign=peace}, +- long = M:get_value("length", R), +- hard = M:get_value("precision", R), +- peace = M:get_value("sign", R), +- <<"long hard">> = bformat("{length} {precision}", R, M), +- <<"long hard">> = bformat("{0.length} {0.precision}", [R], M), +- ok. +diff --git a/src/mochiweb/mochifmt_records.erl b/src/mochiweb/mochifmt_records.erl +deleted file mode 100644 +index 94c7797..0000000 +--- a/src/mochiweb/mochifmt_records.erl ++++ /dev/null +@@ -1,30 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2008 Mochi Media, Inc. +- +-%% @doc Formatter that understands records. +-%% +-%% Usage: +-%% +-%% 1> M = mochifmt_records:new([{rec, record_info(fields, rec)}]), +-%% M:format("{0.bar}", [#rec{bar=foo}]). +-%% foo +- +--module(mochifmt_records, [Recs]). +--author('bob@mochimedia.com'). +--export([get_value/2]). +- +-get_value(Key, Rec) when is_tuple(Rec) and is_atom(element(1, Rec)) -> +- try begin +- Atom = list_to_existing_atom(Key), +- {_, Fields} = proplists:lookup(element(1, Rec), Recs), +- element(get_rec_index(Atom, Fields, 2), Rec) +- end +- catch error:_ -> mochifmt:get_value(Key, Rec) +- end; +-get_value(Key, Args) -> +- mochifmt:get_value(Key, Args). +- +-get_rec_index(Atom, [Atom | _], Index) -> +- Index; +-get_rec_index(Atom, [_ | Rest], Index) -> +- get_rec_index(Atom, Rest, 1 + Index). +diff --git a/src/mochiweb/mochifmt_std.erl b/src/mochiweb/mochifmt_std.erl +deleted file mode 100644 +index 9442016..0000000 +--- a/src/mochiweb/mochifmt_std.erl ++++ /dev/null +@@ -1,23 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2008 Mochi Media, Inc. +- +-%% @doc Template module for a mochifmt formatter. +- +--module(mochifmt_std, []). +--author('bob@mochimedia.com'). +--export([format/2, get_value/2, format_field/2, get_field/2, convert_field/2]). +- +-format(Format, Args) -> +- mochifmt:format(Format, Args, THIS). +- +-get_field(Key, Args) -> +- mochifmt:get_field(Key, Args, THIS). +- +-convert_field(Key, Args) -> +- mochifmt:convert_field(Key, Args). +- +-get_value(Key, Args) -> +- mochifmt:get_value(Key, Args). +- +-format_field(Arg, Format) -> +- mochifmt:format_field(Arg, Format, THIS). +diff --git a/src/mochiweb/mochihex.erl b/src/mochiweb/mochihex.erl +deleted file mode 100644 +index 7fe6899..0000000 +--- a/src/mochiweb/mochihex.erl ++++ /dev/null +@@ -1,75 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2006 Mochi Media, Inc. +- +-%% @doc Utilities for working with hexadecimal strings. +- +--module(mochihex). +--author('bob@mochimedia.com'). +- +--export([test/0, to_hex/1, to_bin/1, to_int/1, dehex/1, hexdigit/1]). +- +-%% @type iolist() = [char() | binary() | iolist()] +-%% @type iodata() = iolist() | binary() +- +-%% @spec to_hex(integer | iolist()) -> string() +-%% @doc Convert an iolist to a hexadecimal string. +-to_hex(0) -> +- "0"; +-to_hex(I) when is_integer(I), I > 0 -> +- to_hex_int(I, []); +-to_hex(B) -> +- to_hex(iolist_to_binary(B), []). +- +-%% @spec to_bin(string()) -> binary() +-%% @doc Convert a hexadecimal string to a binary. +-to_bin(L) -> +- to_bin(L, []). +- +-%% @spec to_int(string()) -> integer() +-%% @doc Convert a hexadecimal string to an integer. +-to_int(L) -> +- erlang:list_to_integer(L, 16). +- +-%% @spec dehex(char()) -> integer() +-%% @doc Convert a hex digit to its integer value. +-dehex(C) when C >= $0, C =< $9 -> +- C - $0; +-dehex(C) when C >= $a, C =< $f -> +- C - $a + 10; +-dehex(C) when C >= $A, C =< $F -> +- C - $A + 10. +- +-%% @spec hexdigit(integer()) -> char() +-%% @doc Convert an integer less than 16 to a hex digit. +-hexdigit(C) when C >= 0, C =< 9 -> +- C + $0; +-hexdigit(C) when C =< 15 -> +- C + $a - 10. +- +-%% @spec test() -> ok +-%% @doc Test this module. +-test() -> +- "ff000ff1" = to_hex([255, 0, 15, 241]), +- <<255, 0, 15, 241>> = to_bin("ff000ff1"), +- 16#ff000ff1 = to_int("ff000ff1"), +- "ff000ff1" = to_hex(16#ff000ff1), +- ok. +- +- +-%% Internal API +- +-to_hex(<<>>, Acc) -> +- lists:reverse(Acc); +-to_hex(<>, Acc) -> +- to_hex(Rest, [hexdigit(C2), hexdigit(C1) | Acc]). +- +-to_hex_int(0, Acc) -> +- Acc; +-to_hex_int(I, Acc) -> +- to_hex_int(I bsr 4, [hexdigit(I band 15) | Acc]). +- +-to_bin([], Acc) -> +- iolist_to_binary(lists:reverse(Acc)); +-to_bin([C1, C2 | Rest], Acc) -> +- to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]). +- +diff --git a/src/mochiweb/mochijson.erl b/src/mochiweb/mochijson.erl +deleted file mode 100644 +index 029642a..0000000 +--- a/src/mochiweb/mochijson.erl ++++ /dev/null +@@ -1,525 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2006 Mochi Media, Inc. +- +-%% @doc Yet another JSON (RFC 4627) library for Erlang. +--module(mochijson). +--author('bob@mochimedia.com'). +--export([encoder/1, encode/1]). +--export([decoder/1, decode/1]). +--export([binary_encoder/1, binary_encode/1]). +--export([binary_decoder/1, binary_decode/1]). +--export([test/0]). +- +-% This is a macro to placate syntax highlighters.. +--define(Q, $\"). +--define(ADV_COL(S, N), S#decoder{column=N+S#decoder.column}). +--define(INC_COL(S), S#decoder{column=1+S#decoder.column}). +--define(INC_LINE(S), S#decoder{column=1, line=1+S#decoder.line}). +- +-%% @type iolist() = [char() | binary() | iolist()] +-%% @type iodata() = iolist() | binary() +-%% @type json_string() = atom | string() | binary() +-%% @type json_number() = integer() | float() +-%% @type json_array() = {array, [json_term()]} +-%% @type json_object() = {struct, [{json_string(), json_term()}]} +-%% @type json_term() = json_string() | json_number() | json_array() | +-%% json_object() +-%% @type encoding() = utf8 | unicode +-%% @type encoder_option() = {input_encoding, encoding()} | +-%% {handler, function()} +-%% @type decoder_option() = {input_encoding, encoding()} | +-%% {object_hook, function()} +-%% @type bjson_string() = binary() +-%% @type bjson_number() = integer() | float() +-%% @type bjson_array() = [bjson_term()] +-%% @type bjson_object() = {struct, [{bjson_string(), bjson_term()}]} +-%% @type bjson_term() = bjson_string() | bjson_number() | bjson_array() | +-%% bjson_object() +-%% @type binary_encoder_option() = {handler, function()} +-%% @type binary_decoder_option() = {object_hook, function()} +- +--record(encoder, {input_encoding=unicode, +- handler=null}). +- +--record(decoder, {input_encoding=utf8, +- object_hook=null, +- line=1, +- column=1, +- state=null}). +- +-%% @spec encoder([encoder_option()]) -> function() +-%% @doc Create an encoder/1 with the given options. +-encoder(Options) -> +- State = parse_encoder_options(Options, #encoder{}), +- fun (O) -> json_encode(O, State) end. +- +-%% @spec encode(json_term()) -> iolist() +-%% @doc Encode the given as JSON to an iolist. +-encode(Any) -> +- json_encode(Any, #encoder{}). +- +-%% @spec decoder([decoder_option()]) -> function() +-%% @doc Create a decoder/1 with the given options. +-decoder(Options) -> +- State = parse_decoder_options(Options, #decoder{}), +- fun (O) -> json_decode(O, State) end. +- +-%% @spec decode(iolist()) -> json_term() +-%% @doc Decode the given iolist to Erlang terms. +-decode(S) -> +- json_decode(S, #decoder{}). +- +-%% @spec binary_decoder([binary_decoder_option()]) -> function() +-%% @doc Create a binary_decoder/1 with the given options. +-binary_decoder(Options) -> +- mochijson2:decoder(Options). +- +-%% @spec binary_encoder([binary_encoder_option()]) -> function() +-%% @doc Create a binary_encoder/1 with the given options. +-binary_encoder(Options) -> +- mochijson2:encoder(Options). +- +-%% @spec binary_encode(bjson_term()) -> iolist() +-%% @doc Encode the given as JSON to an iolist, using lists for arrays and +-%% binaries for strings. +-binary_encode(Any) -> +- mochijson2:encode(Any). +- +-%% @spec binary_decode(iolist()) -> bjson_term() +-%% @doc Decode the given iolist to Erlang terms, using lists for arrays and +-%% binaries for strings. +-binary_decode(S) -> +- mochijson2:decode(S). +- +-test() -> +- test_all(), +- mochijson2:test(). +- +-%% Internal API +- +-parse_encoder_options([], State) -> +- State; +-parse_encoder_options([{input_encoding, Encoding} | Rest], State) -> +- parse_encoder_options(Rest, State#encoder{input_encoding=Encoding}); +-parse_encoder_options([{handler, Handler} | Rest], State) -> +- parse_encoder_options(Rest, State#encoder{handler=Handler}). +- +-parse_decoder_options([], State) -> +- State; +-parse_decoder_options([{input_encoding, Encoding} | Rest], State) -> +- parse_decoder_options(Rest, State#decoder{input_encoding=Encoding}); +-parse_decoder_options([{object_hook, Hook} | Rest], State) -> +- parse_decoder_options(Rest, State#decoder{object_hook=Hook}). +- +-json_encode(true, _State) -> +- "true"; +-json_encode(false, _State) -> +- "false"; +-json_encode(null, _State) -> +- "null"; +-json_encode(I, _State) when is_integer(I) -> +- integer_to_list(I); +-json_encode(F, _State) when is_float(F) -> +- mochinum:digits(F); +-json_encode(L, State) when is_list(L); is_binary(L); is_atom(L) -> +- json_encode_string(L, State); +-json_encode({array, Props}, State) when is_list(Props) -> +- json_encode_array(Props, State); +-json_encode({struct, Props}, State) when is_list(Props) -> +- json_encode_proplist(Props, State); +-json_encode(Bad, #encoder{handler=null}) -> +- exit({json_encode, {bad_term, Bad}}); +-json_encode(Bad, State=#encoder{handler=Handler}) -> +- json_encode(Handler(Bad), State). +- +-json_encode_array([], _State) -> +- "[]"; +-json_encode_array(L, State) -> +- F = fun (O, Acc) -> +- [$,, json_encode(O, State) | Acc] +- end, +- [$, | Acc1] = lists:foldl(F, "[", L), +- lists:reverse([$\] | Acc1]). +- +-json_encode_proplist([], _State) -> +- "{}"; +-json_encode_proplist(Props, State) -> +- F = fun ({K, V}, Acc) -> +- KS = case K of +- K when is_atom(K) -> +- json_encode_string_utf8(atom_to_list(K)); +- K when is_integer(K) -> +- json_encode_string(integer_to_list(K), State); +- K when is_list(K); is_binary(K) -> +- json_encode_string(K, State) +- end, +- VS = json_encode(V, State), +- [$,, VS, $:, KS | Acc] +- end, +- [$, | Acc1] = lists:foldl(F, "{", Props), +- lists:reverse([$\} | Acc1]). +- +-json_encode_string(A, _State) when is_atom(A) -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(atom_to_list(A))); +-json_encode_string(B, _State) when is_binary(B) -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(B)); +-json_encode_string(S, #encoder{input_encoding=utf8}) -> +- json_encode_string_utf8(S); +-json_encode_string(S, #encoder{input_encoding=unicode}) -> +- json_encode_string_unicode(S). +- +-json_encode_string_utf8(S) -> +- [?Q | json_encode_string_utf8_1(S)]. +- +-json_encode_string_utf8_1([C | Cs]) when C >= 0, C =< 16#7f -> +- NewC = case C of +- $\\ -> "\\\\"; +- ?Q -> "\\\""; +- _ when C >= $\s, C < 16#7f -> C; +- $\t -> "\\t"; +- $\n -> "\\n"; +- $\r -> "\\r"; +- $\f -> "\\f"; +- $\b -> "\\b"; +- _ when C >= 0, C =< 16#7f -> unihex(C); +- _ -> exit({json_encode, {bad_char, C}}) +- end, +- [NewC | json_encode_string_utf8_1(Cs)]; +-json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(All)); +-json_encode_string_utf8_1([]) -> +- "\"". +- +-json_encode_string_unicode(S) -> +- [?Q | json_encode_string_unicode_1(S)]. +- +-json_encode_string_unicode_1([C | Cs]) -> +- NewC = case C of +- $\\ -> "\\\\"; +- ?Q -> "\\\""; +- _ when C >= $\s, C < 16#7f -> C; +- $\t -> "\\t"; +- $\n -> "\\n"; +- $\r -> "\\r"; +- $\f -> "\\f"; +- $\b -> "\\b"; +- _ when C >= 0, C =< 16#10FFFF -> unihex(C); +- _ -> exit({json_encode, {bad_char, C}}) +- end, +- [NewC | json_encode_string_unicode_1(Cs)]; +-json_encode_string_unicode_1([]) -> +- "\"". +- +-dehex(C) when C >= $0, C =< $9 -> +- C - $0; +-dehex(C) when C >= $a, C =< $f -> +- C - $a + 10; +-dehex(C) when C >= $A, C =< $F -> +- C - $A + 10. +- +-hexdigit(C) when C >= 0, C =< 9 -> +- C + $0; +-hexdigit(C) when C =< 15 -> +- C + $a - 10. +- +-unihex(C) when C < 16#10000 -> +- <> = <>, +- Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], +- [$\\, $u | Digits]; +-unihex(C) when C =< 16#10FFFF -> +- N = C - 16#10000, +- S1 = 16#d800 bor ((N bsr 10) band 16#3ff), +- S2 = 16#dc00 bor (N band 16#3ff), +- [unihex(S1), unihex(S2)]. +- +-json_decode(B, S) when is_binary(B) -> +- json_decode(binary_to_list(B), S); +-json_decode(L, S) -> +- {Res, L1, S1} = decode1(L, S), +- {eof, [], _} = tokenize(L1, S1#decoder{state=trim}), +- Res. +- +-decode1(L, S=#decoder{state=null}) -> +- case tokenize(L, S#decoder{state=any}) of +- {{const, C}, L1, S1} -> +- {C, L1, S1}; +- {start_array, L1, S1} -> +- decode_array(L1, S1#decoder{state=any}, []); +- {start_object, L1, S1} -> +- decode_object(L1, S1#decoder{state=key}, []) +- end. +- +-make_object(V, #decoder{object_hook=null}) -> +- V; +-make_object(V, #decoder{object_hook=Hook}) -> +- Hook(V). +- +-decode_object(L, S=#decoder{state=key}, Acc) -> +- case tokenize(L, S) of +- {end_object, Rest, S1} -> +- V = make_object({struct, lists:reverse(Acc)}, S1), +- {V, Rest, S1#decoder{state=null}}; +- {{const, K}, Rest, S1} when is_list(K) -> +- {colon, L2, S2} = tokenize(Rest, S1), +- {V, L3, S3} = decode1(L2, S2#decoder{state=null}), +- decode_object(L3, S3#decoder{state=comma}, [{K, V} | Acc]) +- end; +-decode_object(L, S=#decoder{state=comma}, Acc) -> +- case tokenize(L, S) of +- {end_object, Rest, S1} -> +- V = make_object({struct, lists:reverse(Acc)}, S1), +- {V, Rest, S1#decoder{state=null}}; +- {comma, Rest, S1} -> +- decode_object(Rest, S1#decoder{state=key}, Acc) +- end. +- +-decode_array(L, S=#decoder{state=any}, Acc) -> +- case tokenize(L, S) of +- {end_array, Rest, S1} -> +- {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; +- {start_array, Rest, S1} -> +- {Array, Rest1, S2} = decode_array(Rest, S1#decoder{state=any}, []), +- decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); +- {start_object, Rest, S1} -> +- {Array, Rest1, S2} = decode_object(Rest, S1#decoder{state=key}, []), +- decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); +- {{const, Const}, Rest, S1} -> +- decode_array(Rest, S1#decoder{state=comma}, [Const | Acc]) +- end; +-decode_array(L, S=#decoder{state=comma}, Acc) -> +- case tokenize(L, S) of +- {end_array, Rest, S1} -> +- {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; +- {comma, Rest, S1} -> +- decode_array(Rest, S1#decoder{state=any}, Acc) +- end. +- +-tokenize_string(IoList=[C | _], S=#decoder{input_encoding=utf8}, Acc) +- when is_list(C); is_binary(C); C >= 16#7f -> +- List = xmerl_ucs:from_utf8(iolist_to_binary(IoList)), +- tokenize_string(List, S#decoder{input_encoding=unicode}, Acc); +-tokenize_string("\"" ++ Rest, S, Acc) -> +- {lists:reverse(Acc), Rest, ?INC_COL(S)}; +-tokenize_string("\\\"" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\" | Acc]); +-tokenize_string("\\\\" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\\ | Acc]); +-tokenize_string("\\/" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$/ | Acc]); +-tokenize_string("\\b" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\b | Acc]); +-tokenize_string("\\f" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\f | Acc]); +-tokenize_string("\\n" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\n | Acc]); +-tokenize_string("\\r" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\r | Acc]); +-tokenize_string("\\t" ++ Rest, S, Acc) -> +- tokenize_string(Rest, ?ADV_COL(S, 2), [$\t | Acc]); +-tokenize_string([$\\, $u, C3, C2, C1, C0 | Rest], S, Acc) -> +- % coalesce UTF-16 surrogate pair? +- C = dehex(C0) bor +- (dehex(C1) bsl 4) bor +- (dehex(C2) bsl 8) bor +- (dehex(C3) bsl 12), +- tokenize_string(Rest, ?ADV_COL(S, 6), [C | Acc]); +-tokenize_string([C | Rest], S, Acc) when C >= $\s; C < 16#10FFFF -> +- tokenize_string(Rest, ?ADV_COL(S, 1), [C | Acc]). +- +-tokenize_number(IoList=[C | _], Mode, S=#decoder{input_encoding=utf8}, Acc) +- when is_list(C); is_binary(C); C >= 16#7f -> +- List = xmerl_ucs:from_utf8(iolist_to_binary(IoList)), +- tokenize_number(List, Mode, S#decoder{input_encoding=unicode}, Acc); +-tokenize_number([$- | Rest], sign, S, []) -> +- tokenize_number(Rest, int, ?INC_COL(S), [$-]); +-tokenize_number(Rest, sign, S, []) -> +- tokenize_number(Rest, int, S, []); +-tokenize_number([$0 | Rest], int, S, Acc) -> +- tokenize_number(Rest, frac, ?INC_COL(S), [$0 | Acc]); +-tokenize_number([C | Rest], int, S, Acc) when C >= $1, C =< $9 -> +- tokenize_number(Rest, int1, ?INC_COL(S), [C | Acc]); +-tokenize_number([C | Rest], int1, S, Acc) when C >= $0, C =< $9 -> +- tokenize_number(Rest, int1, ?INC_COL(S), [C | Acc]); +-tokenize_number(Rest, int1, S, Acc) -> +- tokenize_number(Rest, frac, S, Acc); +-tokenize_number([$., C | Rest], frac, S, Acc) when C >= $0, C =< $9 -> +- tokenize_number(Rest, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); +-tokenize_number([E | Rest], frac, S, Acc) when E == $e; E == $E -> +- tokenize_number(Rest, esign, ?INC_COL(S), [$e, $0, $. | Acc]); +-tokenize_number(Rest, frac, S, Acc) -> +- {{int, lists:reverse(Acc)}, Rest, S}; +-tokenize_number([C | Rest], frac1, S, Acc) when C >= $0, C =< $9 -> +- tokenize_number(Rest, frac1, ?INC_COL(S), [C | Acc]); +-tokenize_number([E | Rest], frac1, S, Acc) when E == $e; E == $E -> +- tokenize_number(Rest, esign, ?INC_COL(S), [$e | Acc]); +-tokenize_number(Rest, frac1, S, Acc) -> +- {{float, lists:reverse(Acc)}, Rest, S}; +-tokenize_number([C | Rest], esign, S, Acc) when C == $-; C == $+ -> +- tokenize_number(Rest, eint, ?INC_COL(S), [C | Acc]); +-tokenize_number(Rest, esign, S, Acc) -> +- tokenize_number(Rest, eint, S, Acc); +-tokenize_number([C | Rest], eint, S, Acc) when C >= $0, C =< $9 -> +- tokenize_number(Rest, eint1, ?INC_COL(S), [C | Acc]); +-tokenize_number([C | Rest], eint1, S, Acc) when C >= $0, C =< $9 -> +- tokenize_number(Rest, eint1, ?INC_COL(S), [C | Acc]); +-tokenize_number(Rest, eint1, S, Acc) -> +- {{float, lists:reverse(Acc)}, Rest, S}. +- +-tokenize([], S=#decoder{state=trim}) -> +- {eof, [], S}; +-tokenize([L | Rest], S) when is_list(L) -> +- tokenize(L ++ Rest, S); +-tokenize([B | Rest], S) when is_binary(B) -> +- tokenize(xmerl_ucs:from_utf8(B) ++ Rest, S); +-tokenize("\r\n" ++ Rest, S) -> +- tokenize(Rest, ?INC_LINE(S)); +-tokenize("\n" ++ Rest, S) -> +- tokenize(Rest, ?INC_LINE(S)); +-tokenize([C | Rest], S) when C == $\s; C == $\t -> +- tokenize(Rest, ?INC_COL(S)); +-tokenize("{" ++ Rest, S) -> +- {start_object, Rest, ?INC_COL(S)}; +-tokenize("}" ++ Rest, S) -> +- {end_object, Rest, ?INC_COL(S)}; +-tokenize("[" ++ Rest, S) -> +- {start_array, Rest, ?INC_COL(S)}; +-tokenize("]" ++ Rest, S) -> +- {end_array, Rest, ?INC_COL(S)}; +-tokenize("," ++ Rest, S) -> +- {comma, Rest, ?INC_COL(S)}; +-tokenize(":" ++ Rest, S) -> +- {colon, Rest, ?INC_COL(S)}; +-tokenize("null" ++ Rest, S) -> +- {{const, null}, Rest, ?ADV_COL(S, 4)}; +-tokenize("true" ++ Rest, S) -> +- {{const, true}, Rest, ?ADV_COL(S, 4)}; +-tokenize("false" ++ Rest, S) -> +- {{const, false}, Rest, ?ADV_COL(S, 5)}; +-tokenize("\"" ++ Rest, S) -> +- {String, Rest1, S1} = tokenize_string(Rest, ?INC_COL(S), []), +- {{const, String}, Rest1, S1}; +-tokenize(L=[C | _], S) when C >= $0, C =< $9; C == $- -> +- case tokenize_number(L, sign, S, []) of +- {{int, Int}, Rest, S1} -> +- {{const, list_to_integer(Int)}, Rest, S1}; +- {{float, Float}, Rest, S1} -> +- {{const, list_to_float(Float)}, Rest, S1} +- end. +- +-%% testing constructs borrowed from the Yaws JSON implementation. +- +-%% Create an object from a list of Key/Value pairs. +- +-obj_new() -> +- {struct, []}. +- +-is_obj({struct, Props}) -> +- F = fun ({K, _}) when is_list(K) -> +- true; +- (_) -> +- false +- end, +- lists:all(F, Props). +- +-obj_from_list(Props) -> +- Obj = {struct, Props}, +- case is_obj(Obj) of +- true -> Obj; +- false -> exit(json_bad_object) +- end. +- +-%% Test for equivalence of Erlang terms. +-%% Due to arbitrary order of construction, equivalent objects might +-%% compare unequal as erlang terms, so we need to carefully recurse +-%% through aggregates (tuples and objects). +- +-equiv({struct, Props1}, {struct, Props2}) -> +- equiv_object(Props1, Props2); +-equiv({array, L1}, {array, L2}) -> +- equiv_list(L1, L2); +-equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +-equiv(S1, S2) when is_list(S1), is_list(S2) -> S1 == S2; +-equiv(true, true) -> true; +-equiv(false, false) -> true; +-equiv(null, null) -> true. +- +-%% Object representation and traversal order is unknown. +-%% Use the sledgehammer and sort property lists. +- +-equiv_object(Props1, Props2) -> +- L1 = lists:keysort(1, Props1), +- L2 = lists:keysort(1, Props2), +- Pairs = lists:zip(L1, L2), +- true = lists:all(fun({{K1, V1}, {K2, V2}}) -> +- equiv(K1, K2) and equiv(V1, V2) +- end, Pairs). +- +-%% Recursively compare tuple elements for equivalence. +- +-equiv_list([], []) -> +- true; +-equiv_list([V1 | L1], [V2 | L2]) -> +- case equiv(V1, V2) of +- true -> +- equiv_list(L1, L2); +- false -> +- false +- end. +- +-test_all() -> +- test_one(e2j_test_vec(utf8), 1). +- +-test_one([], _N) -> +- %% io:format("~p tests passed~n", [N-1]), +- ok; +-test_one([{E, J} | Rest], N) -> +- %% io:format("[~p] ~p ~p~n", [N, E, J]), +- true = equiv(E, decode(J)), +- true = equiv(E, decode(encode(E))), +- test_one(Rest, 1+N). +- +-e2j_test_vec(utf8) -> +- [ +- {1, "1"}, +- {3.1416, "3.14160"}, % text representation may truncate, trail zeroes +- {-1, "-1"}, +- {-3.1416, "-3.14160"}, +- {12.0e10, "1.20000e+11"}, +- {1.234E+10, "1.23400e+10"}, +- {-1.234E-10, "-1.23400e-10"}, +- {10.0, "1.0e+01"}, +- {123.456, "1.23456E+2"}, +- {10.0, "1e1"}, +- {"foo", "\"foo\""}, +- {"foo" ++ [5] ++ "bar", "\"foo\\u0005bar\""}, +- {"", "\"\""}, +- {"\"", "\"\\\"\""}, +- {"\n\n\n", "\"\\n\\n\\n\""}, +- {"\\", "\"\\\\\""}, +- {"\" \b\f\r\n\t\"", "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, +- {obj_new(), "{}"}, +- {obj_from_list([{"foo", "bar"}]), "{\"foo\":\"bar\"}"}, +- {obj_from_list([{"foo", "bar"}, {"baz", 123}]), +- "{\"foo\":\"bar\",\"baz\":123}"}, +- {{array, []}, "[]"}, +- {{array, [{array, []}]}, "[[]]"}, +- {{array, [1, "foo"]}, "[1,\"foo\"]"}, +- +- % json array in a json object +- {obj_from_list([{"foo", {array, [123]}}]), +- "{\"foo\":[123]}"}, +- +- % json object in a json object +- {obj_from_list([{"foo", obj_from_list([{"bar", true}])}]), +- "{\"foo\":{\"bar\":true}}"}, +- +- % fold evaluation order +- {obj_from_list([{"foo", {array, []}}, +- {"bar", obj_from_list([{"baz", true}])}, +- {"alice", "bob"}]), +- "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, +- +- % json object in a json array +- {{array, [-123, "foo", obj_from_list([{"bar", {array, []}}]), null]}, +- "[-123,\"foo\",{\"bar\":[]},null]"} +- ]. +diff --git a/src/mochiweb/mochijson2.erl b/src/mochiweb/mochijson2.erl +deleted file mode 100644 +index ee19458..0000000 +--- a/src/mochiweb/mochijson2.erl ++++ /dev/null +@@ -1,621 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works +-%% with binaries as strings, arrays as lists (without an {array, _}) +-%% wrapper and it only knows how to decode UTF-8 (and ASCII). +- +--module(mochijson2). +--author('bob@mochimedia.com'). +--export([encoder/1, encode/1]). +--export([decoder/1, decode/1]). +--export([test/0]). +- +-% This is a macro to placate syntax highlighters.. +--define(Q, $\"). +--define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, +- column=N+S#decoder.column}). +--define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, +- column=1+S#decoder.column}). +--define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, +- column=1, +- line=1+S#decoder.line}). +--define(INC_CHAR(S, C), +- case C of +- $\n -> +- S#decoder{column=1, +- line=1+S#decoder.line, +- offset=1+S#decoder.offset}; +- _ -> +- S#decoder{column=1+S#decoder.column, +- offset=1+S#decoder.offset} +- end). +--define(IS_WHITESPACE(C), +- (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). +- +-%% @type iolist() = [char() | binary() | iolist()] +-%% @type iodata() = iolist() | binary() +-%% @type json_string() = atom | binary() +-%% @type json_number() = integer() | float() +-%% @type json_array() = [json_term()] +-%% @type json_object() = {struct, [{json_string(), json_term()}]} +-%% @type json_term() = json_string() | json_number() | json_array() | +-%% json_object() +- +--record(encoder, {handler=null}). +- +--record(decoder, {object_hook=null, +- offset=0, +- line=1, +- column=1, +- state=null}). +- +-%% @spec encoder([encoder_option()]) -> function() +-%% @doc Create an encoder/1 with the given options. +-encoder(Options) -> +- State = parse_encoder_options(Options, #encoder{}), +- fun (O) -> json_encode(O, State) end. +- +-%% @spec encode(json_term()) -> iolist() +-%% @doc Encode the given as JSON to an iolist. +-encode(Any) -> +- json_encode(Any, #encoder{}). +- +-%% @spec decoder([decoder_option()]) -> function() +-%% @doc Create a decoder/1 with the given options. +-decoder(Options) -> +- State = parse_decoder_options(Options, #decoder{}), +- fun (O) -> json_decode(O, State) end. +- +-%% @spec decode(iolist()) -> json_term() +-%% @doc Decode the given iolist to Erlang terms. +-decode(S) -> +- try json_decode(S, #decoder{}) +- catch +- _:_ -> throw({invalid_json, S}) +- end. +- +-test() -> +- test_all(). +- +-%% Internal API +- +-parse_encoder_options([], State) -> +- State; +-parse_encoder_options([{handler, Handler} | Rest], State) -> +- parse_encoder_options(Rest, State#encoder{handler=Handler}). +- +-parse_decoder_options([], State) -> +- State; +-parse_decoder_options([{object_hook, Hook} | Rest], State) -> +- parse_decoder_options(Rest, State#decoder{object_hook=Hook}). +- +-json_encode(true, _State) -> +- <<"true">>; +-json_encode(false, _State) -> +- <<"false">>; +-json_encode(null, _State) -> +- <<"null">>; +-json_encode(I, _State) when is_integer(I) -> +- integer_to_list(I); +-json_encode(F, _State) when is_float(F) -> +- mochinum:digits(F); +-json_encode(S, State) when is_binary(S); is_atom(S) -> +- json_encode_string(S, State); +-json_encode(Array, State) when is_list(Array) -> +- json_encode_array(Array, State); +-json_encode({Props}, State) when is_list(Props) -> +- json_encode_proplist(Props, State); +-json_encode(Bad, #encoder{handler=null}) -> +- exit({json_encode, {bad_term, Bad}}); +-json_encode(Bad, State=#encoder{handler=Handler}) -> +- json_encode(Handler(Bad), State). +- +-json_encode_array([], _State) -> +- <<"[]">>; +-json_encode_array(L, State) -> +- F = fun (O, Acc) -> +- [$,, json_encode(O, State) | Acc] +- end, +- [$, | Acc1] = lists:foldl(F, "[", L), +- lists:reverse([$\] | Acc1]). +- +-json_encode_proplist([], _State) -> +- <<"{}">>; +-json_encode_proplist(Props, State) -> +- F = fun ({K, V}, Acc) -> +- KS = json_encode_string(K, State), +- VS = json_encode(V, State), +- [$,, VS, $:, KS | Acc] +- end, +- [$, | Acc1] = lists:foldl(F, "{", Props), +- lists:reverse([$\} | Acc1]). +- +-json_encode_string(A, _State) when is_atom(A) -> +- L = atom_to_list(A), +- case json_string_is_safe(L) of +- true -> +- [?Q, L, ?Q]; +- false -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(L), [?Q]) +- end; +-json_encode_string(B, _State) when is_binary(B) -> +- case json_bin_is_safe(B) of +- true -> +- [?Q, B, ?Q]; +- false -> +- json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q]) +- end; +-json_encode_string(I, _State) when is_integer(I) -> +- [?Q, integer_to_list(I), ?Q]; +-json_encode_string(L, _State) when is_list(L) -> +- case json_string_is_safe(L) of +- true -> +- [?Q, L, ?Q]; +- false -> +- json_encode_string_unicode(L, [?Q]) +- end. +- +-json_string_is_safe([]) -> +- true; +-json_string_is_safe([C | Rest]) -> +- case C of +- ?Q -> +- false; +- $\\ -> +- false; +- $\b -> +- false; +- $\f -> +- false; +- $\n -> +- false; +- $\r -> +- false; +- $\t -> +- false; +- C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> +- false; +- C when C < 16#7f -> +- json_string_is_safe(Rest); +- _ -> +- false +- end. +- +-json_bin_is_safe(<<>>) -> +- true; +-json_bin_is_safe(<>) -> +- case C of +- ?Q -> +- false; +- $\\ -> +- false; +- $\b -> +- false; +- $\f -> +- false; +- $\n -> +- false; +- $\r -> +- false; +- $\t -> +- false; +- C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> +- false; +- C when C < 16#7f -> +- json_bin_is_safe(Rest); +- _ -> +- false +- end. +- +-json_encode_string_unicode([], Acc) -> +- lists:reverse([$\" | Acc]); +-json_encode_string_unicode([C | Cs], Acc) -> +- Acc1 = case C of +- ?Q -> +- [?Q, $\\ | Acc]; +- %% Escaping solidus is only useful when trying to protect +- %% against "" injection attacks which are only +- %% possible when JSON is inserted into a HTML document +- %% in-line. mochijson2 does not protect you from this, so +- %% if you do insert directly into HTML then you need to +- %% uncomment the following case or escape the output of encode. +- %% +- %% $/ -> +- %% [$/, $\\ | Acc]; +- %% +- $\\ -> +- [$\\, $\\ | Acc]; +- $\b -> +- [$b, $\\ | Acc]; +- $\f -> +- [$f, $\\ | Acc]; +- $\n -> +- [$n, $\\ | Acc]; +- $\r -> +- [$r, $\\ | Acc]; +- $\t -> +- [$t, $\\ | Acc]; +- C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> +- [unihex(C) | Acc]; +- C when C < 16#7f -> +- [C | Acc]; +- _ -> +- exit({json_encode, {bad_char, C}}) +- end, +- json_encode_string_unicode(Cs, Acc1). +- +-hexdigit(C) when C >= 0, C =< 9 -> +- C + $0; +-hexdigit(C) when C =< 15 -> +- C + $a - 10. +- +-unihex(C) when C < 16#10000 -> +- <> = <>, +- Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], +- [$\\, $u | Digits]; +-unihex(C) when C =< 16#10FFFF -> +- N = C - 16#10000, +- S1 = 16#d800 bor ((N bsr 10) band 16#3ff), +- S2 = 16#dc00 bor (N band 16#3ff), +- [unihex(S1), unihex(S2)]. +- +-json_decode(L, S) when is_list(L) -> +- json_decode(iolist_to_binary(L), S); +-json_decode(B, S) -> +- {Res, S1} = decode1(B, S), +- {eof, _} = tokenize(B, S1#decoder{state=trim}), +- Res. +- +-decode1(B, S=#decoder{state=null}) -> +- case tokenize(B, S#decoder{state=any}) of +- {{const, C}, S1} -> +- {C, S1}; +- {start_array, S1} -> +- decode_array(B, S1); +- {start_object, S1} -> +- decode_object(B, S1) +- end. +- +-make_object(V, #decoder{object_hook=null}) -> +- V; +-make_object(V, #decoder{object_hook=Hook}) -> +- Hook(V). +- +-decode_object(B, S) -> +- decode_object(B, S#decoder{state=key}, []). +- +-decode_object(B, S=#decoder{state=key}, Acc) -> +- case tokenize(B, S) of +- {end_object, S1} -> +- V = make_object({lists:reverse(Acc)}, S1), +- {V, S1#decoder{state=null}}; +- {{const, K}, S1} -> +- {colon, S2} = tokenize(B, S1), +- {V, S3} = decode1(B, S2#decoder{state=null}), +- decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) +- end; +-decode_object(B, S=#decoder{state=comma}, Acc) -> +- case tokenize(B, S) of +- {end_object, S1} -> +- V = make_object({lists:reverse(Acc)}, S1), +- {V, S1#decoder{state=null}}; +- {comma, S1} -> +- decode_object(B, S1#decoder{state=key}, Acc) +- end. +- +-decode_array(B, S) -> +- decode_array(B, S#decoder{state=any}, []). +- +-decode_array(B, S=#decoder{state=any}, Acc) -> +- case tokenize(B, S) of +- {end_array, S1} -> +- {lists:reverse(Acc), S1#decoder{state=null}}; +- {start_array, S1} -> +- {Array, S2} = decode_array(B, S1), +- decode_array(B, S2#decoder{state=comma}, [Array | Acc]); +- {start_object, S1} -> +- {Array, S2} = decode_object(B, S1), +- decode_array(B, S2#decoder{state=comma}, [Array | Acc]); +- {{const, Const}, S1} -> +- decode_array(B, S1#decoder{state=comma}, [Const | Acc]) +- end; +-decode_array(B, S=#decoder{state=comma}, Acc) -> +- case tokenize(B, S) of +- {end_array, S1} -> +- {lists:reverse(Acc), S1#decoder{state=null}}; +- {comma, S1} -> +- decode_array(B, S1#decoder{state=any}, Acc) +- end. +- +-tokenize_string(B, S=#decoder{offset=O}) -> +- case tokenize_string_fast(B, O) of +- {escape, O1} -> +- Length = O1 - O, +- S1 = ?ADV_COL(S, Length), +- <<_:O/binary, Head:Length/binary, _/binary>> = B, +- tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); +- O1 -> +- Length = O1 - O, +- <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, +- {{const, String}, ?ADV_COL(S, Length + 1)} +- end. +- +-tokenize_string_fast(B, O) -> +- case B of +- <<_:O/binary, ?Q, _/binary>> -> +- O; +- <<_:O/binary, $\\, _/binary>> -> +- {escape, O}; +- <<_:O/binary, C1, _/binary>> when C1 < 128 -> +- tokenize_string_fast(B, 1 + O); +- <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, +- C2 >= 128, C2 =< 191 -> +- tokenize_string_fast(B, 2 + O); +- <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, +- C2 >= 128, C2 =< 191, +- C3 >= 128, C3 =< 191 -> +- tokenize_string_fast(B, 3 + O); +- <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, +- C2 >= 128, C2 =< 191, +- C3 >= 128, C3 =< 191, +- C4 >= 128, C4 =< 191 -> +- tokenize_string_fast(B, 4 + O); +- _ -> +- throw(invalid_utf8) +- end. +- +-tokenize_string(B, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, ?Q, _/binary>> -> +- {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; +- <<_:O/binary, "\\\"", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); +- <<_:O/binary, "\\\\", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); +- <<_:O/binary, "\\/", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); +- <<_:O/binary, "\\b", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); +- <<_:O/binary, "\\f", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); +- <<_:O/binary, "\\n", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); +- <<_:O/binary, "\\r", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); +- <<_:O/binary, "\\t", _/binary>> -> +- tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); +- <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> +- C = erlang:list_to_integer([C3, C2, C1, C0], 16), +- if C > 16#D7FF, C < 16#DC00 -> +- %% coalesce UTF-16 surrogate pair +- <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, +- D = erlang:list_to_integer([D3,D2,D1,D0], 16), +- [CodePoint] = xmerl_ucs:from_utf16be(<>), +- Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), +- tokenize_string(B, ?ADV_COL(S, 12), Acc1); +- true -> +- Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), +- tokenize_string(B, ?ADV_COL(S, 6), Acc1) +- end; +- <<_:O/binary, C, _/binary>> -> +- tokenize_string(B, ?INC_CHAR(S, C), [C | Acc]) +- end. +- +-tokenize_number(B, S) -> +- case tokenize_number(B, sign, S, []) of +- {{int, Int}, S1} -> +- {{const, list_to_integer(Int)}, S1}; +- {{float, Float}, S1} -> +- {{const, list_to_float(Float)}, S1} +- end. +- +-tokenize_number(B, sign, S=#decoder{offset=O}, []) -> +- case B of +- <<_:O/binary, $-, _/binary>> -> +- tokenize_number(B, int, ?INC_COL(S), [$-]); +- _ -> +- tokenize_number(B, int, S, []) +- end; +-tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, $0, _/binary>> -> +- tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); +- <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> +- tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) +- end; +-tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); +- _ -> +- tokenize_number(B, frac, S, Acc) +- end; +-tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> +- tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); +- <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> +- tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); +- _ -> +- {{int, lists:reverse(Acc)}, S} +- end; +-tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); +- <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> +- tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); +- _ -> +- {{float, lists:reverse(Acc)}, S} +- end; +-tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> +- tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); +- _ -> +- tokenize_number(B, eint, S, Acc) +- end; +-tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) +- end; +-tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> +- tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); +- _ -> +- {{float, lists:reverse(Acc)}, S} +- end. +- +-tokenize(B, S=#decoder{offset=O}) -> +- case B of +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> +- tokenize(B, ?INC_CHAR(S, C)); +- <<_:O/binary, "{", _/binary>> -> +- {start_object, ?INC_COL(S)}; +- <<_:O/binary, "}", _/binary>> -> +- {end_object, ?INC_COL(S)}; +- <<_:O/binary, "[", _/binary>> -> +- {start_array, ?INC_COL(S)}; +- <<_:O/binary, "]", _/binary>> -> +- {end_array, ?INC_COL(S)}; +- <<_:O/binary, ",", _/binary>> -> +- {comma, ?INC_COL(S)}; +- <<_:O/binary, ":", _/binary>> -> +- {colon, ?INC_COL(S)}; +- <<_:O/binary, "null", _/binary>> -> +- {{const, null}, ?ADV_COL(S, 4)}; +- <<_:O/binary, "true", _/binary>> -> +- {{const, true}, ?ADV_COL(S, 4)}; +- <<_:O/binary, "false", _/binary>> -> +- {{const, false}, ?ADV_COL(S, 5)}; +- <<_:O/binary, "\"", _/binary>> -> +- tokenize_string(B, ?INC_COL(S)); +- <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) +- orelse C =:= $- -> +- tokenize_number(B, S); +- <<_:O/binary>> -> +- trim = S#decoder.state, +- {eof, S} +- end. +- +-%% testing constructs borrowed from the Yaws JSON implementation. +- +-%% Create an object from a list of Key/Value pairs. +- +-obj_new() -> +- {[]}. +- +-is_obj({Props}) -> +- F = fun ({K, _}) when is_binary(K) -> +- true; +- (_) -> +- false +- end, +- lists:all(F, Props). +- +-obj_from_list(Props) -> +- Obj = {Props}, +- case is_obj(Obj) of +- true -> Obj; +- false -> exit({json_bad_object, Obj}) +- end. +- +-%% Test for equivalence of Erlang terms. +-%% Due to arbitrary order of construction, equivalent objects might +-%% compare unequal as erlang terms, so we need to carefully recurse +-%% through aggregates (tuples and objects). +- +-equiv({Props1}, {Props2}) -> +- equiv_object(Props1, Props2); +-equiv(L1, L2) when is_list(L1), is_list(L2) -> +- equiv_list(L1, L2); +-equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +-equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; +-equiv(true, true) -> true; +-equiv(false, false) -> true; +-equiv(null, null) -> true. +- +-%% Object representation and traversal order is unknown. +-%% Use the sledgehammer and sort property lists. +- +-equiv_object(Props1, Props2) -> +- L1 = lists:keysort(1, Props1), +- L2 = lists:keysort(1, Props2), +- Pairs = lists:zip(L1, L2), +- true = lists:all(fun({{K1, V1}, {K2, V2}}) -> +- equiv(K1, K2) and equiv(V1, V2) +- end, Pairs). +- +-%% Recursively compare tuple elements for equivalence. +- +-equiv_list([], []) -> +- true; +-equiv_list([V1 | L1], [V2 | L2]) -> +- case equiv(V1, V2) of +- true -> +- equiv_list(L1, L2); +- false -> +- false +- end. +- +-test_all() -> +- [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), +- <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]), +- test_one(e2j_test_vec(utf8), 1). +- +-test_one([], _N) -> +- %% io:format("~p tests passed~n", [N-1]), +- ok; +-test_one([{E, J} | Rest], N) -> +- %% io:format("[~p] ~p ~p~n", [N, E, J]), +- true = equiv(E, decode(J)), +- true = equiv(E, decode(encode(E))), +- test_one(Rest, 1+N). +- +-e2j_test_vec(utf8) -> +- [ +- {1, "1"}, +- {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes +- {-1, "-1"}, +- {-3.1416, "-3.14160"}, +- {12.0e10, "1.20000e+11"}, +- {1.234E+10, "1.23400e+10"}, +- {-1.234E-10, "-1.23400e-10"}, +- {10.0, "1.0e+01"}, +- {123.456, "1.23456E+2"}, +- {10.0, "1e1"}, +- {<<"foo">>, "\"foo\""}, +- {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, +- {<<"">>, "\"\""}, +- {<<"\n\n\n">>, "\"\\n\\n\\n\""}, +- {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, +- {obj_new(), "{}"}, +- {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, +- {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), +- "{\"foo\":\"bar\",\"baz\":123}"}, +- {[], "[]"}, +- {[[]], "[[]]"}, +- {[1, <<"foo">>], "[1,\"foo\"]"}, +- +- %% json array in a json object +- {obj_from_list([{<<"foo">>, [123]}]), +- "{\"foo\":[123]}"}, +- +- %% json object in a json object +- {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), +- "{\"foo\":{\"bar\":true}}"}, +- +- %% fold evaluation order +- {obj_from_list([{<<"foo">>, []}, +- {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, +- {<<"alice">>, <<"bob">>}]), +- "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, +- +- %% json object in a json array +- {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], +- "[-123,\"foo\",{\"bar\":[]},null]"} +- ]. +diff --git a/src/mochiweb/mochinum.erl b/src/mochiweb/mochinum.erl +deleted file mode 100644 +index 6a86604..0000000 +--- a/src/mochiweb/mochinum.erl ++++ /dev/null +@@ -1,289 +0,0 @@ +-%% @copyright 2007 Mochi Media, Inc. +-%% @author Bob Ippolito +- +-%% @doc Useful numeric algorithms for floats that cover some deficiencies +-%% in the math module. More interesting is digits/1, which implements +-%% the algorithm from: +-%% http://www.cs.indiana.edu/~burger/fp/index.html +-%% See also "Printing Floating-Point Numbers Quickly and Accurately" +-%% in Proceedings of the SIGPLAN '96 Conference on Programming Language +-%% Design and Implementation. +- +--module(mochinum). +--author("Bob Ippolito "). +--export([digits/1, frexp/1, int_pow/2, int_ceil/1, test/0]). +- +-%% IEEE 754 Float exponent bias +--define(FLOAT_BIAS, 1022). +--define(MIN_EXP, -1074). +--define(BIG_POW, 4503599627370496). +- +-%% External API +- +-%% @spec digits(number()) -> string() +-%% @doc Returns a string that accurately represents the given integer or float +-%% using a conservative amount of digits. Great for generating +-%% human-readable output, or compact ASCII serializations for floats. +-digits(N) when is_integer(N) -> +- integer_to_list(N); +-digits(0.0) -> +- "0.0"; +-digits(Float) -> +- {Frac, Exp} = frexp(Float), +- Exp1 = Exp - 53, +- Frac1 = trunc(abs(Frac) * (1 bsl 53)), +- [Place | Digits] = digits1(Float, Exp1, Frac1), +- R = insert_decimal(Place, [$0 + D || D <- Digits]), +- case Float < 0 of +- true -> +- [$- | R]; +- _ -> +- R +- end. +- +-%% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} +-%% @doc Return the fractional and exponent part of an IEEE 754 double, +-%% equivalent to the libc function of the same name. +-%% F = Frac * pow(2, Exp). +-frexp(F) -> +- frexp1(unpack(F)). +- +-%% @spec int_pow(X::integer(), N::integer()) -> Y::integer() +-%% @doc Moderately efficient way to exponentiate integers. +-%% int_pow(10, 2) = 100. +-int_pow(_X, 0) -> +- 1; +-int_pow(X, N) when N > 0 -> +- int_pow(X, N, 1). +- +-%% @spec int_ceil(F::float()) -> integer() +-%% @doc Return the ceiling of F as an integer. The ceiling is defined as +-%% F when F == trunc(F); +-%% trunc(F) when F < 0; +-%% trunc(F) + 1 when F > 0. +-int_ceil(X) -> +- T = trunc(X), +- case (X - T) of +- Neg when Neg < 0 -> T; +- Pos when Pos > 0 -> T + 1; +- _ -> T +- end. +- +- +-%% Internal API +- +-int_pow(X, N, R) when N < 2 -> +- R * X; +-int_pow(X, N, R) -> +- int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). +- +-insert_decimal(0, S) -> +- "0." ++ S; +-insert_decimal(Place, S) when Place > 0 -> +- L = length(S), +- case Place - L of +- 0 -> +- S ++ ".0"; +- N when N < 0 -> +- {S0, S1} = lists:split(L + N, S), +- S0 ++ "." ++ S1; +- N when N < 6 -> +- %% More places than digits +- S ++ lists:duplicate(N, $0) ++ ".0"; +- _ -> +- insert_decimal_exp(Place, S) +- end; +-insert_decimal(Place, S) when Place > -6 -> +- "0." ++ lists:duplicate(abs(Place), $0) ++ S; +-insert_decimal(Place, S) -> +- insert_decimal_exp(Place, S). +- +-insert_decimal_exp(Place, S) -> +- [C | S0] = S, +- S1 = case S0 of +- [] -> +- "0"; +- _ -> +- S0 +- end, +- Exp = case Place < 0 of +- true -> +- "e-"; +- false -> +- "e+" +- end, +- [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). +- +- +-digits1(Float, Exp, Frac) -> +- Round = ((Frac band 1) =:= 0), +- case Exp >= 0 of +- true -> +- BExp = 1 bsl Exp, +- case (Frac /= ?BIG_POW) of +- true -> +- scale((Frac * BExp * 2), 2, BExp, BExp, +- Round, Round, Float); +- false -> +- scale((Frac * BExp * 4), 4, (BExp * 2), BExp, +- Round, Round, Float) +- end; +- false -> +- case (Exp == ?MIN_EXP) orelse (Frac /= ?BIG_POW) of +- true -> +- scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, +- Round, Round, Float); +- false -> +- scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, +- Round, Round, Float) +- end +- end. +- +-scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> +- Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), +- %% Note that the scheme implementation uses a 326 element look-up table +- %% for int_pow(10, N) where we do not. +- case Est >= 0 of +- true -> +- fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, +- LowOk, HighOk); +- false -> +- Scale = int_pow(10, -Est), +- fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, +- LowOk, HighOk) +- end. +- +-fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> +- TooLow = case HighOk of +- true -> +- (R + MPlus) >= S; +- false -> +- (R + MPlus) > S +- end, +- case TooLow of +- true -> +- [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; +- false -> +- [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] +- end. +- +-generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> +- D = R0 div S, +- R = R0 rem S, +- TC1 = case LowOk of +- true -> +- R =< MMinus; +- false -> +- R < MMinus +- end, +- TC2 = case HighOk of +- true -> +- (R + MPlus) >= S; +- false -> +- (R + MPlus) > S +- end, +- case TC1 of +- false -> +- case TC2 of +- false -> +- [D | generate(R * 10, S, MPlus * 10, MMinus * 10, +- LowOk, HighOk)]; +- true -> +- [D + 1] +- end; +- true -> +- case TC2 of +- false -> +- [D]; +- true -> +- case R * 2 < S of +- true -> +- [D]; +- false -> +- [D + 1] +- end +- end +- end. +- +-unpack(Float) -> +- <> = <>, +- {Sign, Exp, Frac}. +- +-frexp1({_Sign, 0, 0}) -> +- {0.0, 0}; +-frexp1({Sign, 0, Frac}) -> +- Exp = log2floor(Frac), +- <> = <>, +- {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; +-frexp1({Sign, Exp, Frac}) -> +- <> = <>, +- {Frac1, Exp - ?FLOAT_BIAS}. +- +-log2floor(Int) -> +- log2floor(Int, 0). +- +-log2floor(0, N) -> +- N; +-log2floor(Int, N) -> +- log2floor(Int bsr 1, 1 + N). +- +- +-test() -> +- ok = test_frexp(), +- ok = test_int_ceil(), +- ok = test_int_pow(), +- ok = test_digits(), +- ok. +- +-test_int_ceil() -> +- 1 = int_ceil(0.0001), +- 0 = int_ceil(0.0), +- 1 = int_ceil(0.99), +- 1 = int_ceil(1.0), +- -1 = int_ceil(-1.5), +- -2 = int_ceil(-2.0), +- ok. +- +-test_int_pow() -> +- 1 = int_pow(1, 1), +- 1 = int_pow(1, 0), +- 1 = int_pow(10, 0), +- 10 = int_pow(10, 1), +- 100 = int_pow(10, 2), +- 1000 = int_pow(10, 3), +- ok. +- +-test_digits() -> +- "0" = digits(0), +- "0.0" = digits(0.0), +- "1.0" = digits(1.0), +- "-1.0" = digits(-1.0), +- "0.1" = digits(0.1), +- "0.01" = digits(0.01), +- "0.001" = digits(0.001), +- ok. +- +-test_frexp() -> +- %% zero +- {0.0, 0} = frexp(0.0), +- %% one +- {0.5, 1} = frexp(1.0), +- %% negative one +- {-0.5, 1} = frexp(-1.0), +- %% small denormalized number +- %% 4.94065645841246544177e-324 +- <> = <<0,0,0,0,0,0,0,1>>, +- {0.5, -1073} = frexp(SmallDenorm), +- %% large denormalized number +- %% 2.22507385850720088902e-308 +- <> = <<0,15,255,255,255,255,255,255>>, +- {0.99999999999999978, -1022} = frexp(BigDenorm), +- %% small normalized number +- %% 2.22507385850720138309e-308 +- <> = <<0,16,0,0,0,0,0,0>>, +- {0.5, -1021} = frexp(SmallNorm), +- %% large normalized number +- %% 1.79769313486231570815e+308 +- <> = <<127,239,255,255,255,255,255,255>>, +- {0.99999999999999989, 1024} = frexp(LargeNorm), +- ok. +diff --git a/src/mochiweb/mochiweb.app.in b/src/mochiweb/mochiweb.app.in +deleted file mode 100644 +index cd8dbb2..0000000 +--- a/src/mochiweb/mochiweb.app.in ++++ /dev/null +@@ -1,32 +0,0 @@ +-{application, mochiweb, +- [{description, "MochiMedia Web Server"}, +- {vsn, "0.01"}, +- {modules, [ +- mochihex, +- mochijson, +- mochijson2, +- mochinum, +- mochiweb, +- mochiweb_app, +- mochiweb_charref, +- mochiweb_cookies, +- mochiweb_echo, +- mochiweb_headers, +- mochiweb_html, +- mochiweb_http, +- mochiweb_multipart, +- mochiweb_request, +- mochiweb_response, +- mochiweb_skel, +- mochiweb_socket_server, +- mochiweb_sup, +- mochiweb_util, +- reloader, +- mochifmt, +- mochifmt_std, +- mochifmt_records +- ]}, +- {registered, []}, +- {mod, {mochiweb_app, []}}, +- {env, []}, +- {applications, [kernel, stdlib]}]}. +diff --git a/src/mochiweb/mochiweb.erl b/src/mochiweb/mochiweb.erl +deleted file mode 100644 +index 0f4d52a..0000000 +--- a/src/mochiweb/mochiweb.erl ++++ /dev/null +@@ -1,110 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Start and stop the MochiWeb server. +- +--module(mochiweb). +--author('bob@mochimedia.com'). +- +--export([start/0, stop/0]). +--export([new_request/1, new_response/1]). +--export([all_loaded/0, all_loaded/1, reload/0]). +--export([test/0]). +- +-%% @spec start() -> ok +-%% @doc Start the MochiWeb server. +-start() -> +- ensure_started(crypto), +- application:start(mochiweb). +- +-%% @spec stop() -> ok +-%% @doc Stop the MochiWeb server. +-stop() -> +- Res = application:stop(mochiweb), +- application:stop(crypto), +- Res. +- +-%% @spec test() -> ok +-%% @doc Run all of the tests for MochiWeb. +-test() -> +- mochiweb_util:test(), +- mochiweb_headers:test(), +- mochiweb_cookies:test(), +- mochihex:test(), +- mochinum:test(), +- mochijson:test(), +- mochiweb_charref:test(), +- mochiweb_html:test(), +- mochifmt:test(), +- test_request(), +- ok. +- +-reload() -> +- [c:l(Module) || Module <- all_loaded()]. +- +-all_loaded() -> +- all_loaded(filename:dirname(code:which(?MODULE))). +- +-all_loaded(Base) when is_atom(Base) -> +- []; +-all_loaded(Base) -> +- FullBase = Base ++ "/", +- F = fun ({_Module, Loaded}, Acc) when is_atom(Loaded) -> +- Acc; +- ({Module, Loaded}, Acc) -> +- case lists:prefix(FullBase, Loaded) of +- true -> +- [Module | Acc]; +- false -> +- Acc +- end +- end, +- lists:foldl(F, [], code:all_loaded()). +- +- +-%% @spec new_request({Socket, Request, Headers}) -> MochiWebRequest +-%% @doc Return a mochiweb_request data structure. +-new_request({Socket, {Method, {abs_path, Uri}, Version}, Headers}) -> +- mochiweb_request:new(Socket, +- Method, +- Uri, +- Version, +- mochiweb_headers:make(Headers)); +-% this case probably doesn't "exist". +-new_request({Socket, {Method, {absoluteURI, _Protocol, _Host, _Port, Uri}, +- Version}, Headers}) -> +- mochiweb_request:new(Socket, +- Method, +- Uri, +- Version, +- mochiweb_headers:make(Headers)); +-%% Request-URI is "*" +-%% From http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 +-new_request({Socket, {Method, '*'=Uri, Version}, Headers}) -> +- mochiweb_request:new(Socket, +- Method, +- Uri, +- Version, +- mochiweb_headers:make(Headers)). +- +-%% @spec new_response({Request, integer(), Headers}) -> MochiWebResponse +-%% @doc Return a mochiweb_response data structure. +-new_response({Request, Code, Headers}) -> +- mochiweb_response:new(Request, +- Code, +- mochiweb_headers:make(Headers)). +- +-%% Internal API +- +-test_request() -> +- R = mochiweb_request:new(z, z, "/foo/bar/baz%20wibble+quux?qs=2", z, []), +- "/foo/bar/baz wibble quux" = R:get(path), +- ok. +- +-ensure_started(App) -> +- case application:start(App) of +- ok -> +- ok; +- {error, {already_started, App}} -> +- ok +- end. +diff --git a/src/mochiweb/mochiweb_app.erl b/src/mochiweb/mochiweb_app.erl +deleted file mode 100644 +index 2b437f6..0000000 +--- a/src/mochiweb/mochiweb_app.erl ++++ /dev/null +@@ -1,20 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Callbacks for the mochiweb application. +- +--module(mochiweb_app). +--author('bob@mochimedia.com'). +- +--behaviour(application). +--export([start/2,stop/1]). +- +-%% @spec start(_Type, _StartArgs) -> ServerRet +-%% @doc application start callback for mochiweb. +-start(_Type, _StartArgs) -> +- mochiweb_sup:start_link(). +- +-%% @spec stop(_State) -> ServerRet +-%% @doc application stop callback for mochiweb. +-stop(_State) -> +- ok. +diff --git a/src/mochiweb/mochiweb_charref.erl b/src/mochiweb/mochiweb_charref.erl +deleted file mode 100644 +index d037d2f..0000000 +--- a/src/mochiweb/mochiweb_charref.erl ++++ /dev/null +@@ -1,295 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Converts HTML 4 charrefs and entities to codepoints. +--module(mochiweb_charref). +--export([charref/1, test/0]). +- +-%% External API. +- +-%% @spec charref(S) -> integer() | undefined +-%% @doc Convert a decimal charref, hex charref, or html entity to a unicode +-%% codepoint, or return undefined on failure. +-%% The input should not include an ampersand or semicolon. +-%% charref("#38") = 38, charref("#x26") = 38, charref("amp") = 38. +-charref(B) when is_binary(B) -> +- charref(binary_to_list(B)); +-charref([$#, C | L]) when C =:= $x orelse C =:= $X -> +- try erlang:list_to_integer(L, 16) +- catch +- error:badarg -> undefined +- end; +-charref([$# | L]) -> +- try list_to_integer(L) +- catch +- error:badarg -> undefined +- end; +-charref(L) -> +- entity(L). +- +-%% @spec test() -> ok +-%% @doc Run tests for mochiweb_charref. +-test() -> +- 1234 = charref("#1234"), +- 255 = charref("#xfF"), +- 255 = charref("#XFf"), +- 38 = charref("amp"), +- undefined = charref("not_an_entity"), +- ok. +- +-%% Internal API. +- +-entity("nbsp") -> 160; +-entity("iexcl") -> 161; +-entity("cent") -> 162; +-entity("pound") -> 163; +-entity("curren") -> 164; +-entity("yen") -> 165; +-entity("brvbar") -> 166; +-entity("sect") -> 167; +-entity("uml") -> 168; +-entity("copy") -> 169; +-entity("ordf") -> 170; +-entity("laquo") -> 171; +-entity("not") -> 172; +-entity("shy") -> 173; +-entity("reg") -> 174; +-entity("macr") -> 175; +-entity("deg") -> 176; +-entity("plusmn") -> 177; +-entity("sup2") -> 178; +-entity("sup3") -> 179; +-entity("acute") -> 180; +-entity("micro") -> 181; +-entity("para") -> 182; +-entity("middot") -> 183; +-entity("cedil") -> 184; +-entity("sup1") -> 185; +-entity("ordm") -> 186; +-entity("raquo") -> 187; +-entity("frac14") -> 188; +-entity("frac12") -> 189; +-entity("frac34") -> 190; +-entity("iquest") -> 191; +-entity("Agrave") -> 192; +-entity("Aacute") -> 193; +-entity("Acirc") -> 194; +-entity("Atilde") -> 195; +-entity("Auml") -> 196; +-entity("Aring") -> 197; +-entity("AElig") -> 198; +-entity("Ccedil") -> 199; +-entity("Egrave") -> 200; +-entity("Eacute") -> 201; +-entity("Ecirc") -> 202; +-entity("Euml") -> 203; +-entity("Igrave") -> 204; +-entity("Iacute") -> 205; +-entity("Icirc") -> 206; +-entity("Iuml") -> 207; +-entity("ETH") -> 208; +-entity("Ntilde") -> 209; +-entity("Ograve") -> 210; +-entity("Oacute") -> 211; +-entity("Ocirc") -> 212; +-entity("Otilde") -> 213; +-entity("Ouml") -> 214; +-entity("times") -> 215; +-entity("Oslash") -> 216; +-entity("Ugrave") -> 217; +-entity("Uacute") -> 218; +-entity("Ucirc") -> 219; +-entity("Uuml") -> 220; +-entity("Yacute") -> 221; +-entity("THORN") -> 222; +-entity("szlig") -> 223; +-entity("agrave") -> 224; +-entity("aacute") -> 225; +-entity("acirc") -> 226; +-entity("atilde") -> 227; +-entity("auml") -> 228; +-entity("aring") -> 229; +-entity("aelig") -> 230; +-entity("ccedil") -> 231; +-entity("egrave") -> 232; +-entity("eacute") -> 233; +-entity("ecirc") -> 234; +-entity("euml") -> 235; +-entity("igrave") -> 236; +-entity("iacute") -> 237; +-entity("icirc") -> 238; +-entity("iuml") -> 239; +-entity("eth") -> 240; +-entity("ntilde") -> 241; +-entity("ograve") -> 242; +-entity("oacute") -> 243; +-entity("ocirc") -> 244; +-entity("otilde") -> 245; +-entity("ouml") -> 246; +-entity("divide") -> 247; +-entity("oslash") -> 248; +-entity("ugrave") -> 249; +-entity("uacute") -> 250; +-entity("ucirc") -> 251; +-entity("uuml") -> 252; +-entity("yacute") -> 253; +-entity("thorn") -> 254; +-entity("yuml") -> 255; +-entity("fnof") -> 402; +-entity("Alpha") -> 913; +-entity("Beta") -> 914; +-entity("Gamma") -> 915; +-entity("Delta") -> 916; +-entity("Epsilon") -> 917; +-entity("Zeta") -> 918; +-entity("Eta") -> 919; +-entity("Theta") -> 920; +-entity("Iota") -> 921; +-entity("Kappa") -> 922; +-entity("Lambda") -> 923; +-entity("Mu") -> 924; +-entity("Nu") -> 925; +-entity("Xi") -> 926; +-entity("Omicron") -> 927; +-entity("Pi") -> 928; +-entity("Rho") -> 929; +-entity("Sigma") -> 931; +-entity("Tau") -> 932; +-entity("Upsilon") -> 933; +-entity("Phi") -> 934; +-entity("Chi") -> 935; +-entity("Psi") -> 936; +-entity("Omega") -> 937; +-entity("alpha") -> 945; +-entity("beta") -> 946; +-entity("gamma") -> 947; +-entity("delta") -> 948; +-entity("epsilon") -> 949; +-entity("zeta") -> 950; +-entity("eta") -> 951; +-entity("theta") -> 952; +-entity("iota") -> 953; +-entity("kappa") -> 954; +-entity("lambda") -> 955; +-entity("mu") -> 956; +-entity("nu") -> 957; +-entity("xi") -> 958; +-entity("omicron") -> 959; +-entity("pi") -> 960; +-entity("rho") -> 961; +-entity("sigmaf") -> 962; +-entity("sigma") -> 963; +-entity("tau") -> 964; +-entity("upsilon") -> 965; +-entity("phi") -> 966; +-entity("chi") -> 967; +-entity("psi") -> 968; +-entity("omega") -> 969; +-entity("thetasym") -> 977; +-entity("upsih") -> 978; +-entity("piv") -> 982; +-entity("bull") -> 8226; +-entity("hellip") -> 8230; +-entity("prime") -> 8242; +-entity("Prime") -> 8243; +-entity("oline") -> 8254; +-entity("frasl") -> 8260; +-entity("weierp") -> 8472; +-entity("image") -> 8465; +-entity("real") -> 8476; +-entity("trade") -> 8482; +-entity("alefsym") -> 8501; +-entity("larr") -> 8592; +-entity("uarr") -> 8593; +-entity("rarr") -> 8594; +-entity("darr") -> 8595; +-entity("harr") -> 8596; +-entity("crarr") -> 8629; +-entity("lArr") -> 8656; +-entity("uArr") -> 8657; +-entity("rArr") -> 8658; +-entity("dArr") -> 8659; +-entity("hArr") -> 8660; +-entity("forall") -> 8704; +-entity("part") -> 8706; +-entity("exist") -> 8707; +-entity("empty") -> 8709; +-entity("nabla") -> 8711; +-entity("isin") -> 8712; +-entity("notin") -> 8713; +-entity("ni") -> 8715; +-entity("prod") -> 8719; +-entity("sum") -> 8721; +-entity("minus") -> 8722; +-entity("lowast") -> 8727; +-entity("radic") -> 8730; +-entity("prop") -> 8733; +-entity("infin") -> 8734; +-entity("ang") -> 8736; +-entity("and") -> 8743; +-entity("or") -> 8744; +-entity("cap") -> 8745; +-entity("cup") -> 8746; +-entity("int") -> 8747; +-entity("there4") -> 8756; +-entity("sim") -> 8764; +-entity("cong") -> 8773; +-entity("asymp") -> 8776; +-entity("ne") -> 8800; +-entity("equiv") -> 8801; +-entity("le") -> 8804; +-entity("ge") -> 8805; +-entity("sub") -> 8834; +-entity("sup") -> 8835; +-entity("nsub") -> 8836; +-entity("sube") -> 8838; +-entity("supe") -> 8839; +-entity("oplus") -> 8853; +-entity("otimes") -> 8855; +-entity("perp") -> 8869; +-entity("sdot") -> 8901; +-entity("lceil") -> 8968; +-entity("rceil") -> 8969; +-entity("lfloor") -> 8970; +-entity("rfloor") -> 8971; +-entity("lang") -> 9001; +-entity("rang") -> 9002; +-entity("loz") -> 9674; +-entity("spades") -> 9824; +-entity("clubs") -> 9827; +-entity("hearts") -> 9829; +-entity("diams") -> 9830; +-entity("quot") -> 34; +-entity("amp") -> 38; +-entity("lt") -> 60; +-entity("gt") -> 62; +-entity("OElig") -> 338; +-entity("oelig") -> 339; +-entity("Scaron") -> 352; +-entity("scaron") -> 353; +-entity("Yuml") -> 376; +-entity("circ") -> 710; +-entity("tilde") -> 732; +-entity("ensp") -> 8194; +-entity("emsp") -> 8195; +-entity("thinsp") -> 8201; +-entity("zwnj") -> 8204; +-entity("zwj") -> 8205; +-entity("lrm") -> 8206; +-entity("rlm") -> 8207; +-entity("ndash") -> 8211; +-entity("mdash") -> 8212; +-entity("lsquo") -> 8216; +-entity("rsquo") -> 8217; +-entity("sbquo") -> 8218; +-entity("ldquo") -> 8220; +-entity("rdquo") -> 8221; +-entity("bdquo") -> 8222; +-entity("dagger") -> 8224; +-entity("Dagger") -> 8225; +-entity("permil") -> 8240; +-entity("lsaquo") -> 8249; +-entity("rsaquo") -> 8250; +-entity("euro") -> 8364; +-entity(_) -> undefined. +- +diff --git a/src/mochiweb/mochiweb_cookies.erl b/src/mochiweb/mochiweb_cookies.erl +deleted file mode 100644 +index 61711ff..0000000 +--- a/src/mochiweb/mochiweb_cookies.erl ++++ /dev/null +@@ -1,257 +0,0 @@ +-%% @author Emad El-Haraty +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc HTTP Cookie parsing and generating (RFC 2109, RFC 2965). +- +--module(mochiweb_cookies). +--export([parse_cookie/1, cookie/3, cookie/2, test/0]). +- +--define(QUOTE, $\"). +- +--define(IS_WHITESPACE(C), +- (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). +- +-%% RFC 2616 separators (called tspecials in RFC 2068) +--define(IS_SEPARATOR(C), +- (C < 32 orelse +- C =:= $\s orelse C =:= $\t orelse +- C =:= $( orelse C =:= $) orelse C =:= $< orelse C =:= $> orelse +- C =:= $@ orelse C =:= $, orelse C =:= $; orelse C =:= $: orelse +- C =:= $\\ orelse C =:= $\" orelse C =:= $/ orelse +- C =:= $[ orelse C =:= $] orelse C =:= $? orelse C =:= $= orelse +- C =:= ${ orelse C =:= $})). +- +-%% @type proplist() = [{Key::string(), Value::string()}]. +-%% @type header() = {Name::string(), Value::string()}. +- +-%% @spec cookie(Key::string(), Value::string()) -> header() +-%% @doc Short-hand for cookie(Key, Value, []). +-cookie(Key, Value) -> +- cookie(Key, Value, []). +- +-%% @spec cookie(Key::string(), Value::string(), Options::[Option]) -> header() +-%% where Option = {max_age, integer()} | {local_time, {date(), time()}} +-%% | {domain, string()} | {path, string()} +-%% | {secure, true | false} | {http_only, true | false} +-%% +-%% @doc Generate a Set-Cookie header field tuple. +-cookie(Key, Value, Options) -> +- Cookie = [any_to_list(Key), "=", quote(Value), "; Version=1"], +- %% Set-Cookie: +- %% Comment, Domain, Max-Age, Path, Secure, Version +- %% Set-Cookie2: +- %% Comment, CommentURL, Discard, Domain, Max-Age, Path, Port, Secure, +- %% Version +- ExpiresPart = +- case proplists:get_value(max_age, Options) of +- undefined -> +- ""; +- RawAge -> +- When = case proplists:get_value(local_time, Options) of +- undefined -> +- calendar:local_time(); +- LocalTime -> +- LocalTime +- end, +- Age = case RawAge < 0 of +- true -> +- 0; +- false -> +- RawAge +- end, +- ["; Expires=", age_to_cookie_date(Age, When), +- "; Max-Age=", quote(Age)] +- end, +- SecurePart = +- case proplists:get_value(secure, Options) of +- true -> +- "; Secure"; +- _ -> +- "" +- end, +- DomainPart = +- case proplists:get_value(domain, Options) of +- undefined -> +- ""; +- Domain -> +- ["; Domain=", quote(Domain)] +- end, +- PathPart = +- case proplists:get_value(path, Options) of +- undefined -> +- ""; +- Path -> +- ["; Path=", quote(Path)] +- end, +- HttpOnlyPart = +- case proplists:get_value(http_only, Options) of +- true -> +- "; HttpOnly"; +- _ -> +- "" +- end, +- CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart, HttpOnlyPart], +- {"Set-Cookie", lists:flatten(CookieParts)}. +- +- +-%% Every major browser incorrectly handles quoted strings in a +-%% different and (worse) incompatible manner. Instead of wasting time +-%% writing redundant code for each browser, we restrict cookies to +-%% only contain characters that browsers handle compatibly. +-%% +-%% By replacing the definition of quote with this, we generate +-%% RFC-compliant cookies: +-%% +-%% quote(V) -> +-%% Fun = fun(?QUOTE, Acc) -> [$\\, ?QUOTE | Acc]; +-%% (Ch, Acc) -> [Ch | Acc] +-%% end, +-%% [?QUOTE | lists:foldr(Fun, [?QUOTE], V)]. +- +-%% Convert to a string and raise an error if quoting is required. +-quote(V0) -> +- V = any_to_list(V0), +- lists:all(fun(Ch) -> Ch =:= $/ orelse not ?IS_SEPARATOR(Ch) end, V) +- orelse erlang:error({cookie_quoting_required, V}), +- V. +- +-add_seconds(Secs, LocalTime) -> +- Greg = calendar:datetime_to_gregorian_seconds(LocalTime), +- calendar:gregorian_seconds_to_datetime(Greg + Secs). +- +-age_to_cookie_date(Age, LocalTime) -> +- httpd_util:rfc1123_date(add_seconds(Age, LocalTime)). +- +-%% @spec parse_cookie(string()) -> [{K::string(), V::string()}] +-%% @doc Parse the contents of a Cookie header field, ignoring cookie +-%% attributes, and return a simple property list. +-parse_cookie("") -> +- []; +-parse_cookie(Cookie) -> +- parse_cookie(Cookie, []). +- +-%% @spec test() -> ok +-%% @doc Run tests for mochiweb_cookies. +-test() -> +- parse_cookie_test(), +- cookie_test(), +- ok. +- +-%% Internal API +- +-parse_cookie([], Acc) -> +- lists:reverse(Acc); +-parse_cookie(String, Acc) -> +- {{Token, Value}, Rest} = read_pair(String), +- Acc1 = case Token of +- "" -> +- Acc; +- "$" ++ _ -> +- Acc; +- _ -> +- [{Token, Value} | Acc] +- end, +- parse_cookie(Rest, Acc1). +- +-read_pair(String) -> +- {Token, Rest} = read_token(skip_whitespace(String)), +- {Value, Rest1} = read_value(skip_whitespace(Rest)), +- {{Token, Value}, skip_past_separator(Rest1)}. +- +-read_value([$= | Value]) -> +- Value1 = skip_whitespace(Value), +- case Value1 of +- [?QUOTE | _] -> +- read_quoted(Value1); +- _ -> +- read_token(Value1) +- end; +-read_value(String) -> +- {"", String}. +- +-read_quoted([?QUOTE | String]) -> +- read_quoted(String, []). +- +-read_quoted([], Acc) -> +- {lists:reverse(Acc), []}; +-read_quoted([?QUOTE | Rest], Acc) -> +- {lists:reverse(Acc), Rest}; +-read_quoted([$\\, Any | Rest], Acc) -> +- read_quoted(Rest, [Any | Acc]); +-read_quoted([C | Rest], Acc) -> +- read_quoted(Rest, [C | Acc]). +- +-skip_whitespace(String) -> +- F = fun (C) -> ?IS_WHITESPACE(C) end, +- lists:dropwhile(F, String). +- +-read_token(String) -> +- F = fun (C) -> not ?IS_SEPARATOR(C) end, +- lists:splitwith(F, String). +- +-skip_past_separator([]) -> +- []; +-skip_past_separator([$; | Rest]) -> +- Rest; +-skip_past_separator([$, | Rest]) -> +- Rest; +-skip_past_separator([_ | Rest]) -> +- skip_past_separator(Rest). +- +-parse_cookie_test() -> +- %% RFC example +- C1 = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; +- Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; +- Shipping=\"FedEx\"; $Path=\"/acme\"", +- [ +- {"Customer","WILE_E_COYOTE"}, +- {"Part_Number","Rocket_Launcher_0001"}, +- {"Shipping","FedEx"} +- ] = parse_cookie(C1), +- %% Potential edge cases +- [{"foo", "x"}] = parse_cookie("foo=\"\\x\""), +- [] = parse_cookie("="), +- [{"foo", ""}, {"bar", ""}] = parse_cookie(" foo ; bar "), +- [{"foo", ""}, {"bar", ""}] = parse_cookie("foo=;bar="), +- [{"foo", "\";"}, {"bar", ""}] = parse_cookie("foo = \"\\\";\";bar "), +- [{"foo", "\";bar"}] = parse_cookie("foo=\"\\\";bar"). +- +-any_to_list(V) when is_list(V) -> +- V; +-any_to_list(V) when is_atom(V) -> +- atom_to_list(V); +-any_to_list(V) when is_binary(V) -> +- binary_to_list(V); +-any_to_list(V) when is_integer(V) -> +- integer_to_list(V). +- +- +-cookie_test() -> +- C1 = {"Set-Cookie", +- "Customer=WILE_E_COYOTE; " +- "Version=1; " +- "Path=/acme"}, +- C1 = cookie("Customer", "WILE_E_COYOTE", [{path, "/acme"}]), +- C1 = cookie("Customer", "WILE_E_COYOTE", +- [{path, "/acme"}, {badoption, "negatory"}]), +- C1 = cookie('Customer', 'WILE_E_COYOTE', [{path, '/acme'}]), +- C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}]), +- +- {"Set-Cookie","=NoKey; Version=1"} = cookie("", "NoKey", []), +- +- LocalTime = calendar:universal_time_to_local_time({{2007, 5, 15}, {13, 45, 33}}), +- C2 = {"Set-Cookie", +- "Customer=WILE_E_COYOTE; " +- "Version=1; " +- "Expires=Tue, 15 May 2007 13:45:33 GMT; " +- "Max-Age=0"}, +- C2 = cookie("Customer", "WILE_E_COYOTE", +- [{max_age, -111}, {local_time, LocalTime}]), +- C3 = {"Set-Cookie", +- "Customer=WILE_E_COYOTE; " +- "Version=1; " +- "Expires=Wed, 16 May 2007 13:45:50 GMT; " +- "Max-Age=86417"}, +- C3 = cookie("Customer", "WILE_E_COYOTE", +- [{max_age, 86417}, {local_time, LocalTime}]), +- ok. +diff --git a/src/mochiweb/mochiweb_echo.erl b/src/mochiweb/mochiweb_echo.erl +deleted file mode 100644 +index f32d680..0000000 +--- a/src/mochiweb/mochiweb_echo.erl ++++ /dev/null +@@ -1,31 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Simple and stupid echo server to demo mochiweb_socket_server. +- +--module(mochiweb_echo). +--author('bob@mochimedia.com'). +--export([start/0, stop/0, loop/1]). +- +-stop() -> +- mochiweb_socket_server:stop(?MODULE). +- +-start() -> +- mochiweb_socket_server:start([{name, ?MODULE}, +- {port, 6789}, +- {ip, "127.0.0.1"}, +- {max, 1}, +- {loop, {?MODULE, loop}}]). +- +-loop(Socket) -> +- case gen_tcp:recv(Socket, 0, 30000) of +- {ok, Data} -> +- case gen_tcp:send(Socket, Data) of +- ok -> +- loop(Socket); +- _ -> +- exit(normal) +- end; +- _Other -> +- exit(normal) +- end. +diff --git a/src/mochiweb/mochiweb_headers.erl b/src/mochiweb/mochiweb_headers.erl +deleted file mode 100644 +index 6fcec7c..0000000 +--- a/src/mochiweb/mochiweb_headers.erl ++++ /dev/null +@@ -1,186 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Case preserving (but case insensitive) HTTP Header dictionary. +- +--module(mochiweb_headers). +--author('bob@mochimedia.com'). +--export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]). +--export([delete_any/2, get_primary_value/2]). +--export([default/3, enter_from_list/2, default_from_list/2]). +--export([to_list/1, make/1]). +--export([test/0]). +- +-%% @type headers(). +-%% @type key() = atom() | binary() | string(). +-%% @type value() = atom() | binary() | string() | integer(). +- +-%% @spec test() -> ok +-%% @doc Run tests for this module. +-test() -> +- H = ?MODULE:make([{hdr, foo}, {"Hdr", "bar"}, {'Hdr', 2}]), +- [{hdr, "foo, bar, 2"}] = ?MODULE:to_list(H), +- H1 = ?MODULE:insert(taco, grande, H), +- [{hdr, "foo, bar, 2"}, {taco, "grande"}] = ?MODULE:to_list(H1), +- H2 = ?MODULE:make([{"Set-Cookie", "foo"}]), +- [{"Set-Cookie", "foo"}] = ?MODULE:to_list(H2), +- H3 = ?MODULE:insert("Set-Cookie", "bar", H2), +- [{"Set-Cookie", "foo"}, {"Set-Cookie", "bar"}] = ?MODULE:to_list(H3), +- "foo, bar" = ?MODULE:get_value("set-cookie", H3), +- {value, {"Set-Cookie", "foo, bar"}} = ?MODULE:lookup("set-cookie", H3), +- undefined = ?MODULE:get_value("shibby", H3), +- none = ?MODULE:lookup("shibby", H3), +- H4 = ?MODULE:insert("content-type", +- "application/x-www-form-urlencoded; charset=utf8", +- H3), +- "application/x-www-form-urlencoded" = ?MODULE:get_primary_value( +- "content-type", H4), +- H4 = ?MODULE:delete_any("nonexistent-header", H4), +- H3 = ?MODULE:delete_any("content-type", H4), +- ok. +- +-%% @spec empty() -> headers() +-%% @doc Create an empty headers structure. +-empty() -> +- gb_trees:empty(). +- +-%% @spec make(headers() | [{key(), value()}]) -> headers() +-%% @doc Construct a headers() from the given list. +-make(L) when is_list(L) -> +- from_list(L); +-%% assume a tuple is already mochiweb_headers. +-make(T) when is_tuple(T) -> +- T. +- +-%% @spec from_list([{key(), value()}]) -> headers() +-%% @doc Construct a headers() from the given list. +-from_list(List) -> +- lists:foldl(fun ({K, V}, T) -> insert(K, V, T) end, empty(), List). +- +-%% @spec enter_from_list([{key(), value()}], headers()) -> headers() +-%% @doc Insert pairs into the headers, replace any values for existing keys. +-enter_from_list(List, T) -> +- lists:foldl(fun ({K, V}, T1) -> enter(K, V, T1) end, T, List). +- +-%% @spec default_from_list([{key(), value()}], headers()) -> headers() +-%% @doc Insert pairs into the headers for keys that do not already exist. +-default_from_list(List, T) -> +- lists:foldl(fun ({K, V}, T1) -> default(K, V, T1) end, T, List). +- +-%% @spec to_list(headers()) -> [{key(), string()}] +-%% @doc Return the contents of the headers. The keys will be the exact key +-%% that was first inserted (e.g. may be an atom or binary, case is +-%% preserved). +-to_list(T) -> +- F = fun ({K, {array, L}}, Acc) -> +- L1 = lists:reverse(L), +- lists:foldl(fun (V, Acc1) -> [{K, V} | Acc1] end, Acc, L1); +- (Pair, Acc) -> +- [Pair | Acc] +- end, +- lists:reverse(lists:foldl(F, [], gb_trees:values(T))). +- +-%% @spec get_value(key(), headers()) -> string() | undefined +-%% @doc Return the value of the given header using a case insensitive search. +-%% undefined will be returned for keys that are not present. +-get_value(K, T) -> +- case lookup(K, T) of +- {value, {_, V}} -> +- expand(V); +- none -> +- undefined +- end. +- +-%% @spec get_primary_value(key(), headers()) -> string() | undefined +-%% @doc Return the value of the given header up to the first semicolon using +-%% a case insensitive search. undefined will be returned for keys +-%% that are not present. +-get_primary_value(K, T) -> +- case get_value(K, T) of +- undefined -> +- undefined; +- V -> +- lists:takewhile(fun (C) -> C =/= $; end, V) +- end. +- +-%% @spec lookup(key(), headers()) -> {value, {key(), string()}} | none +-%% @doc Return the case preserved key and value for the given header using +-%% a case insensitive search. none will be returned for keys that are +-%% not present. +-lookup(K, T) -> +- case gb_trees:lookup(normalize(K), T) of +- {value, {K0, V}} -> +- {value, {K0, expand(V)}}; +- none -> +- none +- end. +- +-%% @spec default(key(), value(), headers()) -> headers() +-%% @doc Insert the pair into the headers if it does not already exist. +-default(K, V, T) -> +- K1 = normalize(K), +- V1 = any_to_list(V), +- try gb_trees:insert(K1, {K, V1}, T) +- catch +- error:{key_exists, _} -> +- T +- end. +- +-%% @spec enter(key(), value(), headers()) -> headers() +-%% @doc Insert the pair into the headers, replacing any pre-existing key. +-enter(K, V, T) -> +- K1 = normalize(K), +- V1 = any_to_list(V), +- gb_trees:enter(K1, {K, V1}, T). +- +-%% @spec insert(key(), value(), headers()) -> headers() +-%% @doc Insert the pair into the headers, merging with any pre-existing key. +-%% A merge is done with Value = V0 ++ ", " ++ V1. +-insert(K, V, T) -> +- K1 = normalize(K), +- V1 = any_to_list(V), +- try gb_trees:insert(K1, {K, V1}, T) +- catch +- error:{key_exists, _} -> +- {K0, V0} = gb_trees:get(K1, T), +- V2 = merge(K1, V1, V0), +- gb_trees:update(K1, {K0, V2}, T) +- end. +- +-%% @spec delete_any(key(), headers()) -> headers() +-%% @doc Delete the header corresponding to key if it is present. +-delete_any(K, T) -> +- K1 = normalize(K), +- gb_trees:delete_any(K1, T). +- +-%% Internal API +- +-expand({array, L}) -> +- mochiweb_util:join(lists:reverse(L), ", "); +-expand(V) -> +- V. +- +-merge("set-cookie", V1, {array, L}) -> +- {array, [V1 | L]}; +-merge("set-cookie", V1, V0) -> +- {array, [V1, V0]}; +-merge(_, V1, V0) -> +- V0 ++ ", " ++ V1. +- +-normalize(K) when is_list(K) -> +- string:to_lower(K); +-normalize(K) when is_atom(K) -> +- normalize(atom_to_list(K)); +-normalize(K) when is_binary(K) -> +- normalize(binary_to_list(K)). +- +-any_to_list(V) when is_list(V) -> +- V; +-any_to_list(V) when is_atom(V) -> +- atom_to_list(V); +-any_to_list(V) when is_binary(V) -> +- binary_to_list(V); +-any_to_list(V) when is_integer(V) -> +- integer_to_list(V). +- +- +diff --git a/src/mochiweb/mochiweb_html.erl b/src/mochiweb/mochiweb_html.erl +deleted file mode 100644 +index 0e030c1..0000000 +--- a/src/mochiweb/mochiweb_html.erl ++++ /dev/null +@@ -1,880 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Loosely tokenizes and generates parse trees for HTML 4. +--module(mochiweb_html). +--export([tokens/1, parse/1, parse_tokens/1, to_tokens/1, escape/1, +- escape_attr/1, to_html/1, test/0]). +- +-% This is a macro to placate syntax highlighters.. +--define(QUOTE, $\"). +--define(SQUOTE, $\'). +--define(ADV_COL(S, N), +- S#decoder{column=N+S#decoder.column, +- offset=N+S#decoder.offset}). +--define(INC_COL(S), +- S#decoder{column=1+S#decoder.column, +- offset=1+S#decoder.offset}). +--define(INC_LINE(S), +- S#decoder{column=1, +- line=1+S#decoder.line, +- offset=1+S#decoder.offset}). +--define(INC_CHAR(S, C), +- case C of +- $\n -> +- S#decoder{column=1, +- line=1+S#decoder.line, +- offset=1+S#decoder.offset}; +- _ -> +- S#decoder{column=1+S#decoder.column, +- offset=1+S#decoder.offset} +- end). +- +--define(IS_WHITESPACE(C), +- (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). +--define(IS_LITERAL_SAFE(C), +- ((C >= $A andalso C =< $Z) orelse (C >= $a andalso C =< $z) +- orelse (C >= $0 andalso C =< $9))). +- +--record(decoder, {line=1, +- column=1, +- offset=0}). +- +-%% @type html_node() = {string(), [html_attr()], [html_node() | string()]} +-%% @type html_attr() = {string(), string()} +-%% @type html_token() = html_data() | start_tag() | end_tag() | inline_html() | html_comment() | html_doctype() +-%% @type html_data() = {data, string(), Whitespace::boolean()} +-%% @type start_tag() = {start_tag, Name, [html_attr()], Singleton::boolean()} +-%% @type end_tag() = {end_tag, Name} +-%% @type html_comment() = {comment, Comment} +-%% @type html_doctype() = {doctype, [Doctype]} +-%% @type inline_html() = {'=', iolist()} +- +-%% External API. +- +-%% @spec parse(string() | binary()) -> html_node() +-%% @doc tokenize and then transform the token stream into a HTML tree. +-parse(Input) -> +- parse_tokens(tokens(Input)). +- +-%% @spec parse_tokens([html_token()]) -> html_node() +-%% @doc Transform the output of tokens(Doc) into a HTML tree. +-parse_tokens(Tokens) when is_list(Tokens) -> +- %% Skip over doctype, processing instructions +- F = fun (X) -> +- case X of +- {start_tag, _, _, false} -> +- false; +- _ -> +- true +- end +- end, +- [{start_tag, Tag, Attrs, false} | Rest] = lists:dropwhile(F, Tokens), +- {Tree, _} = tree(Rest, [norm({Tag, Attrs})]), +- Tree. +- +-%% @spec tokens(StringOrBinary) -> [html_token()] +-%% @doc Transform the input UTF-8 HTML into a token stream. +-tokens(Input) -> +- tokens(iolist_to_binary(Input), #decoder{}, []). +- +-%% @spec to_tokens(html_node()) -> [html_token()] +-%% @doc Convert a html_node() tree to a list of tokens. +-to_tokens({Tag0}) -> +- to_tokens({Tag0, [], []}); +-to_tokens(T={'=', _}) -> +- [T]; +-to_tokens(T={doctype, _}) -> +- [T]; +-to_tokens(T={comment, _}) -> +- [T]; +-to_tokens({Tag0, Acc}) -> +- to_tokens({Tag0, [], Acc}); +-to_tokens({Tag0, Attrs, Acc}) -> +- Tag = to_tag(Tag0), +- to_tokens([{Tag, Acc}], [{start_tag, Tag, Attrs, is_singleton(Tag)}]). +- +-%% @spec to_html([html_token()] | html_node()) -> iolist() +-%% @doc Convert a list of html_token() to a HTML document. +-to_html(Node) when is_tuple(Node) -> +- to_html(to_tokens(Node)); +-to_html(Tokens) when is_list(Tokens) -> +- to_html(Tokens, []). +- +-%% @spec escape(string() | atom() | binary()) -> binary() +-%% @doc Escape a string such that it's safe for HTML (amp; lt; gt;). +-escape(B) when is_binary(B) -> +- escape(binary_to_list(B), []); +-escape(A) when is_atom(A) -> +- escape(atom_to_list(A), []); +-escape(S) when is_list(S) -> +- escape(S, []). +- +-%% @spec escape_attr(string() | binary() | atom() | integer() | float()) -> binary() +-%% @doc Escape a string such that it's safe for HTML attrs +-%% (amp; lt; gt; quot;). +-escape_attr(B) when is_binary(B) -> +- escape_attr(binary_to_list(B), []); +-escape_attr(A) when is_atom(A) -> +- escape_attr(atom_to_list(A), []); +-escape_attr(S) when is_list(S) -> +- escape_attr(S, []); +-escape_attr(I) when is_integer(I) -> +- escape_attr(integer_to_list(I), []); +-escape_attr(F) when is_float(F) -> +- escape_attr(mochinum:digits(F), []). +- +-%% @spec test() -> ok +-%% @doc Run tests for mochiweb_html. +-test() -> +- test_destack(), +- test_tokens(), +- test_tokens2(), +- test_parse(), +- test_parse2(), +- test_parse_tokens(), +- test_escape(), +- test_escape_attr(), +- test_to_html(), +- ok. +- +- +-%% Internal API +- +-test_to_html() -> +- Expect = <<"hey!

what's up

sucka
">>, +- Expect = iolist_to_binary( +- to_html({html, [], +- [{<<"head">>, [], +- [{title, <<"hey!">>}]}, +- {body, [], +- [{p, [{class, foo}], [<<"what's">>, <<" up">>, {br}]}, +- {'div', <<"sucka">>}, +- {comment, <<" comment! ">>}]}]})), +- Expect1 = <<"">>, +- Expect1 = iolist_to_binary( +- to_html({doctype, +- [<<"html">>, <<"PUBLIC">>, +- <<"-//W3C//DTD XHTML 1.0 Transitional//EN">>, +- <<"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>]})), +- ok. +-to_html([], Acc) -> +- lists:reverse(Acc); +-to_html([{'=', Content} | Rest], Acc) -> +- to_html(Rest, [Content | Acc]); +-to_html([{pi, Tag, Attrs} | Rest], Acc) -> +- Open = [<<">, +- Tag, +- attrs_to_html(Attrs, []), +- <<"?>">>], +- to_html(Rest, [Open | Acc]); +-to_html([{comment, Comment} | Rest], Acc) -> +- to_html(Rest, [[<<"">>] | Acc]); +-to_html([{doctype, Parts} | Rest], Acc) -> +- Inside = doctype_to_html(Parts, Acc), +- to_html(Rest, [[<<">, Inside, <<">">>] | Acc]); +-to_html([{data, Data, _Whitespace} | Rest], Acc) -> +- to_html(Rest, [escape(Data) | Acc]); +-to_html([{start_tag, Tag, Attrs, Singleton} | Rest], Acc) -> +- Open = [<<"<">>, +- Tag, +- attrs_to_html(Attrs, []), +- case Singleton of +- true -> <<" />">>; +- false -> <<">">> +- end], +- to_html(Rest, [Open | Acc]); +-to_html([{end_tag, Tag} | Rest], Acc) -> +- to_html(Rest, [[<<">, Tag, <<">">>] | Acc]). +- +-doctype_to_html([], Acc) -> +- lists:reverse(Acc); +-doctype_to_html([Word | Rest], Acc) -> +- case lists:all(fun (C) -> ?IS_LITERAL_SAFE(C) end, +- binary_to_list(iolist_to_binary(Word))) of +- true -> +- doctype_to_html(Rest, [[<<" ">>, Word] | Acc]); +- false -> +- doctype_to_html(Rest, [[<<" \"">>, escape_attr(Word), ?QUOTE] | Acc]) +- end. +- +-attrs_to_html([], Acc) -> +- lists:reverse(Acc); +-attrs_to_html([{K, V} | Rest], Acc) -> +- attrs_to_html(Rest, +- [[<<" ">>, escape(K), <<"=\"">>, +- escape_attr(V), <<"\"">>] | Acc]). +- +-test_escape() -> +- <<"&quot;\"word <<up!&quot;">> = +- escape(<<""\"word <>), +- ok. +- +-test_escape_attr() -> +- <<"&quot;"word <<up!&quot;">> = +- escape_attr(<<""\"word <>), +- ok. +- +-escape([], Acc) -> +- list_to_binary(lists:reverse(Acc)); +-escape("<" ++ Rest, Acc) -> +- escape(Rest, lists:reverse("<", Acc)); +-escape(">" ++ Rest, Acc) -> +- escape(Rest, lists:reverse(">", Acc)); +-escape("&" ++ Rest, Acc) -> +- escape(Rest, lists:reverse("&", Acc)); +-escape([C | Rest], Acc) -> +- escape(Rest, [C | Acc]). +- +-escape_attr([], Acc) -> +- list_to_binary(lists:reverse(Acc)); +-escape_attr("<" ++ Rest, Acc) -> +- escape_attr(Rest, lists:reverse("<", Acc)); +-escape_attr(">" ++ Rest, Acc) -> +- escape_attr(Rest, lists:reverse(">", Acc)); +-escape_attr("&" ++ Rest, Acc) -> +- escape_attr(Rest, lists:reverse("&", Acc)); +-escape_attr([?QUOTE | Rest], Acc) -> +- escape_attr(Rest, lists:reverse(""", Acc)); +-escape_attr([C | Rest], Acc) -> +- escape_attr(Rest, [C | Acc]). +- +-to_tag(A) when is_atom(A) -> +- norm(atom_to_list(A)); +-to_tag(L) -> +- norm(L). +- +-to_tokens([], Acc) -> +- lists:reverse(Acc); +-to_tokens([{Tag, []} | Rest], Acc) -> +- to_tokens(Rest, [{end_tag, to_tag(Tag)} | Acc]); +-to_tokens([{Tag0, [{T0} | R1]} | Rest], Acc) -> +- %% Allow {br} +- to_tokens([{Tag0, [{T0, [], []} | R1]} | Rest], Acc); +-to_tokens([{Tag0, [T0={'=', _C0} | R1]} | Rest], Acc) -> +- %% Allow {'=', iolist()} +- to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); +-to_tokens([{Tag0, [T0={comment, _C0} | R1]} | Rest], Acc) -> +- %% Allow {comment, iolist()} +- to_tokens([{Tag0, R1} | Rest], [T0 | Acc]); +-to_tokens([{Tag0, [{T0, A0=[{_, _} | _]} | R1]} | Rest], Acc) -> +- %% Allow {p, [{"class", "foo"}]} +- to_tokens([{Tag0, [{T0, A0, []} | R1]} | Rest], Acc); +-to_tokens([{Tag0, [{T0, C0} | R1]} | Rest], Acc) -> +- %% Allow {p, "content"} and {p, <<"content">>} +- to_tokens([{Tag0, [{T0, [], C0} | R1]} | Rest], Acc); +-to_tokens([{Tag0, [{T0, A1, C0} | R1]} | Rest], Acc) when is_binary(C0) -> +- %% Allow {"p", [{"class", "foo"}], <<"content">>} +- to_tokens([{Tag0, [{T0, A1, binary_to_list(C0)} | R1]} | Rest], Acc); +-to_tokens([{Tag0, [{T0, A1, C0=[C | _]} | R1]} | Rest], Acc) +- when is_integer(C) -> +- %% Allow {"p", [{"class", "foo"}], "content"} +- to_tokens([{Tag0, [{T0, A1, [C0]} | R1]} | Rest], Acc); +-to_tokens([{Tag0, [{T0, A1, C1} | R1]} | Rest], Acc) -> +- %% Native {"p", [{"class", "foo"}], ["content"]} +- Tag = to_tag(Tag0), +- T1 = to_tag(T0), +- case is_singleton(norm(T1)) of +- true -> +- to_tokens([{Tag, R1} | Rest], [{start_tag, T1, A1, true} | Acc]); +- false -> +- to_tokens([{T1, C1}, {Tag, R1} | Rest], +- [{start_tag, T1, A1, false} | Acc]) +- end; +-to_tokens([{Tag0, [L | R1]} | Rest], Acc) when is_list(L) -> +- %% List text +- Tag = to_tag(Tag0), +- to_tokens([{Tag, R1} | Rest], [{data, iolist_to_binary(L), false} | Acc]); +-to_tokens([{Tag0, [B | R1]} | Rest], Acc) when is_binary(B) -> +- %% Binary text +- Tag = to_tag(Tag0), +- to_tokens([{Tag, R1} | Rest], [{data, B, false} | Acc]). +- +-test_tokens() -> +- [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>}, +- {<<"wibble">>, <<"wibble">>}, +- {<<"alice">>, <<"bob">>}], true}] = +- tokens(<<"">>), +- [{start_tag, <<"foo">>, [{<<"bar">>, <<"baz">>}, +- {<<"wibble">>, <<"wibble">>}, +- {<<"alice">>, <<"bob">>}], true}] = +- tokens(<<"">>), +- [{comment, <<"[if lt IE 7]>\n\n>}] = +- tokens(<<"">>), +- [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false}, +- {data, <<" A= B <= C ">>, false}, +- {end_tag, <<"script">>}] = +- tokens(<<"">>), +- [{start_tag, <<"textarea">>, [], false}, +- {data, <<"">>, false}, +- {end_tag, <<"textarea">>}] = +- tokens(<<"">>), +- ok. +- +-tokens(B, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary>> -> +- lists:reverse(Acc); +- _ -> +- {Tag, S1} = tokenize(B, S), +- case parse_flag(Tag) of +- script -> +- {Tag2, S2} = tokenize_script(B, S1), +- tokens(B, S2, [Tag2, Tag | Acc]); +- textarea -> +- {Tag2, S2} = tokenize_textarea(B, S1), +- tokens(B, S2, [Tag2, Tag | Acc]); +- none -> +- tokens(B, S1, [Tag | Acc]) +- end +- end. +- +-parse_flag({start_tag, B, _, false}) -> +- case string:to_lower(binary_to_list(B)) of +- "script" -> +- script; +- "textarea" -> +- textarea; +- _ -> +- none +- end; +-parse_flag(_) -> +- none. +- +-tokenize(B, S=#decoder{offset=O}) -> +- case B of +- <<_:O/binary, " +- +- +- +- CDATA>>]]> +-">>, +- Expect = {<<"html">>, [], +- [{<<"head">>, [], +- [{<<"meta">>, +- [{<<"http-equiv">>,<<"Content-Type">>}, +- {<<"content">>,<<"text/html; charset=UTF-8">>}], +- []}, +- {<<"title">>,[],[<<"Foo">>]}, +- {<<"link">>, +- [{<<"rel">>,<<"stylesheet">>}, +- {<<"type">>,<<"text/css">>}, +- {<<"href">>,<<"/static/rel/dojo/resources/dojo.css">>}, +- {<<"media">>,<<"screen">>}], +- []}, +- {<<"link">>, +- [{<<"rel">>,<<"stylesheet">>}, +- {<<"type">>,<<"text/css">>}, +- {<<"href">>,<<"/static/foo.css">>}, +- {<<"media">>,<<"screen">>}], +- []}, +- {comment,<<"[if lt IE 7]>\n \n >}, +- {<<"link">>, +- [{<<"rel">>,<<"icon">>}, +- {<<"href">>,<<"/static/images/favicon.ico">>}, +- {<<"type">>,<<"image/x-icon">>}], +- []}, +- {<<"link">>, +- [{<<"rel">>,<<"shortcut icon">>}, +- {<<"href">>,<<"/static/images/favicon.ico">>}, +- {<<"type">>,<<"image/x-icon">>}], +- []}]}, +- {<<"body">>, +- [{<<"id">>,<<"home">>}, +- {<<"class">>,<<"tundra">>}], +- [<<"<CDATA>>">>]}]}, +- Expect = parse(D0), +- ok. +- +-test_tokens2() -> +- D0 = <<"from __future__ import *http://bob.pythonmac.orgBob's Rants">>, +- Expect = [{start_tag,<<"channel">>,[],false}, +- {start_tag,<<"title">>,[],false}, +- {data,<<"from __future__ import *">>,false}, +- {end_tag,<<"title">>}, +- {start_tag,<<"link">>,[],true}, +- {data,<<"http://bob.pythonmac.org">>,false}, +- {end_tag,<<"link">>}, +- {start_tag,<<"description">>,[],false}, +- {data,<<"Bob's Rants">>,false}, +- {end_tag,<<"description">>}, +- {end_tag,<<"channel">>}], +- Expect = tokens(D0), +- ok. +- +-test_parse2() -> +- D0 = <<"from __future__ import *http://bob.pythonmac.org
fooBob's Rants
">>, +- Expect = {<<"channel">>,[], +- [{<<"title">>,[],[<<"from __future__ import *">>]}, +- {<<"link">>,[],[ +- <<"http://bob.pythonmac.org">>, +- {<<"br">>,[],[]}, +- <<"foo">>]}, +- {<<"description">>,[],[<<"Bob's Rants">>]}]}, +- Expect = parse(D0), +- ok. +- +-test_parse_tokens() -> +- D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]}, +- {data,<<"\n">>,true}, +- {start_tag,<<"html">>,[],false}], +- {<<"html">>, [], []} = parse_tokens(D0), +- D1 = D0 ++ [{end_tag, <<"html">>}], +- {<<"html">>, [], []} = parse_tokens(D1), +- D2 = D0 ++ [{start_tag, <<"body">>, [], false}], +- {<<"html">>, [], [{<<"body">>, [], []}]} = parse_tokens(D2), +- D3 = D0 ++ [{start_tag, <<"head">>, [], false}, +- {end_tag, <<"head">>}, +- {start_tag, <<"body">>, [], false}], +- {<<"html">>, [], [{<<"head">>, [], []}, {<<"body">>, [], []}]} = parse_tokens(D3), +- D4 = D3 ++ [{data,<<"\n">>,true}, +- {start_tag,<<"div">>,[{<<"class">>,<<"a">>}],false}, +- {start_tag,<<"a">>,[{<<"name">>,<<"#anchor">>}],false}, +- {end_tag,<<"a">>}, +- {end_tag,<<"div">>}, +- {start_tag,<<"div">>,[{<<"class">>,<<"b">>}],false}, +- {start_tag,<<"div">>,[{<<"class">>,<<"c">>}],false}, +- {end_tag,<<"div">>}, +- {end_tag,<<"div">>}], +- {<<"html">>, [], +- [{<<"head">>, [], []}, +- {<<"body">>, [], +- [{<<"div">>, [{<<"class">>, <<"a">>}], [{<<"a">>, [{<<"name">>, <<"#anchor">>}], []}]}, +- {<<"div">>, [{<<"class">>, <<"b">>}], [{<<"div">>, [{<<"class">>, <<"c">>}], []}]} +- ]}]} = parse_tokens(D4), +- D5 = [{start_tag,<<"html">>,[],false}, +- {data,<<"\n">>,true}, +- {data,<<"boo">>,false}, +- {data,<<"hoo">>,false}, +- {data,<<"\n">>,true}, +- {end_tag,<<"html">>}], +- {<<"html">>, [], [<<"\nboohoo\n">>]} = parse_tokens(D5), +- D6 = [{start_tag,<<"html">>,[],false}, +- {data,<<"\n">>,true}, +- {data,<<"\n">>,true}, +- {end_tag,<<"html">>}], +- {<<"html">>, [], []} = parse_tokens(D6), +- D7 = [{start_tag,<<"html">>,[],false}, +- {start_tag,<<"ul">>,[],false}, +- {start_tag,<<"li">>,[],false}, +- {data,<<"word">>,false}, +- {start_tag,<<"li">>,[],false}, +- {data,<<"up">>,false}, +- {end_tag,<<"li">>}, +- {start_tag,<<"li">>,[],false}, +- {data,<<"fdsa">>,false}, +- {start_tag,<<"br">>,[],true}, +- {data,<<"asdf">>,false}, +- {end_tag,<<"ul">>}, +- {end_tag,<<"html">>}], +- {<<"html">>, [], +- [{<<"ul">>, [], +- [{<<"li">>, [], [<<"word">>]}, +- {<<"li">>, [], [<<"up">>]}, +- {<<"li">>, [], [<<"fdsa">>,{<<"br">>, [], []}, <<"asdf">>]}]}]} = parse_tokens(D7), +- ok. +- +-tree_data([{data, Data, Whitespace} | Rest], AllWhitespace, Acc) -> +- tree_data(Rest, (Whitespace andalso AllWhitespace), [Data | Acc]); +-tree_data(Rest, AllWhitespace, Acc) -> +- {iolist_to_binary(lists:reverse(Acc)), AllWhitespace, Rest}. +- +-tree([], Stack) -> +- {destack(Stack), []}; +-tree([{end_tag, Tag} | Rest], Stack) -> +- case destack(norm(Tag), Stack) of +- S when is_list(S) -> +- tree(Rest, S); +- Result -> +- {Result, []} +- end; +-tree([{start_tag, Tag, Attrs, true} | Rest], S) -> +- tree(Rest, append_stack_child(norm({Tag, Attrs}), S)); +-tree([{start_tag, Tag, Attrs, false} | Rest], S) -> +- tree(Rest, stack(norm({Tag, Attrs}), S)); +-tree([T={pi, _Tag, _Attrs} | Rest], S) -> +- tree(Rest, append_stack_child(T, S)); +-tree([T={comment, _Comment} | Rest], S) -> +- tree(Rest, append_stack_child(T, S)); +-tree(L=[{data, _Data, _Whitespace} | _], S) -> +- case tree_data(L, true, []) of +- {_, true, Rest} -> +- tree(Rest, S); +- {Data, false, Rest} -> +- tree(Rest, append_stack_child(Data, S)) +- end. +- +-norm({Tag, Attrs}) -> +- {norm(Tag), [{norm(K), iolist_to_binary(V)} || {K, V} <- Attrs], []}; +-norm(Tag) when is_binary(Tag) -> +- Tag; +-norm(Tag) -> +- list_to_binary(string:to_lower(Tag)). +- +-test_destack() -> +- {<<"a">>, [], []} = +- destack([{<<"a">>, [], []}]), +- {<<"a">>, [], [{<<"b">>, [], []}]} = +- destack([{<<"b">>, [], []}, {<<"a">>, [], []}]), +- {<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]} = +- destack([{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}]), +- [{<<"a">>, [], [{<<"b">>, [], [{<<"c">>, [], []}]}]}] = +- destack(<<"b">>, +- [{<<"c">>, [], []}, {<<"b">>, [], []}, {<<"a">>, [], []}]), +- [{<<"b">>, [], [{<<"c">>, [], []}]}, {<<"a">>, [], []}] = +- destack(<<"c">>, +- [{<<"c">>, [], []}, {<<"b">>, [], []},{<<"a">>, [], []}]), +- ok. +- +-stack(T1={TN, _, _}, Stack=[{TN, _, _} | _Rest]) +- when TN =:= <<"li">> orelse TN =:= <<"option">> -> +- [T1 | destack(TN, Stack)]; +-stack(T1={TN0, _, _}, Stack=[{TN1, _, _} | _Rest]) +- when (TN0 =:= <<"dd">> orelse TN0 =:= <<"dt">>) andalso +- (TN1 =:= <<"dd">> orelse TN1 =:= <<"dt">>) -> +- [T1 | destack(TN1, Stack)]; +-stack(T1, Stack) -> +- [T1 | Stack]. +- +-append_stack_child(StartTag, [{Name, Attrs, Acc} | Stack]) -> +- [{Name, Attrs, [StartTag | Acc]} | Stack]. +- +-destack(TagName, Stack) when is_list(Stack) -> +- F = fun (X) -> +- case X of +- {TagName, _, _} -> +- false; +- _ -> +- true +- end +- end, +- case lists:splitwith(F, Stack) of +- {_, []} -> +- %% If we're parsing something like XML we might find +- %% a tag that is normally a singleton +- %% in HTML but isn't here +- case {is_singleton(TagName), Stack} of +- {true, [{T0, A0, Acc0} | Post0]} -> +- case lists:splitwith(F, Acc0) of +- {_, []} -> +- %% Actually was a singleton +- Stack; +- {Pre, [{T1, A1, []} | Post1]} -> +- [{T0, A0, [{T1, A1, lists:reverse(Pre)} | Post1]} +- | Post0] +- end; +- _ -> +- %% No match, no state change +- Stack +- end; +- {_Pre, [_T]} -> +- %% Unfurl the whole stack, we're done +- destack(Stack); +- {Pre, [T, {T0, A0, Acc0} | Post]} -> +- %% Unfurl up to the tag, then accumulate it +- [{T0, A0, [destack(Pre ++ [T]) | Acc0]} | Post] +- end. +- +-destack([{Tag, Attrs, Acc}]) -> +- {Tag, Attrs, lists:reverse(Acc)}; +-destack([{T1, A1, Acc1}, {T0, A0, Acc0} | Rest]) -> +- destack([{T0, A0, [{T1, A1, lists:reverse(Acc1)} | Acc0]} | Rest]). +- +-is_singleton(<<"br">>) -> true; +-is_singleton(<<"hr">>) -> true; +-is_singleton(<<"img">>) -> true; +-is_singleton(<<"input">>) -> true; +-is_singleton(<<"base">>) -> true; +-is_singleton(<<"meta">>) -> true; +-is_singleton(<<"link">>) -> true; +-is_singleton(<<"area">>) -> true; +-is_singleton(<<"param">>) -> true; +-is_singleton(<<"col">>) -> true; +-is_singleton(_) -> false. +- +-tokenize_data(B, S=#decoder{offset=O}) -> +- tokenize_data(B, S, O, true). +- +-tokenize_data(B, S=#decoder{offset=O}, Start, Whitespace) -> +- case B of +- <<_:O/binary, C, _/binary>> when (C =/= $< andalso C =/= $&) -> +- tokenize_data(B, ?INC_CHAR(S, C), Start, +- (Whitespace andalso ?IS_WHITESPACE(C))); +- _ -> +- Len = O - Start, +- <<_:Start/binary, Data:Len/binary, _/binary>> = B, +- {{data, Data, Whitespace}, S} +- end. +- +-tokenize_attributes(B, S) -> +- tokenize_attributes(B, S, []). +- +-tokenize_attributes(B, S=#decoder{offset=O}, Acc) -> +- case B of +- <<_:O/binary>> -> +- {lists:reverse(Acc), S}; +- <<_:O/binary, C, _/binary>> when (C =:= $> orelse C =:= $/) -> +- {lists:reverse(Acc), S}; +- <<_:O/binary, "?>", _/binary>> -> +- {lists:reverse(Acc), S}; +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> +- tokenize_attributes(B, ?INC_CHAR(S, C), Acc); +- _ -> +- {Attr, S1} = tokenize_literal(B, S), +- {Value, S2} = tokenize_attr_value(Attr, B, S1), +- tokenize_attributes(B, S2, [{Attr, Value} | Acc]) +- end. +- +-tokenize_attr_value(Attr, B, S) -> +- S1 = skip_whitespace(B, S), +- O = S1#decoder.offset, +- case B of +- <<_:O/binary, "=", _/binary>> -> +- tokenize_word_or_literal(B, ?INC_COL(S1)); +- _ -> +- {Attr, S1} +- end. +- +-skip_whitespace(B, S=#decoder{offset=O}) -> +- case B of +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> +- skip_whitespace(B, ?INC_CHAR(S, C)); +- _ -> +- S +- end. +- +-tokenize_literal(Bin, S) -> +- tokenize_literal(Bin, S, []). +- +-tokenize_literal(Bin, S=#decoder{offset=O}, Acc) -> +- case Bin of +- <<_:O/binary, $&, _/binary>> -> +- {{data, Data, false}, S1} = tokenize_charref(Bin, ?INC_COL(S)), +- tokenize_literal(Bin, S1, [Data | Acc]); +- <<_:O/binary, C, _/binary>> when not (?IS_WHITESPACE(C) +- orelse C =:= $> +- orelse C =:= $/ +- orelse C =:= $=) -> +- tokenize_literal(Bin, ?INC_COL(S), [C | Acc]); +- _ -> +- {iolist_to_binary(lists:reverse(Acc)), S} +- end. +- +-find_qgt(Bin, S=#decoder{offset=O}) -> +- case Bin of +- <<_:O/binary, "?>", _/binary>> -> +- ?ADV_COL(S, 2); +- <<_:O/binary, C, _/binary>> -> +- find_qgt(Bin, ?INC_CHAR(S, C)); +- _ -> +- S +- end. +- +-find_gt(Bin, S) -> +- find_gt(Bin, S, false). +- +-find_gt(Bin, S=#decoder{offset=O}, HasSlash) -> +- case Bin of +- <<_:O/binary, $/, _/binary>> -> +- find_gt(Bin, ?INC_COL(S), true); +- <<_:O/binary, $>, _/binary>> -> +- {?INC_COL(S), HasSlash}; +- <<_:O/binary, C, _/binary>> -> +- find_gt(Bin, ?INC_CHAR(S, C), HasSlash); +- _ -> +- {S, HasSlash} +- end. +- +-tokenize_charref(Bin, S=#decoder{offset=O}) -> +- tokenize_charref(Bin, S, O). +- +-tokenize_charref(Bin, S=#decoder{offset=O}, Start) -> +- case Bin of +- <<_:O/binary>> -> +- <<_:Start/binary, Raw/binary>> = Bin, +- {{data, Raw, false}, S}; +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) +- orelse C =:= ?SQUOTE +- orelse C =:= ?QUOTE +- orelse C =:= $/ +- orelse C =:= $> -> +- Len = O - Start, +- <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, +- {{data, Raw, false}, S}; +- <<_:O/binary, $;, _/binary>> -> +- Len = O - Start, +- <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, +- Data = case mochiweb_charref:charref(Raw) of +- undefined -> +- Start1 = Start - 1, +- Len1 = Len + 2, +- <<_:Start1/binary, R:Len1/binary, _/binary>> = Bin, +- R; +- Unichar -> +- list_to_binary(xmerl_ucs:to_utf8(Unichar)) +- end, +- {{data, Data, false}, ?INC_COL(S)}; +- _ -> +- tokenize_charref(Bin, ?INC_COL(S), Start) +- end. +- +-tokenize_doctype(Bin, S) -> +- tokenize_doctype(Bin, S, []). +- +-tokenize_doctype(Bin, S=#decoder{offset=O}, Acc) -> +- case Bin of +- <<_:O/binary>> -> +- {{doctype, lists:reverse(Acc)}, S}; +- <<_:O/binary, $>, _/binary>> -> +- {{doctype, lists:reverse(Acc)}, ?INC_COL(S)}; +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> +- tokenize_doctype(Bin, ?INC_CHAR(S, C), Acc); +- _ -> +- {Word, S1} = tokenize_word_or_literal(Bin, S), +- tokenize_doctype(Bin, S1, [Word | Acc]) +- end. +- +-tokenize_word_or_literal(Bin, S=#decoder{offset=O}) -> +- case Bin of +- <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> +- {error, {whitespace, [C], S}}; +- <<_:O/binary, C, _/binary>> when C =:= ?QUOTE orelse C =:= ?SQUOTE -> +- tokenize_word(Bin, ?INC_COL(S), C); +- _ -> +- tokenize_literal(Bin, S, []) +- end. +- +-tokenize_word(Bin, S, Quote) -> +- tokenize_word(Bin, S, Quote, []). +- +-tokenize_word(Bin, S=#decoder{offset=O}, Quote, Acc) -> +- case Bin of +- <<_:O/binary>> -> +- {iolist_to_binary(lists:reverse(Acc)), S}; +- <<_:O/binary, Quote, _/binary>> -> +- {iolist_to_binary(lists:reverse(Acc)), ?INC_COL(S)}; +- <<_:O/binary, $&, _/binary>> -> +- {{data, Data, false}, S1} = tokenize_charref(Bin, ?INC_COL(S)), +- tokenize_word(Bin, S1, Quote, [Data | Acc]); +- <<_:O/binary, C, _/binary>> -> +- tokenize_word(Bin, ?INC_CHAR(S, C), Quote, [C | Acc]) +- end. +- +-tokenize_cdata(Bin, S=#decoder{offset=O}) -> +- tokenize_cdata(Bin, S, O). +- +-tokenize_cdata(Bin, S=#decoder{offset=O}, Start) -> +- case Bin of +- <<_:O/binary, "]]>", _/binary>> -> +- Len = O - Start, +- <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, +- {{data, Raw, false}, ?ADV_COL(S, 3)}; +- <<_:O/binary, C, _/binary>> -> +- tokenize_cdata(Bin, ?INC_CHAR(S, C), Start); +- _ -> +- <<_:O/binary, Raw/binary>> = Bin, +- {{data, Raw, false}, S} +- end. +- +-tokenize_comment(Bin, S=#decoder{offset=O}) -> +- tokenize_comment(Bin, S, O). +- +-tokenize_comment(Bin, S=#decoder{offset=O}, Start) -> +- case Bin of +- <<_:O/binary, "-->", _/binary>> -> +- Len = O - Start, +- <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, +- {{comment, Raw}, ?ADV_COL(S, 3)}; +- <<_:O/binary, C, _/binary>> -> +- tokenize_comment(Bin, ?INC_CHAR(S, C), Start); +- <<_:Start/binary, Raw/binary>> -> +- {{comment, Raw}, S} +- end. +- +-tokenize_script(Bin, S=#decoder{offset=O}) -> +- tokenize_script(Bin, S, O). +- +-tokenize_script(Bin, S=#decoder{offset=O}, Start) -> +- case Bin of +- %% Just a look-ahead, we want the end_tag separately +- <<_:O/binary, $<, $/, SS, CC, RR, II, PP, TT, _/binary>> +- when (SS =:= $s orelse SS =:= $S) andalso +- (CC =:= $c orelse CC =:= $C) andalso +- (RR =:= $r orelse RR =:= $R) andalso +- (II =:= $i orelse II =:= $I) andalso +- (PP =:= $p orelse PP =:= $P) andalso +- (TT=:= $t orelse TT =:= $T) -> +- Len = O - Start, +- <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, +- {{data, Raw, false}, S}; +- <<_:O/binary, C, _/binary>> -> +- tokenize_script(Bin, ?INC_CHAR(S, C), Start); +- <<_:Start/binary, Raw/binary>> -> +- {{data, Raw, false}, S} +- end. +- +-tokenize_textarea(Bin, S=#decoder{offset=O}) -> +- tokenize_textarea(Bin, S, O). +- +-tokenize_textarea(Bin, S=#decoder{offset=O}, Start) -> +- case Bin of +- %% Just a look-ahead, we want the end_tag separately +- <<_:O/binary, $<, $/, TT, EE, XX, TT2, AA, RR, EE2, AA2, _/binary>> +- when (TT =:= $t orelse TT =:= $T) andalso +- (EE =:= $e orelse EE =:= $E) andalso +- (XX =:= $x orelse XX =:= $X) andalso +- (TT2 =:= $t orelse TT2 =:= $T) andalso +- (AA =:= $a orelse AA =:= $A) andalso +- (RR =:= $r orelse RR =:= $R) andalso +- (EE2 =:= $e orelse EE2 =:= $E) andalso +- (AA2 =:= $a orelse AA2 =:= $A) -> +- Len = O - Start, +- <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin, +- {{data, Raw, false}, S}; +- <<_:O/binary, C, _/binary>> -> +- tokenize_textarea(Bin, ?INC_CHAR(S, C), Start); +- <<_:Start/binary, Raw/binary>> -> +- {{data, Raw, false}, S} +- end. +diff --git a/src/mochiweb/mochiweb_http.erl b/src/mochiweb/mochiweb_http.erl +deleted file mode 100644 +index 14a3657..0000000 +--- a/src/mochiweb/mochiweb_http.erl ++++ /dev/null +@@ -1,142 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc HTTP server. +- +--module(mochiweb_http). +--author('bob@mochimedia.com'). +--export([start/0, start/1, stop/0, stop/1]). +--export([loop/2, default_body/1]). +- +--define(IDLE_TIMEOUT, 30000). +- +--define(MAX_HEADERS, 1000). +--define(DEFAULTS, [{name, ?MODULE}, +- {port, 8888}]). +- +-set_default({Prop, Value}, PropList) -> +- case proplists:is_defined(Prop, PropList) of +- true -> +- PropList; +- false -> +- [{Prop, Value} | PropList] +- end. +- +-set_defaults(Defaults, PropList) -> +- lists:foldl(fun set_default/2, PropList, Defaults). +- +-parse_options(Options) -> +- {loop, HttpLoop} = proplists:lookup(loop, Options), +- Loop = fun (S) -> +- ?MODULE:loop(S, HttpLoop) +- end, +- Options1 = [{loop, Loop} | proplists:delete(loop, Options)], +- set_defaults(?DEFAULTS, Options1). +- +-stop() -> +- mochiweb_socket_server:stop(?MODULE). +- +-stop(Name) -> +- mochiweb_socket_server:stop(Name). +- +-start() -> +- start([{ip, "127.0.0.1"}, +- {loop, {?MODULE, default_body}}]). +- +-start(Options) -> +- mochiweb_socket_server:start(parse_options(Options)). +- +-frm(Body) -> +- ["" +- "
" +- "" +- "" +- "
" +- "
" +- "
" +- "" +- "" +- "" +- "
" +- "
", Body, "
" +- ""]. +- +-default_body(Req, M, "/chunked") when M =:= 'GET'; M =:= 'HEAD' -> +- Res = Req:ok({"text/plain", [], chunked}), +- Res:write_chunk("First chunk\r\n"), +- timer:sleep(5000), +- Res:write_chunk("Last chunk\r\n"), +- Res:write_chunk(""); +-default_body(Req, M, _Path) when M =:= 'GET'; M =:= 'HEAD' -> +- Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, +- {parse_cookie, Req:parse_cookie()}, +- Req:dump()]]), +- Req:ok({"text/html", +- [mochiweb_cookies:cookie("mochiweb_http", "test_cookie")], +- frm(Body)}); +-default_body(Req, 'POST', "/multipart") -> +- Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, +- {parse_cookie, Req:parse_cookie()}, +- {body, Req:recv_body()}, +- Req:dump()]]), +- Req:ok({"text/html", [], frm(Body)}); +-default_body(Req, 'POST', _Path) -> +- Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, +- {parse_cookie, Req:parse_cookie()}, +- {parse_post, Req:parse_post()}, +- Req:dump()]]), +- Req:ok({"text/html", [], frm(Body)}); +-default_body(Req, _Method, _Path) -> +- Req:respond({501, [], []}). +- +-default_body(Req) -> +- default_body(Req, Req:get(method), Req:get(path)). +- +-loop(Socket, Body) -> +- inet:setopts(Socket, [{packet, http}]), +- request(Socket, Body). +- +-request(Socket, Body) -> +- case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of +- {ok, {http_request, Method, Path, Version}} -> +- headers(Socket, {Method, Path, Version}, [], Body, 0); +- {error, {http_error, "\r\n"}} -> +- request(Socket, Body); +- {error, {http_error, "\n"}} -> +- request(Socket, Body); +- _Other -> +- gen_tcp:close(Socket), +- exit(normal) +- end. +- +-headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) -> +- %% Too many headers sent, bad request. +- inet:setopts(Socket, [{packet, raw}]), +- Req = mochiweb:new_request({Socket, Request, +- lists:reverse(Headers)}), +- Req:respond({400, [], []}), +- gen_tcp:close(Socket), +- exit(normal); +-headers(Socket, Request, Headers, Body, HeaderCount) -> +- case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of +- {ok, http_eoh} -> +- inet:setopts(Socket, [{packet, raw}]), +- Req = mochiweb:new_request({Socket, Request, +- lists:reverse(Headers)}), +- Body(Req), +- case Req:should_close() of +- true -> +- gen_tcp:close(Socket), +- exit(normal); +- false -> +- Req:cleanup(), +- ?MODULE:loop(Socket, Body) +- end; +- {ok, {http_header, _, Name, _, Value}} -> +- headers(Socket, Request, [{Name, Value} | Headers], Body, +- 1 + HeaderCount); +- _Other -> +- gen_tcp:close(Socket), +- exit(normal) +- end. +diff --git a/src/mochiweb/mochiweb_multipart.erl b/src/mochiweb/mochiweb_multipart.erl +deleted file mode 100644 +index 9eb4bad..0000000 +--- a/src/mochiweb/mochiweb_multipart.erl ++++ /dev/null +@@ -1,429 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Utilities for parsing multipart/form-data. +- +--module(mochiweb_multipart). +--author('bob@mochimedia.com'). +- +--export([parse_form/1, parse_form/2]). +--export([parse_multipart_request/2]). +--export([test/0]). +- +--define(CHUNKSIZE, 4096). +- +--record(mp, {state, boundary, length, buffer, callback, req}). +- +-%% TODO: DOCUMENT THIS MODULE. +- +-parse_form(Req) -> +- parse_form(Req, fun default_file_handler/2). +- +-parse_form(Req, FileHandler) -> +- Callback = fun (Next) -> parse_form_outer(Next, FileHandler, []) end, +- {_, _, Res} = parse_multipart_request(Req, Callback), +- Res. +- +-parse_form_outer(eof, _, Acc) -> +- lists:reverse(Acc); +-parse_form_outer({headers, H}, FileHandler, State) -> +- {"form-data", H1} = proplists:get_value("content-disposition", H), +- Name = proplists:get_value("name", H1), +- Filename = proplists:get_value("filename", H1), +- case Filename of +- undefined -> +- fun (Next) -> +- parse_form_value(Next, {Name, []}, FileHandler, State) +- end; +- _ -> +- ContentType = proplists:get_value("content-type", H), +- Handler = FileHandler(Filename, ContentType), +- fun (Next) -> +- parse_form_file(Next, {Name, Handler}, FileHandler, State) +- end +- end. +- +-parse_form_value(body_end, {Name, Acc}, FileHandler, State) -> +- Value = binary_to_list(iolist_to_binary(lists:reverse(Acc))), +- State1 = [{Name, Value} | State], +- fun (Next) -> parse_form_outer(Next, FileHandler, State1) end; +-parse_form_value({body, Data}, {Name, Acc}, FileHandler, State) -> +- Acc1 = [Data | Acc], +- fun (Next) -> parse_form_value(Next, {Name, Acc1}, FileHandler, State) end. +- +-parse_form_file(body_end, {Name, Handler}, FileHandler, State) -> +- Value = Handler(eof), +- State1 = [{Name, Value} | State], +- fun (Next) -> parse_form_outer(Next, FileHandler, State1) end; +-parse_form_file({body, Data}, {Name, Handler}, FileHandler, State) -> +- H1 = Handler(Data), +- fun (Next) -> parse_form_file(Next, {Name, H1}, FileHandler, State) end. +- +-default_file_handler(Filename, ContentType) -> +- default_file_handler_1(Filename, ContentType, []). +- +-default_file_handler_1(Filename, ContentType, Acc) -> +- fun(eof) -> +- Value = iolist_to_binary(lists:reverse(Acc)), +- {Filename, ContentType, Value}; +- (Next) -> +- default_file_handler_1(Filename, ContentType, [Next | Acc]) +- end. +- +-parse_multipart_request(Req, Callback) -> +- %% TODO: Support chunked? +- Length = list_to_integer(Req:get_header_value("content-length")), +- Boundary = iolist_to_binary( +- get_boundary(Req:get_header_value("content-type"))), +- Prefix = <<"\r\n--", Boundary/binary>>, +- BS = size(Boundary), +- Chunk = read_chunk(Req, Length), +- Length1 = Length - size(Chunk), +- <<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk, +- feed_mp(headers, #mp{boundary=Prefix, +- length=Length1, +- buffer=Rest, +- callback=Callback, +- req=Req}). +- +-parse_headers(<<>>) -> +- []; +-parse_headers(Binary) -> +- parse_headers(Binary, []). +- +-parse_headers(Binary, Acc) -> +- case find_in_binary(<<"\r\n">>, Binary) of +- {exact, N} -> +- <> = Binary, +- parse_headers(Rest, [split_header(Line) | Acc]); +- not_found -> +- lists:reverse([split_header(Binary) | Acc]) +- end. +- +-split_header(Line) -> +- {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end, +- binary_to_list(Line)), +- {string:to_lower(string:strip(Name)), +- mochiweb_util:parse_header(Value)}. +- +-read_chunk(Req, Length) when Length > 0 -> +- case Length of +- Length when Length < ?CHUNKSIZE -> +- Req:recv(Length); +- _ -> +- Req:recv(?CHUNKSIZE) +- end. +- +-read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) -> +- Data = read_chunk(Req, Length), +- Buffer1 = <>, +- State#mp{length=Length - size(Data), +- buffer=Buffer1}. +- +-feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) -> +- {State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of +- {exact, N} -> +- {State, N}; +- _ -> +- S1 = read_more(State), +- %% Assume headers must be less than ?CHUNKSIZE +- {exact, N} = find_in_binary(<<"\r\n\r\n">>, +- S1#mp.buffer), +- {S1, N} +- end, +- <> = State1#mp.buffer, +- NextCallback = Callback({headers, parse_headers(Headers)}), +- feed_mp(body, State1#mp{buffer=Rest, +- callback=NextCallback}); +-feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) -> +- case find_boundary(Prefix, Buffer) of +- {end_boundary, Start, Skip} -> +- <> = Buffer, +- C1 = Callback({body, Data}), +- C2 = C1(body_end), +- {State#mp.length, Rest, C2(eof)}; +- {next_boundary, Start, Skip} -> +- <> = Buffer, +- C1 = Callback({body, Data}), +- feed_mp(headers, State#mp{callback=C1(body_end), +- buffer=Rest}); +- {maybe, Start} -> +- <> = Buffer, +- feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), +- buffer=Rest})); +- not_found -> +- {Data, Rest} = {Buffer, <<>>}, +- feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), +- buffer=Rest})) +- end. +- +-get_boundary(ContentType) -> +- {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType), +- case proplists:get_value("boundary", Opts) of +- S when is_list(S) -> +- S +- end. +- +-find_in_binary(B, Data) when size(B) > 0 -> +- case size(Data) - size(B) of +- Last when Last < 0 -> +- partial_find(B, Data, 0, size(Data)); +- Last -> +- find_in_binary(B, size(B), Data, 0, Last) +- end. +- +-find_in_binary(B, BS, D, N, Last) when N =< Last-> +- case D of +- <<_:N/binary, B:BS/binary, _/binary>> -> +- {exact, N}; +- _ -> +- find_in_binary(B, BS, D, 1 + N, Last) +- end; +-find_in_binary(B, BS, D, N, Last) when N =:= 1 + Last -> +- partial_find(B, D, N, BS - 1). +- +-partial_find(_B, _D, _N, 0) -> +- not_found; +-partial_find(B, D, N, K) -> +- <> = B, +- case D of +- <<_Skip:N/binary, B1:K/binary>> -> +- {partial, N, K}; +- _ -> +- partial_find(B, D, 1 + N, K - 1) +- end. +- +-find_boundary(Prefix, Data) -> +- case find_in_binary(Prefix, Data) of +- {exact, Skip} -> +- PrefixSkip = Skip + size(Prefix), +- case Data of +- <<_:PrefixSkip/binary, "\r\n", _/binary>> -> +- {next_boundary, Skip, size(Prefix) + 2}; +- <<_:PrefixSkip/binary, "--\r\n", _/binary>> -> +- {end_boundary, Skip, size(Prefix) + 4}; +- _ when size(Data) < PrefixSkip + 4 -> +- %% Underflow +- {maybe, Skip}; +- _ -> +- %% False positive +- not_found +- end; +- {partial, Skip, Length} when (Skip + Length) =:= size(Data) -> +- %% Underflow +- {maybe, Skip}; +- _ -> +- not_found +- end. +- +-with_socket_server(ServerFun, ClientFun) -> +- {ok, Server} = mochiweb_socket_server:start([{ip, "127.0.0.1"}, +- {port, 0}, +- {loop, ServerFun}]), +- Port = mochiweb_socket_server:get(Server, port), +- {ok, Client} = gen_tcp:connect("127.0.0.1", Port, +- [binary, {active, false}]), +- Res = (catch ClientFun(Client)), +- mochiweb_socket_server:stop(Server), +- Res. +- +-fake_request(Socket, ContentType, Length) -> +- mochiweb_request:new(Socket, +- 'POST', +- "/multipart", +- {1,1}, +- mochiweb_headers:make( +- [{"content-type", ContentType}, +- {"content-length", Length}])). +- +-test_callback(Expect, [Expect | Rest]) -> +- case Rest of +- [] -> +- ok; +- _ -> +- fun (Next) -> test_callback(Next, Rest) end +- end. +- +-test_parse3() -> +- ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882", +- BinContent = <<"-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file.txt\"\r\nContent-Type: text/plain\r\n\r\nWoo multiline text file\n\nLa la la\r\n-----------------------------7386909285754635891697677882--\r\n">>, +- Expect = [{headers, +- [{"content-disposition", +- {"form-data", [{"name", "hidden"}]}}]}, +- {body, <<"multipart message">>}, +- body_end, +- {headers, +- [{"content-disposition", +- {"form-data", [{"name", "file"}, {"filename", "test_file.txt"}]}}, +- {"content-type", {"text/plain", []}}]}, +- {body, <<"Woo multiline text file\n\nLa la la">>}, +- body_end, +- eof], +- TestCallback = fun (Next) -> test_callback(Next, Expect) end, +- ServerFun = fun (Socket) -> +- case gen_tcp:send(Socket, BinContent) of +- ok -> +- exit(normal) +- end +- end, +- ClientFun = fun (Socket) -> +- Req = fake_request(Socket, ContentType, +- size(BinContent)), +- Res = parse_multipart_request(Req, TestCallback), +- {0, <<>>, ok} = Res, +- ok +- end, +- ok = with_socket_server(ServerFun, ClientFun), +- ok. +- +- +-test_parse2() -> +- ContentType = "multipart/form-data; boundary=---------------------------6072231407570234361599764024", +- BinContent = <<"-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----------------------------6072231407570234361599764024--\r\n">>, +- Expect = [{headers, +- [{"content-disposition", +- {"form-data", [{"name", "hidden"}]}}]}, +- {body, <<"multipart message">>}, +- body_end, +- {headers, +- [{"content-disposition", +- {"form-data", [{"name", "file"}, {"filename", ""}]}}, +- {"content-type", {"application/octet-stream", []}}]}, +- {body, <<>>}, +- body_end, +- eof], +- TestCallback = fun (Next) -> test_callback(Next, Expect) end, +- ServerFun = fun (Socket) -> +- case gen_tcp:send(Socket, BinContent) of +- ok -> +- exit(normal) +- end +- end, +- ClientFun = fun (Socket) -> +- Req = fake_request(Socket, ContentType, +- size(BinContent)), +- Res = parse_multipart_request(Req, TestCallback), +- {0, <<>>, ok} = Res, +- ok +- end, +- ok = with_socket_server(ServerFun, ClientFun), +- ok. +- +-test_parse_form() -> +- ContentType = "multipart/form-data; boundary=AaB03x", +- "AaB03x" = get_boundary(ContentType), +- Content = mochiweb_util:join( +- ["--AaB03x", +- "Content-Disposition: form-data; name=\"submit-name\"", +- "", +- "Larry", +- "--AaB03x", +- "Content-Disposition: form-data; name=\"files\";" +- ++ "filename=\"file1.txt\"", +- "Content-Type: text/plain", +- "", +- "... contents of file1.txt ...", +- "--AaB03x--", +- ""], "\r\n"), +- BinContent = iolist_to_binary(Content), +- ServerFun = fun (Socket) -> +- case gen_tcp:send(Socket, BinContent) of +- ok -> +- exit(normal) +- end +- end, +- ClientFun = fun (Socket) -> +- Req = fake_request(Socket, ContentType, +- size(BinContent)), +- Res = parse_form(Req), +- [{"submit-name", "Larry"}, +- {"files", {"file1.txt", {"text/plain",[]}, +- <<"... contents of file1.txt ...">>} +- }] = Res, +- ok +- end, +- ok = with_socket_server(ServerFun, ClientFun), +- ok. +- +-test_parse() -> +- ContentType = "multipart/form-data; boundary=AaB03x", +- "AaB03x" = get_boundary(ContentType), +- Content = mochiweb_util:join( +- ["--AaB03x", +- "Content-Disposition: form-data; name=\"submit-name\"", +- "", +- "Larry", +- "--AaB03x", +- "Content-Disposition: form-data; name=\"files\";" +- ++ "filename=\"file1.txt\"", +- "Content-Type: text/plain", +- "", +- "... contents of file1.txt ...", +- "--AaB03x--", +- ""], "\r\n"), +- BinContent = iolist_to_binary(Content), +- Expect = [{headers, +- [{"content-disposition", +- {"form-data", [{"name", "submit-name"}]}}]}, +- {body, <<"Larry">>}, +- body_end, +- {headers, +- [{"content-disposition", +- {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}}, +- {"content-type", {"text/plain", []}}]}, +- {body, <<"... contents of file1.txt ...">>}, +- body_end, +- eof], +- TestCallback = fun (Next) -> test_callback(Next, Expect) end, +- ServerFun = fun (Socket) -> +- case gen_tcp:send(Socket, BinContent) of +- ok -> +- exit(normal) +- end +- end, +- ClientFun = fun (Socket) -> +- Req = fake_request(Socket, ContentType, +- size(BinContent)), +- Res = parse_multipart_request(Req, TestCallback), +- {0, <<>>, ok} = Res, +- ok +- end, +- ok = with_socket_server(ServerFun, ClientFun), +- ok. +- +-test_find_boundary() -> +- B = <<"\r\n--X">>, +- {next_boundary, 0, 7} = find_boundary(B, <<"\r\n--X\r\nRest">>), +- {next_boundary, 1, 7} = find_boundary(B, <<"!\r\n--X\r\nRest">>), +- {end_boundary, 0, 9} = find_boundary(B, <<"\r\n--X--\r\nRest">>), +- {end_boundary, 1, 9} = find_boundary(B, <<"!\r\n--X--\r\nRest">>), +- not_found = find_boundary(B, <<"--X\r\nRest">>), +- {maybe, 0} = find_boundary(B, <<"\r\n--X\r">>), +- {maybe, 1} = find_boundary(B, <<"!\r\n--X\r">>), +- P = <<"\r\n-----------------------------16037454351082272548568224146">>, +- B0 = <<55,212,131,77,206,23,216,198,35,87,252,118,252,8,25,211,132,229, +- 182,42,29,188,62,175,247,243,4,4,0,59, 13,10,45,45,45,45,45,45,45, +- 45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45, +- 49,54,48,51,55,52,53,52,51,53,49>>, +- {maybe, 30} = find_boundary(P, B0), +- ok. +- +-test_find_in_binary() -> +- {exact, 0} = find_in_binary(<<"foo">>, <<"foobarbaz">>), +- {exact, 1} = find_in_binary(<<"oo">>, <<"foobarbaz">>), +- {exact, 8} = find_in_binary(<<"z">>, <<"foobarbaz">>), +- not_found = find_in_binary(<<"q">>, <<"foobarbaz">>), +- {partial, 7, 2} = find_in_binary(<<"azul">>, <<"foobarbaz">>), +- {exact, 0} = find_in_binary(<<"foobarbaz">>, <<"foobarbaz">>), +- {partial, 0, 3} = find_in_binary(<<"foobar">>, <<"foo">>), +- {partial, 1, 3} = find_in_binary(<<"foobar">>, <<"afoo">>), +- ok. +- +-test() -> +- test_find_in_binary(), +- test_find_boundary(), +- test_parse(), +- test_parse2(), +- test_parse3(), +- test_parse_form(), +- ok. +diff --git a/src/mochiweb/mochiweb_request.erl b/src/mochiweb/mochiweb_request.erl +deleted file mode 100644 +index 64c4f58..0000000 +--- a/src/mochiweb/mochiweb_request.erl ++++ /dev/null +@@ -1,790 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc MochiWeb HTTP Request abstraction. +- +--module(mochiweb_request, [Socket, Method, RawPath, Version, Headers]). +--author('bob@mochimedia.com'). +- +--include_lib("kernel/include/file.hrl"). +- +--define(QUIP, "Any of you quaids got a smint?"). +--define(READ_SIZE, 8192). +- +--export([get_header_value/1, get_primary_header_value/1, get/1, dump/0]). +--export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]). +--export([start_response/1, start_response_length/1, start_raw_response/1]). +--export([respond/1, ok/1]). +--export([not_found/0, not_found/1]). +--export([parse_post/0, parse_qs/0]). +--export([should_close/0, cleanup/0]). +--export([parse_cookie/0, get_cookie_value/1]). +--export([serve_file/2, serve_file/3]). +--export([test/0]). +- +--define(SAVE_QS, mochiweb_request_qs). +--define(SAVE_PATH, mochiweb_request_path). +--define(SAVE_RECV, mochiweb_request_recv). +--define(SAVE_BODY, mochiweb_request_body). +--define(SAVE_BODY_LENGTH, mochiweb_request_body_length). +--define(SAVE_POST, mochiweb_request_post). +--define(SAVE_COOKIE, mochiweb_request_cookie). +--define(SAVE_FORCE_CLOSE, mochiweb_request_force_close). +- +-%% @type iolist() = [iolist() | binary() | char()]. +-%% @type iodata() = binary() | iolist(). +-%% @type key() = atom() | string() | binary() +-%% @type value() = atom() | string() | binary() | integer() +-%% @type headers(). A mochiweb_headers structure. +-%% @type response(). A mochiweb_response parameterized module instance. +-%% @type ioheaders() = headers() | [{key(), value()}]. +- +-% 10 second default idle timeout +--define(IDLE_TIMEOUT, 10000). +- +-% Maximum recv_body() length of 1MB +--define(MAX_RECV_BODY, (1024*1024)). +- +-%% @spec get_header_value(K) -> undefined | Value +-%% @doc Get the value of a given request header. +-get_header_value(K) -> +- mochiweb_headers:get_value(K, Headers). +- +-get_primary_header_value(K) -> +- mochiweb_headers:get_primary_value(K, Headers). +- +-%% @type field() = socket | method | raw_path | version | headers | peer | path | body_length | range +- +-%% @spec get(field()) -> term() +-%% @doc Return the internal representation of the given field. +-get(socket) -> +- Socket; +-get(method) -> +- Method; +-get(raw_path) -> +- RawPath; +-get(version) -> +- Version; +-get(headers) -> +- Headers; +-get(peer) -> +- case inet:peername(Socket) of +- {ok, {Addr={10, _, _, _}, _Port}} -> +- case get_header_value("x-forwarded-for") of +- undefined -> +- inet_parse:ntoa(Addr); +- Hosts -> +- string:strip(lists:last(string:tokens(Hosts, ","))) +- end; +- {ok, {{127, 0, 0, 1}, _Port}} -> +- case get_header_value("x-forwarded-for") of +- undefined -> +- "127.0.0.1"; +- Hosts -> +- string:strip(lists:last(string:tokens(Hosts, ","))) +- end; +- {ok, {Addr, _Port}} -> +- inet_parse:ntoa(Addr) +- end; +-get(path) -> +- case erlang:get(?SAVE_PATH) of +- undefined -> +- {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath), +- Path = mochiweb_util:unquote(Path0), +- put(?SAVE_PATH, Path), +- Path; +- Cached -> +- Cached +- end; +-get(body_length) -> +- erlang:get(?SAVE_BODY_LENGTH); +-get(range) -> +- case get_header_value(range) of +- undefined -> +- undefined; +- RawRange -> +- parse_range_request(RawRange) +- end. +- +-%% @spec dump() -> {mochiweb_request, [{atom(), term()}]} +-%% @doc Dump the internal representation to a "human readable" set of terms +-%% for debugging/inspection purposes. +-dump() -> +- {?MODULE, [{method, Method}, +- {version, Version}, +- {raw_path, RawPath}, +- {headers, mochiweb_headers:to_list(Headers)}]}. +- +-%% @spec send(iodata()) -> ok +-%% @doc Send data over the socket. +-send(Data) -> +- case gen_tcp:send(Socket, Data) of +- ok -> +- ok; +- _ -> +- exit(normal) +- end. +- +-%% @spec recv(integer()) -> binary() +-%% @doc Receive Length bytes from the client as a binary, with the default +-%% idle timeout. +-recv(Length) -> +- recv(Length, ?IDLE_TIMEOUT). +- +-%% @spec recv(integer(), integer()) -> binary() +-%% @doc Receive Length bytes from the client as a binary, with the given +-%% Timeout in msec. +-recv(Length, Timeout) -> +- case gen_tcp:recv(Socket, Length, Timeout) of +- {ok, Data} -> +- put(?SAVE_RECV, true), +- Data; +- _ -> +- exit(normal) +- end. +- +-%% @spec body_length() -> undefined | chunked | unknown_transfer_encoding | integer() +-%% @doc Infer body length from transfer-encoding and content-length headers. +-body_length() -> +- case get_header_value("transfer-encoding") of +- undefined -> +- case get_header_value("content-length") of +- undefined -> +- undefined; +- Length -> +- list_to_integer(Length) +- end; +- "chunked" -> +- chunked; +- Unknown -> +- {unknown_transfer_encoding, Unknown} +- end. +- +- +-%% @spec recv_body() -> binary() +-%% @doc Receive the body of the HTTP request (defined by Content-Length). +-%% Will only receive up to the default max-body length of 1MB. +-recv_body() -> +- recv_body(?MAX_RECV_BODY). +- +-%% @spec recv_body(integer()) -> binary() +-%% @doc Receive the body of the HTTP request (defined by Content-Length). +-%% Will receive up to MaxBody bytes. +-recv_body(MaxBody) -> +- % we could use a sane constant for max chunk size +- Body = stream_body(?MAX_RECV_BODY, fun +- ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> +- iolist_to_binary(lists:reverse(BinAcc)); +- ({Length, Bin}, {LengthAcc, BinAcc}) -> +- NewLength = Length + LengthAcc, +- if NewLength > MaxBody -> +- exit({body_too_large, chunked}); +- true -> +- {NewLength, [Bin | BinAcc]} +- end +- end, {0, []}, MaxBody), +- put(?SAVE_BODY, Body), +- Body. +- +-stream_body(MaxChunkSize, ChunkFun, FunState) -> +- stream_body(MaxChunkSize, ChunkFun, FunState, undefined). +- +-stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) -> +- +- case get_header_value("expect") of +- "100-continue" -> +- start_raw_response({100, gb_trees:empty()}); +- _Else -> +- ok +- end, +- case body_length() of +- undefined -> +- undefined; +- {unknown_transfer_encoding, Unknown} -> +- exit({unknown_transfer_encoding, Unknown}); +- chunked -> +- % In this case the MaxBody is actually used to +- % determine the maximum allowed size of a single +- % chunk. +- stream_chunked_body(MaxChunkSize, ChunkFun, FunState); +- 0 -> +- <<>>; +- Length when is_integer(Length) -> +- case MaxBodyLength of +- MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length -> +- exit({body_too_large, content_length}); +- _ -> +- stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState) +- end; +- Length -> +- exit({length_not_integer, Length}) +- end. +- +- +-%% @spec start_response({integer(), ioheaders()}) -> response() +-%% @doc Start the HTTP response by sending the Code HTTP response and +-%% ResponseHeaders. The server will set header defaults such as Server +-%% and Date if not present in ResponseHeaders. +-start_response({Code, ResponseHeaders}) -> +- HResponse = mochiweb_headers:make(ResponseHeaders), +- HResponse1 = mochiweb_headers:default_from_list(server_headers(), +- HResponse), +- start_raw_response({Code, HResponse1}). +- +-%% @spec start_raw_response({integer(), headers()}) -> response() +-%% @doc Start the HTTP response by sending the Code HTTP response and +-%% ResponseHeaders. +-start_raw_response({Code, ResponseHeaders}) -> +- F = fun ({K, V}, Acc) -> +- [make_io(K), <<": ">>, V, <<"\r\n">> | Acc] +- end, +- End = lists:foldl(F, [<<"\r\n">>], +- mochiweb_headers:to_list(ResponseHeaders)), +- send([make_version(Version), make_code(Code), <<"\r\n">> | End]), +- mochiweb:new_response({THIS, Code, ResponseHeaders}). +- +- +-%% @spec start_response_length({integer(), ioheaders(), integer()}) -> response() +-%% @doc Start the HTTP response by sending the Code HTTP response and +-%% ResponseHeaders including a Content-Length of Length. The server +-%% will set header defaults such as Server +-%% and Date if not present in ResponseHeaders. +-start_response_length({Code, ResponseHeaders, Length}) -> +- HResponse = mochiweb_headers:make(ResponseHeaders), +- HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse), +- start_response({Code, HResponse1}). +- +-%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}) -> response() +-%% @doc Start the HTTP response with start_response, and send Body to the +-%% client (if the get(method) /= 'HEAD'). The Content-Length header +-%% will be set by the Body length, and the server will insert header +-%% defaults. +-respond({Code, ResponseHeaders, {file, IoDevice}}) -> +- Length = iodevice_size(IoDevice), +- Response = start_response_length({Code, ResponseHeaders, Length}), +- case Method of +- 'HEAD' -> +- ok; +- _ -> +- iodevice_stream(IoDevice) +- end, +- Response; +-respond({Code, ResponseHeaders, chunked}) -> +- HResponse = mochiweb_headers:make(ResponseHeaders), +- HResponse1 = case Method of +- 'HEAD' -> +- %% This is what Google does, http://www.google.com/ +- %% is chunked but HEAD gets Content-Length: 0. +- %% The RFC is ambiguous so emulating Google is smart. +- mochiweb_headers:enter("Content-Length", "0", +- HResponse); +- _ when Version >= {1, 1} -> +- %% Only use chunked encoding for HTTP/1.1 +- mochiweb_headers:enter("Transfer-Encoding", "chunked", +- HResponse); +- _ -> +- %% For pre-1.1 clients we send the data as-is +- %% without a Content-Length header and without +- %% chunk delimiters. Since the end of the document +- %% is now ambiguous we must force a close. +- put(?SAVE_FORCE_CLOSE, true), +- HResponse +- end, +- start_response({Code, HResponse1}); +-respond({Code, ResponseHeaders, Body}) -> +- Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}), +- case Method of +- 'HEAD' -> +- ok; +- _ -> +- send(Body) +- end, +- Response. +- +-%% @spec not_found() -> response() +-%% @doc Alias for not_found([]). +-not_found() -> +- not_found([]). +- +-%% @spec not_found(ExtraHeaders) -> response() +-%% @doc Alias for respond({404, [{"Content-Type", "text/plain"} +-%% | ExtraHeaders], <<"Not found.">>}). +-not_found(ExtraHeaders) -> +- respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders], +- <<"Not found.">>}). +- +-%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}) -> +-%% response() +-%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}). +-ok({ContentType, Body}) -> +- ok({ContentType, [], Body}); +-ok({ContentType, ResponseHeaders, Body}) -> +- HResponse = mochiweb_headers:make(ResponseHeaders), +- case THIS:get(range) of +- X when X =:= undefined; X =:= fail -> +- HResponse1 = mochiweb_headers:enter("Content-Type", ContentType, HResponse), +- respond({200, HResponse1, Body}); +- Ranges -> +- {PartList, Size} = range_parts(Body, Ranges), +- case PartList of +- [] -> %% no valid ranges +- HResponse1 = mochiweb_headers:enter("Content-Type", +- ContentType, +- HResponse), +- %% could be 416, for now we'll just return 200 +- respond({200, HResponse1, Body}); +- PartList -> +- {RangeHeaders, RangeBody} = +- parts_to_body(PartList, ContentType, Size), +- HResponse1 = mochiweb_headers:enter_from_list( +- [{"Accept-Ranges", "bytes"} | +- RangeHeaders], +- HResponse), +- respond({206, HResponse1, RangeBody}) +- end +- end. +- +-%% @spec should_close() -> bool() +-%% @doc Return true if the connection must be closed. If false, using +-%% Keep-Alive should be safe. +-should_close() -> +- ForceClose = erlang:get(mochiweb_request_force_close) =/= undefined, +- DidNotRecv = erlang:get(mochiweb_request_recv) =:= undefined, +- ForceClose orelse Version < {1, 0} +- %% Connection: close +- orelse get_header_value("connection") =:= "close" +- %% HTTP 1.0 requires Connection: Keep-Alive +- orelse (Version =:= {1, 0} +- andalso get_header_value("connection") =/= "Keep-Alive") +- %% unread data left on the socket, can't safely continue +- orelse (DidNotRecv +- andalso get_header_value("content-length") =/= undefined +- andalso list_to_integer(get_header_value("content-length")) > 0) +- orelse (DidNotRecv +- andalso get_header_value("transfer-encoding") =:= "chunked"). +- +-%% @spec cleanup() -> ok +-%% @doc Clean up any junk in the process dictionary, required before continuing +-%% a Keep-Alive request. +-cleanup() -> +- [erase(K) || K <- [?SAVE_QS, +- ?SAVE_PATH, +- ?SAVE_RECV, +- ?SAVE_BODY, +- ?SAVE_POST, +- ?SAVE_COOKIE, +- ?SAVE_FORCE_CLOSE]], +- ok. +- +-%% @spec parse_qs() -> [{Key::string(), Value::string()}] +-%% @doc Parse the query string of the URL. +-parse_qs() -> +- case erlang:get(?SAVE_QS) of +- undefined -> +- {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath), +- Parsed = mochiweb_util:parse_qs(QueryString), +- put(?SAVE_QS, Parsed), +- Parsed; +- Cached -> +- Cached +- end. +- +-%% @spec get_cookie_value(Key::string) -> string() | undefined +-%% @doc Get the value of the given cookie. +-get_cookie_value(Key) -> +- proplists:get_value(Key, parse_cookie()). +- +-%% @spec parse_cookie() -> [{Key::string(), Value::string()}] +-%% @doc Parse the cookie header. +-parse_cookie() -> +- case erlang:get(?SAVE_COOKIE) of +- undefined -> +- Cookies = case get_header_value("cookie") of +- undefined -> +- []; +- Value -> +- mochiweb_cookies:parse_cookie(Value) +- end, +- put(?SAVE_COOKIE, Cookies), +- Cookies; +- Cached -> +- Cached +- end. +- +-%% @spec parse_post() -> [{Key::string(), Value::string()}] +-%% @doc Parse an application/x-www-form-urlencoded form POST. This +-%% has the side-effect of calling recv_body(). +-parse_post() -> +- case erlang:get(?SAVE_POST) of +- undefined -> +- Parsed = case recv_body() of +- undefined -> +- []; +- Binary -> +- case get_primary_header_value("content-type") of +- "application/x-www-form-urlencoded" ++ _ -> +- mochiweb_util:parse_qs(Binary); +- _ -> +- [] +- end +- end, +- put(?SAVE_POST, Parsed), +- Parsed; +- Cached -> +- Cached +- end. +- +-%% @spec stream_chunked_body(integer(), fun(), term()) -> term() +-%% @doc The function is called for each chunk. +-%% Used internally by read_chunked_body. +-stream_chunked_body(MaxChunkSize, Fun, FunState) -> +- case read_chunk_length() of +- 0 -> +- Fun({0, read_chunk(0)}, FunState); +- Length when Length > MaxChunkSize -> +- NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState), +- stream_chunked_body(MaxChunkSize, Fun, NewState); +- Length -> +- NewState = Fun({Length, read_chunk(Length)}, FunState), +- stream_chunked_body(MaxChunkSize, Fun, NewState) +- end. +- +-stream_unchunked_body(0, _MaxChunkSize, Fun, FunState) -> +- Fun({0, <<>>}, FunState); +-stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize -> +- Bin = recv(MaxChunkSize), +- NewState = Fun({MaxChunkSize, Bin}, FunState), +- stream_unchunked_body(Length - MaxChunkSize, MaxChunkSize, Fun, NewState); +-stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) -> +- Bin = recv(Length), +- NewState = Fun({Length, Bin}, FunState), +- stream_unchunked_body(0, MaxChunkSize, Fun, NewState). +- +- +-%% @spec read_chunk_length() -> integer() +-%% @doc Read the length of the next HTTP chunk. +-read_chunk_length() -> +- inet:setopts(Socket, [{packet, line}]), +- case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of +- {ok, Header} -> +- inet:setopts(Socket, [{packet, raw}]), +- Splitter = fun (C) -> +- C =/= $\r andalso C =/= $\n andalso C =/= $ +- end, +- {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)), +- mochihex:to_int(Hex); +- _ -> +- exit(normal) +- end. +- +-%% @spec read_chunk(integer()) -> Chunk::binary() | [Footer::binary()] +-%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the +-%% HTTP footers (as a list of binaries, since they're nominal). +-read_chunk(0) -> +- inet:setopts(Socket, [{packet, line}]), +- F = fun (F1, Acc) -> +- case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of +- {ok, <<"\r\n">>} -> +- Acc; +- {ok, Footer} -> +- F1(F1, [Footer | Acc]); +- _ -> +- exit(normal) +- end +- end, +- Footers = F(F, []), +- inet:setopts(Socket, [{packet, raw}]), +- Footers; +-read_chunk(Length) -> +- case gen_tcp:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of +- {ok, <>} -> +- Chunk; +- _ -> +- exit(normal) +- end. +- +-read_sub_chunks(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize -> +- Bin = recv(MaxChunkSize), +- NewState = Fun({size(Bin), Bin}, FunState), +- read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState); +- +-read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) -> +- Fun({Length, read_chunk(Length)}, FunState). +- +-%% @spec serve_file(Path, DocRoot) -> Response +-%% @doc Serve a file relative to DocRoot. +-serve_file(Path, DocRoot) -> +- serve_file(Path, DocRoot, []). +- +-%% @spec serve_file(Path, DocRoot, ExtraHeaders) -> Response +-%% @doc Serve a file relative to DocRoot. +-serve_file(Path, DocRoot, ExtraHeaders) -> +- case mochiweb_util:safe_relative_path(Path) of +- undefined -> +- not_found(ExtraHeaders); +- RelPath -> +- FullPath = filename:join([DocRoot, RelPath]), +- File = case filelib:is_dir(FullPath) of +- true -> +- filename:join([FullPath, "index.html"]); +- false -> +- FullPath +- end, +- case file:read_file_info(File) of +- {ok, FileInfo} -> +- LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime), +- case get_header_value("if-modified-since") of +- LastModified -> +- respond({304, ExtraHeaders, ""}); +- _ -> +- case file:open(File, [raw, binary]) of +- {ok, IoDevice} -> +- ContentType = mochiweb_util:guess_mime(File), +- Res = ok({ContentType, +- [{"last-modified", LastModified} +- | ExtraHeaders], +- {file, IoDevice}}), +- file:close(IoDevice), +- Res; +- _ -> +- not_found(ExtraHeaders) +- end +- end; +- {error, _} -> +- not_found(ExtraHeaders) +- end +- end. +- +- +-%% Internal API +- +-server_headers() -> +- [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"}, +- {"Date", httpd_util:rfc1123_date()}]. +- +-make_io(Atom) when is_atom(Atom) -> +- atom_to_list(Atom); +-make_io(Integer) when is_integer(Integer) -> +- integer_to_list(Integer); +-make_io(Io) when is_list(Io); is_binary(Io) -> +- Io. +- +-make_code(X) when is_integer(X) -> +- [integer_to_list(X), [" " | httpd_util:reason_phrase(X)]]; +-make_code(Io) when is_list(Io); is_binary(Io) -> +- Io. +- +-make_version({1, 0}) -> +- <<"HTTP/1.0 ">>; +-make_version(_) -> +- <<"HTTP/1.1 ">>. +- +-iodevice_stream(IoDevice) -> +- case file:read(IoDevice, ?READ_SIZE) of +- eof -> +- ok; +- {ok, Data} -> +- ok = send(Data), +- iodevice_stream(IoDevice) +- end. +- +- +-parts_to_body([{Start, End, Body}], ContentType, Size) -> +- %% return body for a range reponse with a single body +- HeaderList = [{"Content-Type", ContentType}, +- {"Content-Range", +- ["bytes ", +- make_io(Start), "-", make_io(End), +- "/", make_io(Size)]}], +- {HeaderList, Body}; +-parts_to_body(BodyList, ContentType, Size) when is_list(BodyList) -> +- %% return +- %% header Content-Type: multipart/byteranges; boundary=441934886133bdee4 +- %% and multipart body +- Boundary = mochihex:to_hex(crypto:rand_bytes(8)), +- HeaderList = [{"Content-Type", +- ["multipart/byteranges; ", +- "boundary=", Boundary]}], +- MultiPartBody = multipart_body(BodyList, ContentType, Boundary, Size), +- +- {HeaderList, MultiPartBody}. +- +-multipart_body([], _ContentType, Boundary, _Size) -> +- ["--", Boundary, "--\r\n"]; +-multipart_body([{Start, End, Body} | BodyList], ContentType, Boundary, Size) -> +- ["--", Boundary, "\r\n", +- "Content-Type: ", ContentType, "\r\n", +- "Content-Range: ", +- "bytes ", make_io(Start), "-", make_io(End), +- "/", make_io(Size), "\r\n\r\n", +- Body, "\r\n" +- | multipart_body(BodyList, ContentType, Boundary, Size)]. +- +-iodevice_size(IoDevice) -> +- {ok, Size} = file:position(IoDevice, eof), +- {ok, 0} = file:position(IoDevice, bof), +- Size. +- +-range_parts({file, IoDevice}, Ranges) -> +- Size = iodevice_size(IoDevice), +- F = fun (Spec, Acc) -> +- case range_skip_length(Spec, Size) of +- invalid_range -> +- Acc; +- V -> +- [V | Acc] +- end +- end, +- LocNums = lists:foldr(F, [], Ranges), +- {ok, Data} = file:pread(IoDevice, LocNums), +- Bodies = lists:zipwith(fun ({Skip, Length}, PartialBody) -> +- {Skip, Skip + Length - 1, PartialBody} +- end, +- LocNums, Data), +- {Bodies, Size}; +- +-range_parts(Body0, Ranges) -> +- Body = iolist_to_binary(Body0), +- Size = size(Body), +- F = fun(Spec, Acc) -> +- case range_skip_length(Spec, Size) of +- invalid_range -> +- Acc; +- {Skip, Length} -> +- <<_:Skip/binary, PartialBody:Length/binary, _/binary>> = Body, +- [{Skip, Skip + Length - 1, PartialBody} | Acc] +- end +- end, +- {lists:foldr(F, [], Ranges), Size}. +- +-range_skip_length(Spec, Size) -> +- case Spec of +- {none, R} when R =< Size, R >= 0 -> +- {Size - R, R}; +- {none, _OutOfRange} -> +- {0, Size}; +- {R, none} when R >= 0, R < Size -> +- {R, Size - R}; +- {_OutOfRange, none} -> +- invalid_range; +- {Start, End} when 0 =< Start, Start =< End, End < Size -> +- {Start, End - Start + 1}; +- {_OutOfRange, _End} -> +- invalid_range +- end. +- +-parse_range_request(RawRange) when is_list(RawRange) -> +- try +- "bytes=" ++ RangeString = RawRange, +- Ranges = string:tokens(RangeString, ","), +- lists:map(fun ("-" ++ V) -> +- {none, list_to_integer(V)}; +- (R) -> +- case string:tokens(R, "-") of +- [S1, S2] -> +- {list_to_integer(S1), list_to_integer(S2)}; +- [S] -> +- {list_to_integer(S), none} +- end +- end, +- Ranges) +- catch +- _:_ -> +- fail +- end. +- +- +-test() -> +- ok = test_range(), +- ok. +- +-test_range() -> +- %% valid, single ranges +- io:format("Testing parse_range_request with valid single ranges~n"), +- io:format("1"), +- [{20, 30}] = parse_range_request("bytes=20-30"), +- io:format("2"), +- [{20, none}] = parse_range_request("bytes=20-"), +- io:format("3"), +- [{none, 20}] = parse_range_request("bytes=-20"), +- io:format(".. ok ~n"), +- +- +- %% invalid, single ranges +- io:format("Testing parse_range_request with invalid ranges~n"), +- io:format("1"), +- fail = parse_range_request(""), +- io:format("2"), +- fail = parse_range_request("garbage"), +- io:format("3"), +- fail = parse_range_request("bytes=-20-30"), +- io:format(".. ok ~n"), +- +- %% valid, multiple range +- io:format("Testing parse_range_request with valid multiple ranges~n"), +- io:format("1"), +- [{20, 30}, {50, 100}, {110, 200}] = +- parse_range_request("bytes=20-30,50-100,110-200"), +- io:format("2"), +- [{20, none}, {50, 100}, {none, 200}] = +- parse_range_request("bytes=20-,50-100,-200"), +- io:format(".. ok~n"), +- +- %% no ranges +- io:format("Testing out parse_range_request with no ranges~n"), +- io:format("1"), +- [] = parse_range_request("bytes="), +- io:format(".. ok~n"), +- +- Body = <<"012345678901234567890123456789012345678901234567890123456789">>, +- BodySize = size(Body), %% 60 +- BodySize = 60, +- +- %% these values assume BodySize =:= 60 +- io:format("Testing out range_skip_length on valid ranges~n"), +- io:format("1"), +- {1,9} = range_skip_length({1,9}, BodySize), %% 1-9 +- io:format("2"), +- {10,10} = range_skip_length({10,19}, BodySize), %% 10-19 +- io:format("3"), +- {40, 20} = range_skip_length({none, 20}, BodySize), %% -20 +- io:format("4"), +- {30, 30} = range_skip_length({30, none}, BodySize), %% 30- +- io:format(".. ok ~n"), +- +- %% valid edge cases for range_skip_length +- io:format("Testing out range_skip_length on valid edge case ranges~n"), +- io:format("1"), +- {BodySize, 0} = range_skip_length({none, 0}, BodySize), +- io:format("2"), +- {0, BodySize} = range_skip_length({none, BodySize}, BodySize), +- io:format("3"), +- {0, BodySize} = range_skip_length({0, none}, BodySize), +- BodySizeLess1 = BodySize - 1, +- io:format("4"), +- {BodySizeLess1, 1} = range_skip_length({BodySize - 1, none}, BodySize), +- +- %% out of range, return whole thing +- io:format("5"), +- {0, BodySize} = range_skip_length({none, BodySize + 1}, BodySize), +- io:format("6"), +- {0, BodySize} = range_skip_length({none, -1}, BodySize), +- io:format(".. ok ~n"), +- +- %% invalid ranges +- io:format("Testing out range_skip_length on invalid ranges~n"), +- io:format("1"), +- invalid_range = range_skip_length({-1, 30}, BodySize), +- io:format("2"), +- invalid_range = range_skip_length({0, BodySize + 1}, BodySize), +- io:format("3"), +- invalid_range = range_skip_length({-1, BodySize + 1}, BodySize), +- io:format("4"), +- invalid_range = range_skip_length({BodySize, 40}, BodySize), +- io:format("5"), +- invalid_range = range_skip_length({-1, none}, BodySize), +- io:format("6"), +- invalid_range = range_skip_length({BodySize, none}, BodySize), +- io:format(".. ok ~n"), +- ok. +- +diff --git a/src/mochiweb/mochiweb_response.erl b/src/mochiweb/mochiweb_response.erl +deleted file mode 100644 +index 6285c4c..0000000 +--- a/src/mochiweb/mochiweb_response.erl ++++ /dev/null +@@ -1,56 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Response abstraction. +- +--module(mochiweb_response, [Request, Code, Headers]). +--author('bob@mochimedia.com'). +- +--define(QUIP, "Any of you quaids got a smint?"). +- +--export([get_header_value/1, get/1, dump/0]). +--export([send/1, write_chunk/1]). +- +-%% @spec get_header_value(string() | atom() | binary()) -> string() | undefined +-%% @doc Get the value of the given response header. +-get_header_value(K) -> +- mochiweb_headers:get_value(K, Headers). +- +-%% @spec get(request | code | headers) -> term() +-%% @doc Return the internal representation of the given field. +-get(request) -> +- Request; +-get(code) -> +- Code; +-get(headers) -> +- Headers. +- +-%% @spec dump() -> {mochiweb_request, [{atom(), term()}]} +-%% @doc Dump the internal representation to a "human readable" set of terms +-%% for debugging/inspection purposes. +-dump() -> +- [{request, Request:dump()}, +- {code, Code}, +- {headers, mochiweb_headers:to_list(Headers)}]. +- +-%% @spec send(iodata()) -> ok +-%% @doc Send data over the socket if the method is not HEAD. +-send(Data) -> +- case Request:get(method) of +- 'HEAD' -> +- ok; +- _ -> +- Request:send(Data) +- end. +- +-%% @spec write_chunk(iodata()) -> ok +-%% @doc Write a chunk of a HTTP chunked response. If Data is zero length, +-%% then the chunked response will be finished. +-write_chunk(Data) -> +- case Request:get(version) of +- Version when Version >= {1, 1} -> +- Length = iolist_size(Data), +- send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>]); +- _ -> +- send(Data) +- end. +diff --git a/src/mochiweb/mochiweb_skel.erl b/src/mochiweb/mochiweb_skel.erl +deleted file mode 100644 +index 098951b..0000000 +--- a/src/mochiweb/mochiweb_skel.erl ++++ /dev/null +@@ -1,71 +0,0 @@ +--module(mochiweb_skel). +--export([skelcopy/2]). +- +--include_lib("kernel/include/file.hrl"). +- +-%% External API +- +-skelcopy(DestDir, Name) -> +- ok = ensuredir(DestDir), +- LDst = case length(filename:dirname(DestDir)) of +- 1 -> %% handle case when dirname returns "/" +- 0; +- N -> +- N + 1 +- end, +- skelcopy(src(), DestDir, Name, LDst), +- ok = file:make_symlink( +- filename:join(filename:dirname(code:which(?MODULE)), ".."), +- filename:join([DestDir, Name, "deps", "mochiweb-src"])). +- +- +-%% Internal API +- +-src() -> +- Dir = filename:dirname(code:which(?MODULE)), +- filename:join(Dir, "../priv/skel"). +- +-skel() -> +- "skel". +- +-skelcopy(Src, DestDir, Name, LDst) -> +- {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name), +- case file:read_file_info(Src) of +- {ok, #file_info{type=directory, mode=Mode}} -> +- Dir = DestDir ++ "/" ++ Dest, +- EDst = lists:nthtail(LDst, Dir), +- ok = ensuredir(Dir), +- ok = file:write_file_info(Dir, #file_info{mode=Mode}), +- {ok, Files} = file:list_dir(Src), +- io:format("~s/~n", [EDst]), +- lists:foreach(fun ("." ++ _) -> ok; +- (F) -> +- skelcopy(filename:join(Src, F), +- Dir, +- Name, +- LDst) +- end, +- Files), +- ok; +- {ok, #file_info{type=regular, mode=Mode}} -> +- OutFile = filename:join(DestDir, Dest), +- {ok, B} = file:read_file(Src), +- {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name), +- ok = file:write_file(OutFile, list_to_binary(S)), +- ok = file:write_file_info(OutFile, #file_info{mode=Mode}), +- io:format(" ~s~n", [filename:basename(Src)]), +- ok; +- {ok, _} -> +- io:format("ignored source file: ~p~n", [Src]), +- ok +- end. +- +-ensuredir(Dir) -> +- case file:make_dir(Dir) of +- ok -> +- ok; +- {error, eexist} -> +- ok; +- E -> +- E +- end. +diff --git a/src/mochiweb/mochiweb_socket_server.erl b/src/mochiweb/mochiweb_socket_server.erl +deleted file mode 100644 +index a483c3d..0000000 +--- a/src/mochiweb/mochiweb_socket_server.erl ++++ /dev/null +@@ -1,248 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc MochiWeb socket server. +- +--module(mochiweb_socket_server). +--author('bob@mochimedia.com'). +--behaviour(gen_server). +- +--export([start/1, stop/1]). +--export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3, +- handle_info/2]). +--export([get/2]). +- +--export([acceptor_loop/1]). +- +--record(mochiweb_socket_server, +- {port, +- loop, +- name=undefined, +- max=2048, +- ip=any, +- listen=null, +- acceptor=null, +- backlog=30}). +- +-start(State=#mochiweb_socket_server{}) -> +- start_server(State); +-start(Options) -> +- start(parse_options(Options)). +- +-get(Name, Property) -> +- gen_server:call(Name, {get, Property}). +- +-stop(Name) when is_atom(Name) -> +- gen_server:cast(Name, stop); +-stop(Pid) when is_pid(Pid) -> +- gen_server:cast(Pid, stop); +-stop({local, Name}) -> +- stop(Name); +-stop({global, Name}) -> +- stop(Name); +-stop(Options) -> +- State = parse_options(Options), +- stop(State#mochiweb_socket_server.name). +- +-%% Internal API +- +-parse_options(Options) -> +- parse_options(Options, #mochiweb_socket_server{}). +- +-parse_options([], State) -> +- State; +-parse_options([{name, L} | Rest], State) when is_list(L) -> +- Name = {local, list_to_atom(L)}, +- parse_options(Rest, State#mochiweb_socket_server{name=Name}); +-parse_options([{name, A} | Rest], State) when is_atom(A) -> +- Name = {local, A}, +- parse_options(Rest, State#mochiweb_socket_server{name=Name}); +-parse_options([{name, Name} | Rest], State) -> +- parse_options(Rest, State#mochiweb_socket_server{name=Name}); +-parse_options([{port, L} | Rest], State) when is_list(L) -> +- Port = list_to_integer(L), +- parse_options(Rest, State#mochiweb_socket_server{port=Port}); +-parse_options([{port, Port} | Rest], State) -> +- parse_options(Rest, State#mochiweb_socket_server{port=Port}); +-parse_options([{ip, Ip} | Rest], State) -> +- ParsedIp = case Ip of +- any -> +- any; +- Ip when is_tuple(Ip) -> +- Ip; +- Ip when is_list(Ip) -> +- {ok, IpTuple} = inet_parse:address(Ip), +- IpTuple +- end, +- parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp}); +-parse_options([{loop, Loop} | Rest], State) -> +- parse_options(Rest, State#mochiweb_socket_server{loop=Loop}); +-parse_options([{backlog, Backlog} | Rest], State) -> +- parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog}); +-parse_options([{max, Max} | Rest], State) -> +- MaxInt = case Max of +- Max when is_list(Max) -> +- list_to_integer(Max); +- Max when is_integer(Max) -> +- Max +- end, +- parse_options(Rest, State#mochiweb_socket_server{max=MaxInt}). +- +-start_server(State=#mochiweb_socket_server{name=Name}) -> +- case Name of +- undefined -> +- gen_server:start_link(?MODULE, State, []); +- _ -> +- gen_server:start_link(Name, ?MODULE, State, []) +- end. +- +-ipv6_supported() -> +- case (catch inet:getaddr("localhost", inet6)) of +- {ok, _Addr} -> +- true; +- {error, _} -> +- false +- end. +- +-init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog}) -> +- process_flag(trap_exit, true), +- BaseOpts = [binary, +- {reuseaddr, true}, +- {packet, 0}, +- {backlog, Backlog}, +- {recbuf, 8192}, +- {active, false}, +- {nodelay, true}], +- Opts = case Ip of +- any -> +- case ipv6_supported() of % IPv4, and IPv6 if supported +- true -> [inet, inet6 | BaseOpts]; +- _ -> BaseOpts +- end; +- {_, _, _, _} -> % IPv4 +- [inet, {ip, Ip} | BaseOpts]; +- {_, _, _, _, _, _, _, _} -> % IPv6 +- [inet6, {ip, Ip} | BaseOpts] +- end, +- case gen_tcp_listen(Port, Opts, State) of +- {stop, eacces} -> +- case Port < 1024 of +- true -> +- case fdsrv:start() of +- {ok, _} -> +- case fdsrv:bind_socket(tcp, Port) of +- {ok, Fd} -> +- gen_tcp_listen(Port, [{fd, Fd} | Opts], State); +- _ -> +- {stop, fdsrv_bind_failed} +- end; +- _ -> +- {stop, fdsrv_start_failed} +- end; +- false -> +- {stop, eacces} +- end; +- Other -> +- Other +- end. +- +-gen_tcp_listen(Port, Opts, State) -> +- case gen_tcp:listen(Port, Opts) of +- {ok, Listen} -> +- {ok, ListenPort} = inet:port(Listen), +- {ok, new_acceptor(State#mochiweb_socket_server{listen=Listen, +- port=ListenPort})}; +- {error, Reason} -> +- {stop, Reason} +- end. +- +-new_acceptor(State=#mochiweb_socket_server{max=0}) -> +- io:format("Not accepting new connections~n"), +- State#mochiweb_socket_server{acceptor=null}; +-new_acceptor(State=#mochiweb_socket_server{listen=Listen,loop=Loop}) -> +- Pid = proc_lib:spawn_link(?MODULE, acceptor_loop, +- [{self(), Listen, Loop}]), +- State#mochiweb_socket_server{acceptor=Pid}. +- +-call_loop({M, F}, Socket) -> +- M:F(Socket); +-call_loop(Loop, Socket) -> +- Loop(Socket). +- +-acceptor_loop({Server, Listen, Loop}) -> +- case catch gen_tcp:accept(Listen) of +- {ok, Socket} -> +- gen_server:cast(Server, {accepted, self()}), +- call_loop(Loop, Socket); +- {error, closed} -> +- exit({error, closed}); +- Other -> +- error_logger:error_report( +- [{application, mochiweb}, +- "Accept failed error", +- lists:flatten(io_lib:format("~p", [Other]))]), +- exit({error, accept_failed}) +- end. +- +- +-do_get(port, #mochiweb_socket_server{port=Port}) -> +- Port. +- +-handle_call({get, Property}, _From, State) -> +- Res = do_get(Property, State), +- {reply, Res, State}; +-handle_call(_Message, _From, State) -> +- Res = error, +- {reply, Res, State}. +- +-handle_cast({accepted, Pid}, +- State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> +- % io:format("accepted ~p~n", [Pid]), +- State1 = State#mochiweb_socket_server{max=Max - 1}, +- {noreply, new_acceptor(State1)}; +-handle_cast(stop, State) -> +- {stop, normal, State}. +- +-terminate(_Reason, #mochiweb_socket_server{listen=Listen, port=Port}) -> +- gen_tcp:close(Listen), +- case Port < 1024 of +- true -> +- catch fdsrv:stop(), +- ok; +- false -> +- ok +- end. +- +-code_change(_OldVsn, State, _Extra) -> +- State. +- +-handle_info({'EXIT', Pid, normal}, +- State=#mochiweb_socket_server{acceptor=Pid}) -> +- % io:format("normal acceptor down~n"), +- {noreply, new_acceptor(State)}; +-handle_info({'EXIT', Pid, Reason}, +- State=#mochiweb_socket_server{acceptor=Pid}) -> +- error_logger:error_report({?MODULE, ?LINE, +- {acceptor_error, Reason}}), +- timer:sleep(100), +- {noreply, new_acceptor(State)}; +-handle_info({'EXIT', _LoopPid, Reason}, +- State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> +- case Reason of +- normal -> +- ok; +- _ -> +- error_logger:error_report({?MODULE, ?LINE, +- {child_error, Reason}}) +- end, +- State1 = State#mochiweb_socket_server{max=Max + 1}, +- State2 = case Pid of +- null -> +- new_acceptor(State1); +- _ -> +- State1 +- end, +- {noreply, State2}; +-handle_info(Info, State) -> +- error_logger:info_report([{'INFO', Info}, {'State', State}]), +- {noreply, State}. +diff --git a/src/mochiweb/mochiweb_sup.erl b/src/mochiweb/mochiweb_sup.erl +deleted file mode 100644 +index 5cb525b..0000000 +--- a/src/mochiweb/mochiweb_sup.erl ++++ /dev/null +@@ -1,34 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Supervisor for the mochiweb application. +- +--module(mochiweb_sup). +--author('bob@mochimedia.com'). +- +--behaviour(supervisor). +- +-%% External exports +--export([start_link/0, upgrade/0]). +- +-%% supervisor callbacks +--export([init/1]). +- +-%% @spec start_link() -> ServerRet +-%% @doc API for starting the supervisor. +-start_link() -> +- supervisor:start_link({local, ?MODULE}, ?MODULE, []). +- +-%% @spec upgrade() -> ok +-%% @doc Add processes if necessary. +-upgrade() -> +- {ok, {_, Specs}} = init([]), +- [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], +- ok. +- +-%% @spec init([]) -> SupervisorTree +-%% @doc supervisor callback, ensures yaws is in embedded mode and then +-%% returns the supervisor tree. +-init([]) -> +- Processes = [], +- {ok, {{one_for_one, 10, 10}, Processes}}. +diff --git a/src/mochiweb/mochiweb_util.erl b/src/mochiweb/mochiweb_util.erl +deleted file mode 100644 +index 7bf18d1..0000000 +--- a/src/mochiweb/mochiweb_util.erl ++++ /dev/null +@@ -1,579 +0,0 @@ +-%% @author Bob Ippolito +-%% @copyright 2007 Mochi Media, Inc. +- +-%% @doc Utilities for parsing and quoting. +- +--module(mochiweb_util). +--author('bob@mochimedia.com'). +--export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1]). +--export([path_split/1]). +--export([urlsplit/1, urlsplit_path/1, urlunsplit/1, urlunsplit_path/1]). +--export([guess_mime/1, parse_header/1]). +--export([shell_quote/1, cmd/1, cmd_string/1, cmd_port/2]). +--export([record_to_proplist/2, record_to_proplist/3]). +--export([safe_relative_path/1, partition/2]). +--export([test/0]). +- +--define(PERCENT, 37). % $\% +--define(FULLSTOP, 46). % $\. +--define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse +- (C >= $a andalso C =< $f) orelse +- (C >= $A andalso C =< $F))). +--define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse +- (C >= $A andalso C =< $Z) orelse +- (C >= $0 andalso C =< $9) orelse +- (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse +- C =:= $_))). +- +-hexdigit(C) when C < 10 -> $0 + C; +-hexdigit(C) when C < 16 -> $A + (C - 10). +- +-unhexdigit(C) when C >= $0, C =< $9 -> C - $0; +-unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10; +-unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10. +- +-%% @spec partition(String, Sep) -> {String, [], []} | {Prefix, Sep, Postfix} +-%% @doc Inspired by Python 2.5's str.partition: +-%% partition("foo/bar", "/") = {"foo", "/", "bar"}, +-%% partition("foo", "/") = {"foo", "", ""}. +-partition(String, Sep) -> +- case partition(String, Sep, []) of +- undefined -> +- {String, "", ""}; +- Result -> +- Result +- end. +- +-partition("", _Sep, _Acc) -> +- undefined; +-partition(S, Sep, Acc) -> +- case partition2(S, Sep) of +- undefined -> +- [C | Rest] = S, +- partition(Rest, Sep, [C | Acc]); +- Rest -> +- {lists:reverse(Acc), Sep, Rest} +- end. +- +-partition2(Rest, "") -> +- Rest; +-partition2([C | R1], [C | R2]) -> +- partition2(R1, R2); +-partition2(_S, _Sep) -> +- undefined. +- +- +- +-%% @spec safe_relative_path(string()) -> string() | undefined +-%% @doc Return the reduced version of a relative path or undefined if it +-%% is not safe. safe relative paths can be joined with an absolute path +-%% and will result in a subdirectory of the absolute path. +-safe_relative_path("/" ++ _) -> +- undefined; +-safe_relative_path(P) -> +- safe_relative_path(P, []). +- +-safe_relative_path("", Acc) -> +- case Acc of +- [] -> +- ""; +- _ -> +- join(lists:reverse(Acc), "/") +- end; +-safe_relative_path(P, Acc) -> +- case partition(P, "/") of +- {"", "/", _} -> +- %% /foo or foo//bar +- undefined; +- {"..", _, _} when Acc =:= [] -> +- undefined; +- {"..", _, Rest} -> +- safe_relative_path(Rest, tl(Acc)); +- {Part, "/", ""} -> +- safe_relative_path("", ["", Part | Acc]); +- {Part, _, Rest} -> +- safe_relative_path(Rest, [Part | Acc]) +- end. +- +-%% @spec shell_quote(string()) -> string() +-%% @doc Quote a string according to UNIX shell quoting rules, returns a string +-%% surrounded by double quotes. +-shell_quote(L) -> +- shell_quote(L, [$\"]). +- +-%% @spec cmd_port([string()], Options) -> port() +-%% @doc open_port({spawn, mochiweb_util:cmd_string(Argv)}, Options). +-cmd_port(Argv, Options) -> +- open_port({spawn, cmd_string(Argv)}, Options). +- +-%% @spec cmd([string()]) -> string() +-%% @doc os:cmd(cmd_string(Argv)). +-cmd(Argv) -> +- os:cmd(cmd_string(Argv)). +- +-%% @spec cmd_string([string()]) -> string() +-%% @doc Create a shell quoted command string from a list of arguments. +-cmd_string(Argv) -> +- join([shell_quote(X) || X <- Argv], " "). +- +-%% @spec join([string()], Separator) -> string() +-%% @doc Join a list of strings together with the given separator +-%% string or char. +-join([], _Separator) -> +- []; +-join([S], _Separator) -> +- lists:flatten(S); +-join(Strings, Separator) -> +- lists:flatten(revjoin(lists:reverse(Strings), Separator, [])). +- +-revjoin([], _Separator, Acc) -> +- Acc; +-revjoin([S | Rest], Separator, []) -> +- revjoin(Rest, Separator, [S]); +-revjoin([S | Rest], Separator, Acc) -> +- revjoin(Rest, Separator, [S, Separator | Acc]). +- +-%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string() +-%% @doc URL safe encoding of the given term. +-quote_plus(Atom) when is_atom(Atom) -> +- quote_plus(atom_to_list(Atom)); +-quote_plus(Int) when is_integer(Int) -> +- quote_plus(integer_to_list(Int)); +-quote_plus(Binary) when is_binary(Binary) -> +- quote_plus(binary_to_list(Binary)); +-quote_plus(Float) when is_float(Float) -> +- quote_plus(mochinum:digits(Float)); +-quote_plus(String) -> +- quote_plus(String, []). +- +-quote_plus([], Acc) -> +- lists:reverse(Acc); +-quote_plus([C | Rest], Acc) when ?QS_SAFE(C) -> +- quote_plus(Rest, [C | Acc]); +-quote_plus([$\s | Rest], Acc) -> +- quote_plus(Rest, [$+ | Acc]); +-quote_plus([C | Rest], Acc) -> +- <> = <>, +- quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]). +- +-%% @spec urlencode([{Key, Value}]) -> string() +-%% @doc URL encode the property list. +-urlencode(Props) -> +- RevPairs = lists:foldl(fun ({K, V}, Acc) -> +- [[quote_plus(K), $=, quote_plus(V)] | Acc] +- end, [], Props), +- lists:flatten(revjoin(RevPairs, $&, [])). +- +-%% @spec parse_qs(string() | binary()) -> [{Key, Value}] +-%% @doc Parse a query string or application/x-www-form-urlencoded. +-parse_qs(Binary) when is_binary(Binary) -> +- parse_qs(binary_to_list(Binary)); +-parse_qs(String) -> +- parse_qs(String, []). +- +-parse_qs([], Acc) -> +- lists:reverse(Acc); +-parse_qs(String, Acc) -> +- {Key, Rest} = parse_qs_key(String), +- {Value, Rest1} = parse_qs_value(Rest), +- parse_qs(Rest1, [{Key, Value} | Acc]). +- +-parse_qs_key(String) -> +- parse_qs_key(String, []). +- +-parse_qs_key([], Acc) -> +- {qs_revdecode(Acc), ""}; +-parse_qs_key([$= | Rest], Acc) -> +- {qs_revdecode(Acc), Rest}; +-parse_qs_key(Rest=[$; | _], Acc) -> +- {qs_revdecode(Acc), Rest}; +-parse_qs_key(Rest=[$& | _], Acc) -> +- {qs_revdecode(Acc), Rest}; +-parse_qs_key([C | Rest], Acc) -> +- parse_qs_key(Rest, [C | Acc]). +- +-parse_qs_value(String) -> +- parse_qs_value(String, []). +- +-parse_qs_value([], Acc) -> +- {qs_revdecode(Acc), ""}; +-parse_qs_value([$; | Rest], Acc) -> +- {qs_revdecode(Acc), Rest}; +-parse_qs_value([$& | Rest], Acc) -> +- {qs_revdecode(Acc), Rest}; +-parse_qs_value([C | Rest], Acc) -> +- parse_qs_value(Rest, [C | Acc]). +- +-%% @spec unquote(string() | binary()) -> string() +-%% @doc Unquote a URL encoded string. +-unquote(Binary) when is_binary(Binary) -> +- unquote(binary_to_list(Binary)); +-unquote(String) -> +- qs_revdecode(lists:reverse(String)). +- +-qs_revdecode(S) -> +- qs_revdecode(S, []). +- +-qs_revdecode([], Acc) -> +- Acc; +-qs_revdecode([$+ | Rest], Acc) -> +- qs_revdecode(Rest, [$\s | Acc]); +-qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) -> +- qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]); +-qs_revdecode([C | Rest], Acc) -> +- qs_revdecode(Rest, [C | Acc]). +- +-%% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment} +-%% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style +-%% URLs. +-urlsplit(Url) -> +- {Scheme, Url1} = urlsplit_scheme(Url), +- {Netloc, Url2} = urlsplit_netloc(Url1), +- {Path, Query, Fragment} = urlsplit_path(Url2), +- {Scheme, Netloc, Path, Query, Fragment}. +- +-urlsplit_scheme(Url) -> +- urlsplit_scheme(Url, []). +- +-urlsplit_scheme([], Acc) -> +- {"", lists:reverse(Acc)}; +-urlsplit_scheme(":" ++ Rest, Acc) -> +- {string:to_lower(lists:reverse(Acc)), Rest}; +-urlsplit_scheme([C | Rest], Acc) -> +- urlsplit_scheme(Rest, [C | Acc]). +- +-urlsplit_netloc("//" ++ Rest) -> +- urlsplit_netloc(Rest, []); +-urlsplit_netloc(Path) -> +- {"", Path}. +- +-urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# -> +- {lists:reverse(Acc), Rest}; +-urlsplit_netloc([C | Rest], Acc) -> +- urlsplit_netloc(Rest, [C | Acc]). +- +- +-%% @spec path_split(string()) -> {Part, Rest} +-%% @doc Split a path starting from the left, as in URL traversal. +-%% path_split("foo/bar") = {"foo", "bar"}, +-%% path_split("/foo/bar") = {"", "foo/bar"}. +-path_split(S) -> +- path_split(S, []). +- +-path_split("", Acc) -> +- {lists:reverse(Acc), ""}; +-path_split("/" ++ Rest, Acc) -> +- {lists:reverse(Acc), Rest}; +-path_split([C | Rest], Acc) -> +- path_split(Rest, [C | Acc]). +- +- +-%% @spec urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> string() +-%% @doc Assemble a URL from the 5-tuple. Path must be absolute. +-urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> +- lists:flatten([case Scheme of "" -> ""; _ -> [Scheme, "://"] end, +- Netloc, +- urlunsplit_path({Path, Query, Fragment})]). +- +-%% @spec urlunsplit_path({Path, Query, Fragment}) -> string() +-%% @doc Assemble a URL path from the 3-tuple. +-urlunsplit_path({Path, Query, Fragment}) -> +- lists:flatten([Path, +- case Query of "" -> ""; _ -> [$? | Query] end, +- case Fragment of "" -> ""; _ -> [$# | Fragment] end]). +- +-%% @spec urlsplit_path(Url) -> {Path, Query, Fragment} +-%% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style +-%% paths. +-urlsplit_path(Path) -> +- urlsplit_path(Path, []). +- +-urlsplit_path("", Acc) -> +- {lists:reverse(Acc), "", ""}; +-urlsplit_path("?" ++ Rest, Acc) -> +- {Query, Fragment} = urlsplit_query(Rest), +- {lists:reverse(Acc), Query, Fragment}; +-urlsplit_path("#" ++ Rest, Acc) -> +- {lists:reverse(Acc), "", Rest}; +-urlsplit_path([C | Rest], Acc) -> +- urlsplit_path(Rest, [C | Acc]). +- +-urlsplit_query(Query) -> +- urlsplit_query(Query, []). +- +-urlsplit_query("", Acc) -> +- {lists:reverse(Acc), ""}; +-urlsplit_query("#" ++ Rest, Acc) -> +- {lists:reverse(Acc), Rest}; +-urlsplit_query([C | Rest], Acc) -> +- urlsplit_query(Rest, [C | Acc]). +- +-%% @spec guess_mime(string()) -> string() +-%% @doc Guess the mime type of a file by the extension of its filename. +-guess_mime(File) -> +- case filename:extension(File) of +- ".html" -> +- "text/html"; +- ".xhtml" -> +- "application/xhtml+xml"; +- ".xml" -> +- "application/xml"; +- ".css" -> +- "text/css"; +- ".js" -> +- "application/x-javascript"; +- ".jpg" -> +- "image/jpeg"; +- ".gif" -> +- "image/gif"; +- ".png" -> +- "image/png"; +- ".swf" -> +- "application/x-shockwave-flash"; +- ".zip" -> +- "application/zip"; +- ".bz2" -> +- "application/x-bzip2"; +- ".gz" -> +- "application/x-gzip"; +- ".tar" -> +- "application/x-tar"; +- ".tgz" -> +- "application/x-gzip"; +- ".txt" -> +- "text/plain"; +- ".doc" -> +- "application/msword"; +- ".pdf" -> +- "application/pdf"; +- ".xls" -> +- "application/vnd.ms-excel"; +- ".rtf" -> +- "application/rtf"; +- ".mov" -> +- "video/quicktime"; +- ".mp3" -> +- "audio/mpeg"; +- ".z" -> +- "application/x-compress"; +- ".wav" -> +- "audio/x-wav"; +- ".ico" -> +- "image/x-icon"; +- ".bmp" -> +- "image/bmp"; +- ".m4a" -> +- "audio/mpeg"; +- ".m3u" -> +- "audio/x-mpegurl"; +- ".exe" -> +- "application/octet-stream"; +- ".csv" -> +- "text/csv"; +- _ -> +- "text/plain" +- end. +- +-%% @spec parse_header(string()) -> {Type, [{K, V}]} +-%% @doc Parse a Content-Type like header, return the main Content-Type +-%% and a property list of options. +-parse_header(String) -> +- %% TODO: This is exactly as broken as Python's cgi module. +- %% Should parse properly like mochiweb_cookies. +- [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")], +- F = fun (S, Acc) -> +- case lists:splitwith(fun (C) -> C =/= $= end, S) of +- {"", _} -> +- %% Skip anything with no name +- Acc; +- {_, ""} -> +- %% Skip anything with no value +- Acc; +- {Name, [$\= | Value]} -> +- [{string:to_lower(string:strip(Name)), +- unquote_header(string:strip(Value))} | Acc] +- end +- end, +- {string:to_lower(Type), +- lists:foldr(F, [], Parts)}. +- +-unquote_header("\"" ++ Rest) -> +- unquote_header(Rest, []); +-unquote_header(S) -> +- S. +- +-unquote_header("", Acc) -> +- lists:reverse(Acc); +-unquote_header("\"", Acc) -> +- lists:reverse(Acc); +-unquote_header([$\\, C | Rest], Acc) -> +- unquote_header(Rest, [C | Acc]); +-unquote_header([C | Rest], Acc) -> +- unquote_header(Rest, [C | Acc]). +- +-%% @spec record_to_proplist(Record, Fields) -> proplist() +-%% @doc calls record_to_proplist/3 with a default TypeKey of '__record' +-record_to_proplist(Record, Fields) -> +- record_to_proplist(Record, Fields, '__record'). +- +-%% @spec record_to_proplist(Record, Fields, TypeKey) -> proplist() +-%% @doc Return a proplist of the given Record with each field in the +-%% Fields list set as a key with the corresponding value in the Record. +-%% TypeKey is the key that is used to store the record type +-%% Fields should be obtained by calling record_info(fields, record_type) +-%% where record_type is the record type of Record +-record_to_proplist(Record, Fields, TypeKey) +- when is_tuple(Record), +- is_list(Fields), +- size(Record) - 1 =:= length(Fields) -> +- lists:zip([TypeKey | Fields], tuple_to_list(Record)). +- +- +-shell_quote([], Acc) -> +- lists:reverse([$\" | Acc]); +-shell_quote([C | Rest], Acc) when C =:= $\" orelse C =:= $\` orelse +- C =:= $\\ orelse C =:= $\$ -> +- shell_quote(Rest, [C, $\\ | Acc]); +-shell_quote([C | Rest], Acc) -> +- shell_quote(Rest, [C | Acc]). +- +-test() -> +- test_join(), +- test_quote_plus(), +- test_unquote(), +- test_urlencode(), +- test_parse_qs(), +- test_urlsplit_path(), +- test_urlunsplit_path(), +- test_urlsplit(), +- test_urlunsplit(), +- test_path_split(), +- test_guess_mime(), +- test_parse_header(), +- test_shell_quote(), +- test_cmd(), +- test_cmd_string(), +- test_partition(), +- test_safe_relative_path(), +- ok. +- +-test_shell_quote() -> +- "\"foo \\$bar\\\"\\`' baz\"" = shell_quote("foo $bar\"`' baz"), +- ok. +- +-test_cmd() -> +- "$bling$ `word`!\n" = cmd(["echo", "$bling$ `word`!"]), +- ok. +- +-test_cmd_string() -> +- "\"echo\" \"\\$bling\\$ \\`word\\`!\"" = cmd_string(["echo", "$bling$ `word`!"]), +- ok. +- +-test_parse_header() -> +- {"multipart/form-data", [{"boundary", "AaB03x"}]} = +- parse_header("multipart/form-data; boundary=AaB03x"), +- ok. +- +-test_guess_mime() -> +- "text/plain" = guess_mime(""), +- "text/plain" = guess_mime(".text"), +- "application/zip" = guess_mime(".zip"), +- "application/zip" = guess_mime("x.zip"), +- "text/html" = guess_mime("x.html"), +- "application/xhtml+xml" = guess_mime("x.xhtml"), +- ok. +- +-test_path_split() -> +- {"", "foo/bar"} = path_split("/foo/bar"), +- {"foo", "bar"} = path_split("foo/bar"), +- {"bar", ""} = path_split("bar"), +- ok. +- +-test_urlsplit() -> +- {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"), +- {"http", "host:port", "/foo", "", "bar?baz"} = +- urlsplit("http://host:port/foo#bar?baz"), +- ok. +- +-test_urlsplit_path() -> +- {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"), +- {"/foo", "baz", ""} = urlsplit_path("/foo?baz"), +- {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"), +- {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"), +- {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"), +- {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"), +- ok. +- +-test_urlunsplit() -> +- "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}), +- "http://host:port/foo#bar?baz" = +- urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}), +- ok. +- +-test_urlunsplit_path() -> +- "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}), +- "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}), +- "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}), +- "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}), +- "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}), +- "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}), +- ok. +- +-test_join() -> +- "foo,bar,baz" = join(["foo", "bar", "baz"], $,), +- "foo,bar,baz" = join(["foo", "bar", "baz"], ","), +- "foo bar" = join([["foo", " bar"]], ","), +- "foo bar,baz" = join([["foo", " bar"], "baz"], ","), +- "foo" = join(["foo"], ","), +- "foobarbaz" = join(["foo", "bar", "baz"], ""), +- ok. +- +-test_quote_plus() -> +- "foo" = quote_plus(foo), +- "1" = quote_plus(1), +- "1.1" = quote_plus(1.1), +- "foo" = quote_plus("foo"), +- "foo+bar" = quote_plus("foo bar"), +- "foo%0A" = quote_plus("foo\n"), +- "foo%0A" = quote_plus("foo\n"), +- "foo%3B%26%3D" = quote_plus("foo;&="), +- ok. +- +-test_unquote() -> +- "foo bar" = unquote("foo+bar"), +- "foo bar" = unquote("foo%20bar"), +- "foo\r\n" = unquote("foo%0D%0A"), +- ok. +- +-test_urlencode() -> +- "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"}, +- {"baz", "wibble \r\n"}, +- {z, 1}]), +- ok. +- +-test_parse_qs() -> +- [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}] = +- parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"), +- ok. +- +-test_partition() -> +- {"foo", "", ""} = partition("foo", "/"), +- {"foo", "/", "bar"} = partition("foo/bar", "/"), +- {"foo", "/", ""} = partition("foo/", "/"), +- {"", "/", "bar"} = partition("/bar", "/"), +- {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"), +- ok. +- +-test_safe_relative_path() -> +- "foo" = safe_relative_path("foo"), +- "foo/" = safe_relative_path("foo/"), +- "foo" = safe_relative_path("foo/bar/.."), +- "bar" = safe_relative_path("foo/../bar"), +- "bar/" = safe_relative_path("foo/../bar/"), +- "" = safe_relative_path("foo/.."), +- "" = safe_relative_path("foo/../"), +- undefined = safe_relative_path("/foo"), +- undefined = safe_relative_path("../foo"), +- undefined = safe_relative_path("foo/../.."), +- undefined = safe_relative_path("foo//"), +- ok. +diff --git a/src/mochiweb/reloader.erl b/src/mochiweb/reloader.erl +deleted file mode 100644 +index 2ff154b..0000000 +--- a/src/mochiweb/reloader.erl ++++ /dev/null +@@ -1,124 +0,0 @@ +-%% @copyright 2007 Mochi Media, Inc. +-%% @author Matthew Dempsky +-%% +-%% @doc Erlang module for automatically reloading modified modules +-%% during development. +- +--module(reloader). +--author("Matthew Dempsky "). +- +--include_lib("kernel/include/file.hrl"). +- +--behaviour(gen_server). +--export([start/0, start_link/0]). +--export([stop/0]). +--export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +- +--record(state, {last, tref}). +- +-%% External API +- +-%% @spec start() -> ServerRet +-%% @doc Start the reloader. +-start() -> +- gen_server:start({local, ?MODULE}, ?MODULE, [], []). +- +-%% @spec start_link() -> ServerRet +-%% @doc Start the reloader. +-start_link() -> +- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +- +-%% @spec stop() -> ok +-%% @doc Stop the reloader. +-stop() -> +- gen_server:call(?MODULE, stop). +- +-%% gen_server callbacks +- +-%% @spec init([]) -> {ok, State} +-%% @doc gen_server init, opens the server in an initial state. +-init([]) -> +- {ok, TRef} = timer:send_interval(timer:seconds(1), doit), +- {ok, #state{last = stamp(), tref = TRef}}. +- +-%% @spec handle_call(Args, From, State) -> tuple() +-%% @doc gen_server callback. +-handle_call(stop, _From, State) -> +- {stop, shutdown, stopped, State}; +-handle_call(_Req, _From, State) -> +- {reply, {error, badrequest}, State}. +- +-%% @spec handle_cast(Cast, State) -> tuple() +-%% @doc gen_server callback. +-handle_cast(_Req, State) -> +- {noreply, State}. +- +-%% @spec handle_info(Info, State) -> tuple() +-%% @doc gen_server callback. +-handle_info(doit, State) -> +- Now = stamp(), +- doit(State#state.last, Now), +- {noreply, State#state{last = Now}}; +-handle_info(_Info, State) -> +- {noreply, State}. +- +-%% @spec terminate(Reason, State) -> ok +-%% @doc gen_server termination callback. +-terminate(_Reason, State) -> +- {ok, cancel} = timer:cancel(State#state.tref), +- ok. +- +- +-%% @spec code_change(_OldVsn, State, _Extra) -> State +-%% @doc gen_server code_change callback (trivial). +-code_change(_Vsn, State, _Extra) -> +- {ok, State}. +- +-%% Internal API +- +-doit(From, To) -> +- [case file:read_file_info(Filename) of +- {ok, FileInfo} when FileInfo#file_info.mtime >= From, +- FileInfo#file_info.mtime < To -> +- reload(Module); +- {ok, _} -> +- unmodified; +- {error, enoent} -> +- %% The Erlang compiler deletes existing .beam files if +- %% recompiling fails. Maybe it's worth spitting out a +- %% warning here, but I'd want to limit it to just once. +- gone; +- {error, Reason} -> +- io:format("Error reading ~s's file info: ~p~n", +- [Filename, Reason]), +- error +- end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. +- +-reload(Module) -> +- io:format("Reloading ~p ...", [Module]), +- code:purge(Module), +- case code:load_file(Module) of +- {module, Module} -> +- io:format(" ok.~n"), +- case erlang:function_exported(Module, test, 0) of +- true -> +- io:format(" - Calling ~p:test() ...", [Module]), +- case catch Module:test() of +- ok -> +- io:format(" ok.~n"), +- reload; +- Reason -> +- io:format(" fail: ~p.~n", [Reason]), +- reload_but_test_failed +- end; +- false -> +- reload +- end; +- {error, Reason} -> +- io:format(" fail: ~p.~n", [Reason]), +- error +- end. +- +- +-stamp() -> +- erlang:localtime(). +diff --git a/test/etap/030-doc-from-json.t b/test/etap/030-doc-from-json.t +index dc3327a..c984c66 100755 +--- a/test/etap/030-doc-from-json.t ++++ b/test/etap/030-doc-from-json.t +@@ -1,6 +1,6 @@ + #!/usr/bin/env escript + %% -*- erlang -*- +-%%! -pa ./src/couchdb -pa ./src/mochiweb -sasl errlog_type false -noshell ++%%! -pa ./src/couchdb -sasl errlog_type false -noshell + + % Licensed under the Apache License, Version 2.0 (the "License"); you may not + % use this file except in compliance with the License. You may obtain a copy of +diff --git a/test/etap/031-doc-to-json.t b/test/etap/031-doc-to-json.t +index 4e0c3f7..819c1fe 100755 +--- a/test/etap/031-doc-to-json.t ++++ b/test/etap/031-doc-to-json.t +@@ -1,6 +1,6 @@ + #!/usr/bin/env escript + %% -*- erlang -*- +-%%! -pa ./src/couchdb -pa ./src/mochiweb -sasl errlog_type false -noshell ++%%! -pa ./src/couchdb -sasl errlog_type false -noshell + + % Licensed under the Apache License, Version 2.0 (the "License"); you may not + % use this file except in compliance with the License. You may obtain a copy of +diff --git a/test/etap/test_util.erl.in b/test/etap/test_util.erl.in +index c57d7a8..948958c 100644 +--- a/test/etap/test_util.erl.in ++++ b/test/etap/test_util.erl.in +@@ -22,7 +22,7 @@ builddir() -> + "@abs_top_builddir@". + + init_code_path() -> +- Paths = ["couchdb", "ibrowse", "mochiweb"], ++ Paths = ["couchdb", "ibrowse"], + lists:foreach(fun(Name) -> + code:add_pathz(filename:join([builddir(), "src", Name])) + end, Paths). +diff --git a/test/run_native_process.es b/test/run_native_process.es +index 48092e4..4e15d2c 100755 +--- a/test/run_native_process.es ++++ b/test/run_native_process.es +@@ -49,7 +49,6 @@ loop(Pid) -> + + main([]) -> + code:add_pathz("../src/couchdb"), +- code:add_pathz("../src/mochiweb"), + {ok, Pid} = couch_native_process:start_link(), + loop(Pid). + +diff --git a/test/runner.sh b/test/runner.sh +index e1936b7..a7d518b 100755 +--- a/test/runner.sh ++++ b/test/runner.sh +@@ -14,7 +14,7 @@ + + erlc runner.erl + +-erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()" ++erl -noshell -pa ../src/couchdb -eval "runner:run()" + + cat ../share/www/script/couch.js \ + ../share/www/script/couch_test_runner.js \ +-- +1.6.6.1 + diff --git a/couchdb-0005-Remove-pid-file-after-stop.diff b/couchdb-0005-Remove-pid-file-after-stop.diff deleted file mode 100644 index 631e05a..0000000 --- a/couchdb-0005-Remove-pid-file-after-stop.diff +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/bin/couchdb.tpl.in~ b/bin/couchdb.tpl.in -index 78cf755..e74f708 100644 ---- a/bin/couchdb.tpl.in~ -+++ b/bin/couchdb.tpl.in -@@ -266,10 +266,8 @@ EOF - - stop_couchdb () { - PID=`_get_pid` -+ rm -f $PID_FILE - if test -n "$PID"; then -- if test "$1" = "false"; then -- echo > $PID_FILE -- fi - if kill -0 $PID 2> /dev/null; then - if kill -1 $PID 2> /dev/null; then - if test "$1" = "false"; then diff --git a/couchdb-0006-Remove-pid-file-after-stop.patch b/couchdb-0006-Remove-pid-file-after-stop.patch new file mode 100644 index 0000000..9ad7cdd --- /dev/null +++ b/couchdb-0006-Remove-pid-file-after-stop.patch @@ -0,0 +1,28 @@ +From 24308e72761870d7608ebead2a8ebd32106c4d83 Mon Sep 17 00:00:00 2001 +From: Peter Lemenkov +Date: Mon, 7 Jun 2010 15:08:42 +0400 +Subject: [PATCH 6/7] Remove pid-file after stop + +--- + bin/couchdb.tpl.in | 4 +--- + 1 files changed, 1 insertions(+), 3 deletions(-) + +diff --git a/bin/couchdb.tpl.in b/bin/couchdb.tpl.in +index 78cf755..e74f708 100644 +--- a/bin/couchdb.tpl.in ++++ b/bin/couchdb.tpl.in +@@ -266,10 +266,8 @@ EOF + + stop_couchdb () { + PID=`_get_pid` ++ rm -f $PID_FILE + if test -n "$PID"; then +- if test "$1" = "false"; then +- echo > $PID_FILE +- fi + if kill -0 $PID 2> /dev/null; then + if kill -1 $PID 2> /dev/null; then + if test "$1" = "false"; then +-- +1.6.6.1 + diff --git a/couchdb-0007-Fix-for-system-wide-mochiweb.patch b/couchdb-0007-Fix-for-system-wide-mochiweb.patch new file mode 100644 index 0000000..30b83a5 --- /dev/null +++ b/couchdb-0007-Fix-for-system-wide-mochiweb.patch @@ -0,0 +1,60 @@ +From 4cd1b65aa50d551664bec8d8ac8241d223c9448a Mon Sep 17 00:00:00 2001 +From: Peter Lemenkov +Date: Mon, 7 Jun 2010 23:01:25 +0400 +Subject: [PATCH 7/7] Fix for system-wide mochiweb + +--- + src/couchdb/couch_db.hrl | 4 ++-- + src/couchdb/couch_util.erl | 14 ++++++++++++++ + 2 files changed, 16 insertions(+), 2 deletions(-) + +diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl +index ead7841..a07863f 100644 +--- a/src/couchdb/couch_db.hrl ++++ b/src/couchdb/couch_db.hrl +@@ -14,8 +14,8 @@ + -define(DESIGN_DOC_PREFIX0, "_design"). + -define(DESIGN_DOC_PREFIX, "_design/"). + +--define(JSON_ENCODE(V), mochijson2:encode(V)). +--define(JSON_DECODE(V), mochijson2:decode(V)). ++-define(JSON_ENCODE(V), couch_util:json_encode(V)). ++-define(JSON_DECODE(V), couch_util:json_decode(V)). + + -define(b2l(V), binary_to_list(V)). + -define(l2b(V), list_to_binary(V)). +diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl +index d655989..67854fb 100644 +--- a/src/couchdb/couch_util.erl ++++ b/src/couchdb/couch_util.erl +@@ -21,6 +21,7 @@ + -export([file_read_size/1, get_nested_json_value/2, json_user_ctx/1]). + -export([to_binary/1, to_list/1, url_encode/1]). + -export([verify/2]). ++-export([json_encode/1, json_decode/1]). + + -include("couch_db.hrl"). + -include_lib("kernel/include/file.hrl"). +@@ -397,6 +398,19 @@ url_encode([H|T]) -> + url_encode([]) -> + []. + ++json_encode(V) -> ++ Handler = ++ fun({L}) when is_list(L) -> ++ {struct,L}; ++ (Bad) -> ++ exit({json_encode, {bad_term, Bad}}) ++ end, ++ (mochijson2:encoder([{handler, Handler}]))(V). ++ ++json_decode(V) -> ++ try (mochijson2:decoder([{object_hook, fun({struct,L}) -> {L} end}]))(V) ++ catch _:_ -> throw({invalid_json,V}) end. ++ + verify([X|RestX], [Y|RestY], Result) -> + verify(RestX, RestY, (X bxor Y) bor Result); + verify([], [], Result) -> +-- +1.6.6.1 + diff --git a/couchdb.spec b/couchdb.spec index 9e2ba27..51582c3 100644 --- a/couchdb.spec +++ b/couchdb.spec @@ -4,7 +4,7 @@ Name: couchdb Version: 0.10.2 -Release: 9%{?dist} +Release: 10%{?dist} Summary: A document database server, accessible via a RESTful JSON API Group: Applications/Databases @@ -16,18 +16,36 @@ Patch1: couchdb-0001-Force-init-script-installation.patch Patch2: couchdb-0002-Install-into-erllibdir-by-default.patch Patch3: couchdb-0003-Remove-bundled-erlang-oauth-library.patch Patch4: couchdb-0004-Remove-bundled-erlang-etap-library.patch -Patch5: couchdb-0005-Remove-pid-file-after-stop.diff +Patch5: couchdb-0005-Remove-bundled-mochiweb-library.patch +Patch6: couchdb-0006-Remove-pid-file-after-stop.patch +# Backported from 0.11.0 +Patch7: couchdb-0007-Fix-for-system-wide-mochiweb.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildRequires: curl-devel BuildRequires: erlang -BuildRequires: libicu-devel -BuildRequires: js-devel +BuildRequires: erlang-etap +BuildRequires: erlang-mochiweb +BuildRequires: erlang-oauth BuildRequires: help2man -BuildRequires: curl-devel -#BuildRequires: erlang-etap - +BuildRequires: js-devel +BuildRequires: libicu-devel +# /usr/bin/prove +BuildRequires: perl(Test::Harness) + +#Requires: erlang-crypto +#Requires: erlang-erts +#Requires: erlang-ibrowse +#Requires: erlang-inets +#Requires: erlang-kernel +#Requires: erlang-mochiweb +#Requires: erlang-oauth +#Requires: erlang-ssl +#Requires: erlang-stdlib +#Requires: erlang-tools Requires: erlang Requires: erlang-oauth +Requires: erlang-mochiweb # For %{_bindir}/icu-config Requires: libicu-devel @@ -47,15 +65,16 @@ with bi-directional conflict detection and resolution, and is queryable and indexable using a table-oriented view engine with JavaScript acting as the default view definition language. + %prep %setup -q -n apache-%{name}-%{version} %patch1 -p1 -b .initenabled %patch2 -p1 -b .fix_lib_path %patch3 -p1 -b .remove_bundled_oauth %patch4 -p1 -b .remove_bundled_etap -%patch5 -p1 -b .remove_pid_file -rm -rf src/erlang-oauth -rm -rf src/etap +%patch5 -p1 -b .remove_bundled_mochiweb +%patch6 -p1 -b .remove_pid_file +%patch7 -p1 -b .fix_for_mochi # Restore original timestamps to avoid reconfiguring touch -r configure.ac.initenabled configure.ac touch -r configure.fix_lib_path configure @@ -104,6 +123,11 @@ find $RPM_BUILD_ROOT -name '*.la' -exec rm -f {} ';' # fix respawn timeout to match default value sed -i s,^COUCHDB_RESPAWN_TIMEOUT=5,COUCHDB_RESPAWN_TIMEOUT=0,g $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/couchdb + +%check +make check && cd test && sh runner.sh || exit 1 + + %clean rm -rf $RPM_BUILD_ROOT @@ -151,6 +175,10 @@ fi %dir %attr(0755, %{couchdb_user}, root) %{_localstatedir}/lib/couchdb %changelog +* Mon Jun 7 2010 Peter Lemenkov 0.10.2-10 +- Use system-wide erlang-mochiweb instead of bundled copy (rhbz #581284) +- Added %%check target and necessary BuildRequires - etap, oauth, mochiweb + * Wed Jun 2 2010 Peter Lemenkov 0.10.2-9 - Remove pid-file after stopping CouchDB @@ -164,10 +192,10 @@ fi - Fix 'stop' and 'status' targets in the init-script (see rhbz #591026) * Thu May 27 2010 Peter Lemenkov 0.10.2-5 -- Use system-wide erlang-etap instead of bundled copy +- Use system-wide erlang-etap instead of bundled copy (rhbz #581281) * Fri May 14 2010 Peter Lemenkov 0.10.2-4 -- Use system-wide erlang-oauth instead of bundled copy +- Use system-wide erlang-oauth instead of bundled copy (rhbz #581283) * Thu May 13 2010 Peter Lemenkov 0.10.2-3 - Fixed init-script to use /etc/sysconfig/couchdb values (see rhbz #583004) diff --git a/import.log b/import.log index 211594a..f648d2c 100644 --- a/import.log +++ b/import.log @@ -13,3 +13,4 @@ couchdb-0_10_2-6_fc12:HEAD:couchdb-0.10.2-6.fc12.src.rpm:1275308416 couchdb-0_10_2-7_fc12:HEAD:couchdb-0.10.2-7.fc12.src.rpm:1275310561 couchdb-0_10_2-8_fc12:HEAD:couchdb-0.10.2-8.fc12.src.rpm:1275368318 couchdb-0_10_2-9_fc12:HEAD:couchdb-0.10.2-9.fc12.src.rpm:1275456881 +couchdb-0_10_2-10_fc12:HEAD:couchdb-0.10.2-10.fc12.src.rpm:1275997917