aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/doc/src/erl.xml6
-rw-r--r--erts/emulator/beam/bif.c10
-rw-r--r--erts/emulator/beam/erl_init.c10
-rw-r--r--erts/emulator/beam/erl_process.c10
-rw-r--r--erts/emulator/beam/erl_trace.c10
-rw-r--r--erts/emulator/test/process_SUITE.erl10
-rw-r--r--erts/emulator/test/system_info_SUITE.erl10
-rw-r--r--erts/etc/common/erlexec.c10
-rw-r--r--lib/cosFileTransfer/doc/src/notes.xml18
-rw-r--r--lib/cosFileTransfer/vsn.mk3
-rw-r--r--lib/inets/Makefile20
-rw-r--r--lib/inets/doc/src/Makefile58
-rw-r--r--lib/inets/doc/src/ftp.xml16
-rw-r--r--lib/inets/doc/src/http_server.xml86
-rw-r--r--lib/inets/doc/src/httpc.xml (renamed from lib/inets/doc/src/http.xml)184
-rw-r--r--lib/inets/doc/src/httpd.xml46
-rw-r--r--lib/inets/doc/src/httpd_util.xml11
-rw-r--r--lib/inets/doc/src/inets.xml8
-rw-r--r--lib/inets/doc/src/make.dep22
-rw-r--r--lib/inets/doc/src/mod_esi.xml8
-rw-r--r--lib/inets/doc/src/notes.xml122
-rw-r--r--lib/inets/doc/src/notes_history.xml24
-rw-r--r--lib/inets/doc/src/ref_man.xml8
-rw-r--r--lib/inets/src/ftp/Makefile18
-rw-r--r--lib/inets/src/http_client/Makefile27
-rw-r--r--lib/inets/src/http_client/http.erl772
-rw-r--r--lib/inets/src/http_client/httpc.erl1030
-rw-r--r--lib/inets/src/http_client/httpc_cookie.erl (renamed from lib/inets/src/http_client/http_cookie.erl)368
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl767
-rw-r--r--lib/inets/src/http_client/httpc_handler_sup.erl31
-rw-r--r--lib/inets/src/http_client/httpc_internal.hrl46
-rw-r--r--lib/inets/src/http_client/httpc_manager.erl713
-rw-r--r--lib/inets/src/http_client/httpc_profile_sup.erl20
-rw-r--r--lib/inets/src/http_client/httpc_request.erl50
-rw-r--r--lib/inets/src/http_client/httpc_response.erl19
-rw-r--r--lib/inets/src/http_lib/Makefile22
-rw-r--r--lib/inets/src/http_lib/http_chunk.erl21
-rw-r--r--lib/inets/src/http_lib/http_transport.erl11
-rw-r--r--lib/inets/src/http_lib/http_util.erl35
-rw-r--r--lib/inets/src/http_server/Makefile15
-rw-r--r--lib/inets/src/http_server/httpd.erl14
-rw-r--r--lib/inets/src/http_server/httpd_conf.erl19
-rw-r--r--lib/inets/src/http_server/httpd_instance_sup.erl16
-rw-r--r--lib/inets/src/http_server/httpd_request.erl17
-rw-r--r--lib/inets/src/http_server/httpd_sup.erl12
-rw-r--r--lib/inets/src/http_server/mod_alias.erl111
-rw-r--r--lib/inets/src/http_server/mod_cgi.erl12
-rw-r--r--lib/inets/src/http_server/mod_esi.erl51
-rw-r--r--lib/inets/src/inets_app/Makefile24
-rw-r--r--lib/inets/src/inets_app/inets.app.src15
-rw-r--r--lib/inets/src/inets_app/inets.appup.src24
-rw-r--r--lib/inets/src/inets_app/inets.erl16
-rw-r--r--lib/inets/src/tftp/Makefile20
-rw-r--r--lib/inets/test/Makefile343
-rw-r--r--lib/inets/test/ftp_SUITE.erl143
-rw-r--r--lib/inets/test/ftp_SUITE_data/TAR.exclude2
-rw-r--r--lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel17
-rw-r--r--lib/inets/test/ftp_format_SUITE.erl341
-rw-r--r--lib/inets/test/ftp_freebsd_x86_test.erl153
l---------lib/inets/test/ftp_internal.hrl1
-rw-r--r--lib/inets/test/ftp_linux_ppc_test.erl151
-rw-r--r--lib/inets/test/ftp_linux_x86_test.erl160
-rw-r--r--lib/inets/test/ftp_macosx_ppc_test.erl152
-rw-r--r--lib/inets/test/ftp_macosx_x86_test.erl152
-rw-r--r--lib/inets/test/ftp_netbsd_x86_test.erl152
-rw-r--r--lib/inets/test/ftp_openbsd_x86_test.erl151
-rw-r--r--lib/inets/test/ftp_solaris10_sparc_test.erl154
-rw-r--r--lib/inets/test/ftp_solaris10_x86_test.erl155
-rw-r--r--lib/inets/test/ftp_solaris8_sparc_test.erl152
-rw-r--r--lib/inets/test/ftp_solaris9_sparc_test.erl151
-rw-r--r--lib/inets/test/ftp_suite_lib.erl1593
-rw-r--r--lib/inets/test/ftp_ticket_test.erl51
-rw-r--r--lib/inets/test/ftp_windows_2003_server_test.erl152
-rw-r--r--lib/inets/test/ftp_windows_xp_test.erl150
-rw-r--r--lib/inets/test/http_format_SUITE.erl585
l---------lib/inets/test/http_internal.hrl1
-rw-r--r--lib/inets/test/httpc_SUITE.erl2846
l---------lib/inets/test/httpc_SUITE_data/Makefile.src1
l---------lib/inets/test/httpc_SUITE_data/cgi_echo.c1
-rw-r--r--lib/inets/test/httpc_SUITE_data/dummy.html12
-rw-r--r--lib/inets/test/httpc_SUITE_data/empty.html0
-rw-r--r--lib/inets/test/httpc_SUITE_data/mime.types465
-rw-r--r--lib/inets/test/httpc_SUITE_data/redirect.html10
-rw-r--r--lib/inets/test/httpc_SUITE_data/ssl_client_cert.pem22
-rw-r--r--lib/inets/test/httpc_SUITE_data/ssl_server_cert.pem22
-rw-r--r--lib/inets/test/httpc_cookie_SUITE.erl451
l---------lib/inets/test/httpc_internal.hrl1
-rw-r--r--lib/inets/test/httpd_1_1.erl494
-rw-r--r--lib/inets/test/httpd_SUITE.erl2081
-rw-r--r--lib/inets/test/httpd_SUITE_data/Makefile.src14
-rw-r--r--lib/inets/test/httpd_SUITE_data/cgi_echo.c97
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/auth/group3
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/auth/passwd4
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.bat9
-rwxr-xr-xlib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.sh6
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/conf/8080.conf79
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/conf/8888.conf63
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/conf/httpd.conf268
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/conf/mime.types465
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/conf/ssl.conf66
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/config.shtml70
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_open/dummy.html10
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/dummy.html10
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/top_secret/index.html9
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/echo.shtml35
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/exec.shtml30
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/flastmod.shtml29
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/fsize.shtml29
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/include.shtml33
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/index.html25
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/last_modified.html22
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/friedrich.html7
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/oech.html4
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/welcome.html1
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_open/dummy.html10
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/dummy.html10
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/top_secret/index.html9
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/open/dummy.html10
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/dummy.html10
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/top_secret/index.html9
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/README161
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/a.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/alert.black.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/alert.red.gifbin0 -> 247 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/apache_pb.gifbin0 -> 2326 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/back.gifbin0 -> 216 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/ball.gray.gifbin0 -> 233 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/ball.red.gifbin0 -> 205 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/binary.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/binhex.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/blank.gifbin0 -> 148 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/bomb.gifbin0 -> 308 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/box1.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/box2.gifbin0 -> 268 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/broken.gifbin0 -> 247 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/burst.gifbin0 -> 235 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button1.gifbin0 -> 755 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button10.gifbin0 -> 781 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button2.gifbin0 -> 785 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button3.gifbin0 -> 745 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button4.gifbin0 -> 786 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button5.gifbin0 -> 780 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button6.gifbin0 -> 791 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button7.gifbin0 -> 796 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button8.gifbin0 -> 784 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/button9.gifbin0 -> 784 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/buttonl.gifbin0 -> 587 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/buttonr.gifbin0 -> 576 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/c.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/comp.blue.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/comp.gray.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/compressed.gifbin0 -> 1038 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/continued.gifbin0 -> 214 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/dir.gifbin0 -> 225 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/down.gifbin0 -> 163 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/dvi.gifbin0 -> 238 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/f.gifbin0 -> 236 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/folder.gifbin0 -> 225 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/folder.open.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/folder.sec.gifbin0 -> 243 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/forward.gifbin0 -> 219 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/generic.gifbin0 -> 221 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/generic.red.gifbin0 -> 220 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/generic.sec.gifbin0 -> 249 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/hand.right.gifbin0 -> 217 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/hand.up.gifbin0 -> 223 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/htdig.gifbin0 -> 1822 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/icon.sheet.gifbin0 -> 11977 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/image1.gifbin0 -> 274 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/image2.gifbin0 -> 309 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/image3.gifbin0 -> 286 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/index.gifbin0 -> 268 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/layout.gifbin0 -> 276 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/left.gifbin0 -> 172 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/link.gifbin0 -> 249 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/movie.gifbin0 -> 243 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/p.gifbin0 -> 237 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/patch.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pdf.gifbin0 -> 249 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie0.gifbin0 -> 188 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie1.gifbin0 -> 198 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie2.gifbin0 -> 198 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie3.gifbin0 -> 191 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie4.gifbin0 -> 193 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie5.gifbin0 -> 189 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie6.gifbin0 -> 186 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie7.gifbin0 -> 185 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/pie8.gifbin0 -> 173 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/portal.gifbin0 -> 254 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/poweredby.gifbin0 -> 2748 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/ps.gifbin0 -> 244 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/quill.gifbin0 -> 267 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/right.gifbin0 -> 172 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/screw1.gifbin0 -> 258 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/screw2.gifbin0 -> 263 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/script.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/sound1.gifbin0 -> 248 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/sound2.gifbin0 -> 221 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/sphere1.gifbin0 -> 285 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/sphere2.gifbin0 -> 264 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/star.gifbin0 -> 89 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/star_blank.gifbin0 -> 53 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/tar.gifbin0 -> 243 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/tex.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/text.gifbin0 -> 229 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/transfer.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/unknown.gifbin0 -> 245 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/up.gifbin0 -> 164 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/uu.gifbin0 -> 236 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/uuencoded.gifbin0 -> 236 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/world1.gifbin0 -> 228 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/icons/world2.gifbin0 -> 261 bytes
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/logs/Dummy_File_Needed_By_WinZip1
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_client.pem22
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_server.pem22
-rw-r--r--lib/inets/test/httpd_basic_SUITE.erl136
-rw-r--r--lib/inets/test/httpd_block.erl299
-rw-r--r--lib/inets/test/httpd_load.erl99
-rw-r--r--lib/inets/test/httpd_mod.erl947
-rw-r--r--lib/inets/test/httpd_poll.erl496
-rw-r--r--lib/inets/test/httpd_test_data/server_root/auth/group3
-rw-r--r--lib/inets/test/httpd_test_data/server_root/auth/passwd4
-rw-r--r--lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.bat9
-rwxr-xr-xlib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.sh6
-rw-r--r--lib/inets/test/httpd_test_data/server_root/conf/8080.conf79
-rw-r--r--lib/inets/test/httpd_test_data/server_root/conf/8888.conf63
-rw-r--r--lib/inets/test/httpd_test_data/server_root/conf/httpd.conf268
-rw-r--r--lib/inets/test/httpd_test_data/server_root/conf/mime.types465
-rw-r--r--lib/inets/test/httpd_test_data/server_root/conf/ssl.conf66
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/config.shtml70
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/dets_open/dummy.html10
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/dummy.html10
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/top_secret/index.html9
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/echo.shtml35
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/exec.shtml30
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/flastmod.shtml29
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/fsize.shtml29
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/include.shtml33
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/index.html25
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/last_modified.html22
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/misc/friedrich.html7
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/misc/oech.html4
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/misc/welcome.html1
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_open/dummy.html10
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/dummy.html10
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/top_secret/index.html9
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/open/dummy.html10
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/secret/dummy.html10
-rw-r--r--lib/inets/test/httpd_test_data/server_root/htdocs/secret/top_secret/index.html9
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/README161
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/a.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/alert.black.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/alert.red.gifbin0 -> 247 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/apache_pb.gifbin0 -> 2326 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/back.gifbin0 -> 216 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/ball.gray.gifbin0 -> 233 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/ball.red.gifbin0 -> 205 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/binary.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/binhex.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/blank.gifbin0 -> 148 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/bomb.gifbin0 -> 308 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/box1.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/box2.gifbin0 -> 268 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/broken.gifbin0 -> 247 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/burst.gifbin0 -> 235 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button1.gifbin0 -> 755 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button10.gifbin0 -> 781 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button2.gifbin0 -> 785 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button3.gifbin0 -> 745 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button4.gifbin0 -> 786 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button5.gifbin0 -> 780 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button6.gifbin0 -> 791 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button7.gifbin0 -> 796 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button8.gifbin0 -> 784 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/button9.gifbin0 -> 784 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/buttonl.gifbin0 -> 587 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/buttonr.gifbin0 -> 576 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/c.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/comp.blue.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/comp.gray.gifbin0 -> 246 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/compressed.gifbin0 -> 1038 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/continued.gifbin0 -> 214 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/dir.gifbin0 -> 225 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/down.gifbin0 -> 163 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/dvi.gifbin0 -> 238 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/f.gifbin0 -> 236 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/folder.gifbin0 -> 225 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/folder.open.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/folder.sec.gifbin0 -> 243 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/forward.gifbin0 -> 219 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/generic.gifbin0 -> 221 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/generic.red.gifbin0 -> 220 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/generic.sec.gifbin0 -> 249 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/hand.right.gifbin0 -> 217 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/hand.up.gifbin0 -> 223 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/htdig.gifbin0 -> 1822 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/icon.sheet.gifbin0 -> 11977 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/image1.gifbin0 -> 274 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/image2.gifbin0 -> 309 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/image3.gifbin0 -> 286 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/index.gifbin0 -> 268 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/layout.gifbin0 -> 276 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/left.gifbin0 -> 172 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/link.gifbin0 -> 249 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/movie.gifbin0 -> 243 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/p.gifbin0 -> 237 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/patch.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pdf.gifbin0 -> 249 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie0.gifbin0 -> 188 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie1.gifbin0 -> 198 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie2.gifbin0 -> 198 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie3.gifbin0 -> 191 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie4.gifbin0 -> 193 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie5.gifbin0 -> 189 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie6.gifbin0 -> 186 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie7.gifbin0 -> 185 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/pie8.gifbin0 -> 173 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/portal.gifbin0 -> 254 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/poweredby.gifbin0 -> 2748 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/ps.gifbin0 -> 244 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/quill.gifbin0 -> 267 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/right.gifbin0 -> 172 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/screw1.gifbin0 -> 258 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/screw2.gifbin0 -> 263 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/script.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/sound1.gifbin0 -> 248 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/sound2.gifbin0 -> 221 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/sphere1.gifbin0 -> 285 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/sphere2.gifbin0 -> 264 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/star.gifbin0 -> 89 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/star_blank.gifbin0 -> 53 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/tar.gifbin0 -> 243 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/tex.gifbin0 -> 251 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/text.gifbin0 -> 229 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/transfer.gifbin0 -> 242 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/unknown.gifbin0 -> 245 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/up.gifbin0 -> 164 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/uu.gifbin0 -> 236 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/uuencoded.gifbin0 -> 236 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/world1.gifbin0 -> 228 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/icons/world2.gifbin0 -> 261 bytes
-rw-r--r--lib/inets/test/httpd_test_data/server_root/logs/Dummy_File_Needed_By_WinZip1
-rw-r--r--lib/inets/test/httpd_test_data/server_root/ssl/ssl_client.pem22
-rw-r--r--lib/inets/test/httpd_test_data/server_root/ssl/ssl_server.pem22
-rw-r--r--lib/inets/test/httpd_test_lib.erl332
-rw-r--r--lib/inets/test/httpd_time_test.erl500
-rw-r--r--lib/inets/test/inets.config1
-rw-r--r--lib/inets/test/inets.spec2
-rw-r--r--lib/inets/test/inets.spec.vxworks5
-rw-r--r--lib/inets/test/inets_SUITE.erl583
-rw-r--r--lib/inets/test/inets_SUITE_data/.gitignore0
-rw-r--r--lib/inets/test/inets_app_test.erl296
-rw-r--r--lib/inets/test/inets_appup_test.erl336
l---------lib/inets/test/inets_internal.hrl1
-rw-r--r--lib/inets/test/inets_sup_SUITE.erl414
-rw-r--r--lib/inets/test/inets_sup_SUITE_data/mime.types3
-rw-r--r--lib/inets/test/inets_sup_SUITE_data/simple.conf6
-rw-r--r--lib/inets/test/inets_test_lib.erl302
-rw-r--r--lib/inets/test/inets_test_lib.hrl104
-rw-r--r--lib/inets/test/rules.mk59
-rw-r--r--lib/inets/test/tftp_SUITE.erl903
-rw-r--r--lib/inets/test/tftp_test_lib.erl307
-rw-r--r--lib/inets/test/tftp_test_lib.hrl43
-rw-r--r--lib/inets/vsn.mk32
-rw-r--r--lib/orber/doc/src/notes.xml18
-rw-r--r--lib/orber/vsn.mk3
-rw-r--r--system/doc/reference_manual/modules.xml30
-rw-r--r--system/doc/reference_manual/part.xml7
-rwxr-xr-xsystem/doc/reference_manual/typespec.xml464
369 files changed, 24516 insertions, 1899 deletions
diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml
index fc1cd13fc9..6f5dd248ce 100644
--- a/erts/doc/src/erl.xml
+++ b/erts/doc/src/erl.xml
@@ -4,7 +4,7 @@
<comref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>erl</title>
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index a6668d5665..9c8c0df9f0 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1996-2010. All Rights Reserved.
+ *
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
- *
+ *
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
- *
+ *
* %CopyrightEnd%
*/
diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c
index 0677037bc5..bdf888eaff 100644
--- a/erts/emulator/beam/erl_init.c
+++ b/erts/emulator/beam/erl_init.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1997-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1997-2010. All Rights Reserved.
+ *
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
- *
+ *
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
- *
+ *
* %CopyrightEnd%
*/
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index ed907f05de..5f512a9982 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1996-2010. All Rights Reserved.
+ *
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
- *
+ *
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
- *
+ *
* %CopyrightEnd%
*/
diff --git a/erts/emulator/beam/erl_trace.c b/erts/emulator/beam/erl_trace.c
index 0b13569ab7..2842c2361a 100644
--- a/erts/emulator/beam/erl_trace.c
+++ b/erts/emulator/beam/erl_trace.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1999-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1999-2010. All Rights Reserved.
+ *
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
- *
+ *
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
- *
+ *
* %CopyrightEnd%
*/
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index 89603b45f4..77f850d0fb 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
diff --git a/erts/emulator/test/system_info_SUITE.erl b/erts/emulator/test/system_info_SUITE.erl
index a1068a91cd..e782d2f293 100644
--- a/erts/emulator/test/system_info_SUITE.erl
+++ b/erts/emulator/test/system_info_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c
index 1fc753293f..0ad26aeea7 100644
--- a/erts/etc/common/erlexec.c
+++ b/erts/etc/common/erlexec.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1996-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1996-2010. All Rights Reserved.
+ *
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
- *
+ *
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
- *
+ *
* %CopyrightEnd%
*/
diff --git a/lib/cosFileTransfer/doc/src/notes.xml b/lib/cosFileTransfer/doc/src/notes.xml
index 5bb2ea68c4..48d0c04236 100644
--- a/lib/cosFileTransfer/doc/src/notes.xml
+++ b/lib/cosFileTransfer/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2009</year>
+ <year>2000</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>cosFileTransfer Release Notes</title>
@@ -34,6 +34,18 @@
<title>cosFileTransfer 1.1.10</title>
<section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>
+ Removed obsolete SSL dependency.</p>
+ <p>
+ Own Id: OTP-8374 Aux Id:</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
<title>Fixed Bugs and Malfunctions</title>
<list type="bulleted">
<item>
diff --git a/lib/cosFileTransfer/vsn.mk b/lib/cosFileTransfer/vsn.mk
index 88786178fb..2700ecb3e3 100644
--- a/lib/cosFileTransfer/vsn.mk
+++ b/lib/cosFileTransfer/vsn.mk
@@ -1,7 +1,8 @@
COSFILETRANSFER_VSN = 1.1.10
TICKETS = \
- OTP-8355
+ OTP-8355 \
+ OTP-8374
TICKETS_1.1.9 = OTP-8201
diff --git a/lib/inets/Makefile b/lib/inets/Makefile
index 9a54bfb8e2..ec05efa461 100644
--- a/lib/inets/Makefile
+++ b/lib/inets/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1996-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1996-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -24,7 +24,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Macros
# ----------------------------------------------------
-SUB_DIRECTORIES = src examples priv doc/src
+SUB_DIRECTORIES = src examples priv doc/src
include vsn.mk
VSN = $(INETS_VSN)
@@ -36,3 +36,11 @@ SPECIAL_TARGETS =
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_subdir.mk
+info:
+ @echo "OS: $(OS)"
+ @echo "DOCB: $(DOCB)"
+ @echo ""
+ @echo "INETS_VSN: $(INETS_VSN)"
+ @echo "APP_VSN: $(APP_VSN)"
+ @echo ""
+
diff --git a/lib/inets/doc/src/Makefile b/lib/inets/doc/src/Makefile
index 5b5a818db8..e4cb0c4e48 100644
--- a/lib/inets/doc/src/Makefile
+++ b/lib/inets/doc/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1997-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -25,7 +25,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
include ../../vsn.mk
VSN=$(INETS_VSN)
-APPLICATION=inets
+
# ----------------------------------------------------
# Include dependency
@@ -35,11 +35,13 @@ ifndef DOCSUPPORT
include make.dep
endif
+
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
@@ -56,7 +58,7 @@ XML_REF3_FILES = \
inets.xml \
ftp.xml \
tftp.xml \
- http.xml\
+ httpc.xml\
httpd.xml \
httpd_conf.xml \
httpd_socket.xml \
@@ -67,41 +69,29 @@ XML_REF3_FILES = \
mod_security.xml
XML_PART_FILES = \
- part.xml \
- part_notes.xml \
- part_notes_history.xml
-XML_HTML_FILES = \
- notes_history.xml
+ part.xml
BOOK_FILES = book.xml
-XML_FILES = $(BOOK_FILES) \
- $(XML_CHAPTER_FILES) \
- $(XML_PART_FILES) \
- $(XML_REF6_FILES) \
- $(XML_REF3_FILES) \
- $(XML_APPLICATION_FILES)
+XML_FILES = \
+ $(BOOK_FILES) \
+ $(XML_CHAPTER_FILES) \
+ $(XML_PART_FILES) \
+ $(XML_REF6_FILES) \
+ $(XML_REF3_FILES) \
+ $(XML_APPLICATION_FILES)
-GIF_FILES = \
- inets.gif \
- notes.gif \
- ref_man.gif \
- book.gif \
- warning.gif \
- note.gif
+# GIF_FILES = inets.gif
# ----------------------------------------------------
HTML_FILES = \
$(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_HTML_FILES:%.xml=$(HTMLDIR)/%.html) \
$(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
INFO_FILE = ../../info
EXTRA_FILES = summary.html.src \
- $(DEFAULT_GIF_FILES) \
- $(DEFAULT_HTML_FILES) \
$(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
$(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
$(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
@@ -208,6 +198,7 @@ clean_html:
clean_man:
rm -f $(MAN3_FILES)
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -216,11 +207,11 @@ include $(ERL_TOP)/make/otp_release_targets.mk
ifdef DOCSUPPORT
release_docs_spec: docs
+ @echo "release_docs_spec(docs) when DOCSUPPORT=$DOCSUPPORT"
$(INSTALL_DIR) $(RELSYSDIR)/doc/pdf
$(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf
$(INSTALL_DIR) $(RELSYSDIR)/doc/html
- $(INSTALL_DATA) $(HTMLDIR)/* \
- $(RELSYSDIR)/doc/html
+ $(INSTALL_DATA) $(HTMLDIR)/* $(RELSYSDIR)/doc/html
$(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
$(INSTALL_DIR) $(RELEASE_PATH)/man/man3
$(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3
@@ -228,15 +219,18 @@ else
ifeq ($(DOCTYPE),pdf)
release_docs_spec: pdf
+ @echo "release_docs_spec(pdf)"
$(INSTALL_DIR) $(RELEASE_PATH)/pdf
$(INSTALL_DATA) $(TOP_PDF_FILE) $(RELEASE_PATH)/pdf
else
ifeq ($(DOCTYPE),ps)
release_docs_spec: ps
+ @echo "release_docs_spec(ps)"
$(INSTALL_DIR) $(RELEASE_PATH)/ps
$(INSTALL_DATA) $(TOP_PS_FILE) $(RELEASE_PATH)/ps
else
release_docs_spec: docs
+ @echo "release_docs_spec(docs)"
$(INSTALL_DIR) $(RELSYSDIR)/doc/html
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \
$(RELSYSDIR)/doc/html
@@ -260,10 +254,6 @@ info:
@echo ""
@echo "TOP_HTML_FILES:\n$(TOP_HTML_FILES)"
@echo ""
- @echo "DEFAULT_GIF_FILES:\n$(DEFAULT_GIF_FILES)"
- @echo ""
- @echo "DEFAULT_HTML_FILES:\n$(DEFAULT_HTML_FILES)"
- @echo ""
@echo "XML_REF3_FILES:\n$(XML_REF3_FILES)"
@echo ""
@echo "XML_REF6_FILES:\n$(XML_REF6_FILES)"
diff --git a/lib/inets/doc/src/ftp.xml b/lib/inets/doc/src/ftp.xml
index 9ecca3dde1..25dfe716fc 100644
--- a/lib/inets/doc/src/ftp.xml
+++ b/lib/inets/doc/src/ftp.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2009</year>
+ <year>1997</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>ftp</title>
@@ -534,12 +534,14 @@
<fsummary>Start an standalone ftp client.</fsummary>
<type>
<v>Host = string() | ip_address()</v>
- <v>Opts = start_options() | open_options()</v>
- <v>start_options() = [start_option()]</v>
+ <v>Opts = options()</v>
+ <v>options() = [option()]</v>
+ <v>option() = start_option() | open_option()</v>
+ <!-- <v>start_options() = [start_option()]</v> -->
<v>start_option() = {verbose, verbose()} | {debug, debug()}</v>
<v>verbose() = boolean() (defaults to false)</v>
<v>debug() = disable | debug | trace (defaults to disable)</v>
- <v>open_options() = [open_option()]</v>
+ <!-- <v>open_options() = [open_option()]</v> -->
<v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {timeout, timeout()} | {progress, progress()}</v>
<v>ipfamily() = inet | inet6 | inet6fb4 (defaults to inet)</v>
<v>port() = integer() > 0 (defaults to 21)</v>
@@ -845,7 +847,7 @@
<type>
<v>Pid = pid()</v>
<v>Command = string()</v>
- <v>FTPLine = string() - Note the telnet end of line characters, from the ftp protocol definition, CRLF e.g. "\\r\ " has been removed.</v>
+ <v>FTPLine = string() - Note the telnet end of line characters, from the ftp protocol definition, CRLF e.g. "\\r\\n" has been removed.</v>
</type>
<desc>
<p>Sends an arbitrary FTP command and returns verbatimly a list
diff --git a/lib/inets/doc/src/http_server.xml b/lib/inets/doc/src/http_server.xml
index 56317d647c..547617e2e3 100644
--- a/lib/inets/doc/src/http_server.xml
+++ b/lib/inets/doc/src/http_server.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>HTTP server </title>
@@ -82,7 +82,7 @@
<p>The server is configured using an erlang property list.
For the available properties see
- <seealso marker="inets:inets">httpd(3)</seealso>
+ <seealso marker="httpd">httpd(3)</seealso>
For backwards compatibility also apache-like config files
are supported.
</p>
@@ -246,7 +246,7 @@
every row contains the name of the group and the members
of the group separated by a space, for example:</p>
<pre>
-\011 GroupName: Member1 Member2 .... MemberN
+GroupName: Member1 Member2 .... MemberN
</pre>
</item>
<item>
@@ -278,8 +278,8 @@
and every row contains User Name and Password separated by a
colon, for example:</p>
<pre>
-\011 UserName:Password
-\011 UserName:Password
+UserName:Password
+UserName:Password
</pre>
</item>
<item>
@@ -299,11 +299,11 @@
the specified methods. If no request method is specified
all request methods are verified against the restrictions.</p>
<pre>
-\011 &lt;Limit POST GET HEAD&gt;
-\011 order allow deny
-\011 require group group1
-\011 allow from 123.145.244.5
-\011 &lt;/Limit&gt;
+&lt;Limit POST GET HEAD&gt;
+ order allow deny
+ require group group1
+ allow from 123.145.244.5
+&lt;/Limit&gt;
</pre>
</item>
<item>
@@ -363,12 +363,10 @@
message-body, separated by a blank line. The message-header
contains one or more header fields. The body may be
empty. Example: </p>
- <code type="none">
-"Content-Type:text/plain\
-Accept-Ranges:none\
-\
-some very
-\011plain text" </code>
+
+ <code>"Content-Type:text/plain\nAccept-Ranges:none\n\nsome very
+ plain text" </code>
+
<p>The server will interpret the cgi-headers and most of them
will be transformed into HTTP headers and sent back to the
client together with the body.</p>
@@ -387,7 +385,7 @@ some very
the extra overhead. An URL which calls an Erlang erl function
has the following syntax (regular expression): </p>
<code type="none">
-\011 http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)
+http://your.server.org/***/Module[:/]Function(?QueryString|/PathInfo)
</code>
<p>*** above depends on how the ErlScriptAlias config
directive has been used</p>
@@ -428,7 +426,7 @@ http://your.server.org/***/Mod:Func(Arg1,...,ArgN)
http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[])))
</code>
<p>which effectively will close down the Erlang node,
- that is use the erl scheme instead, until this
+ therefor, use the erl scheme instead, until this
security breach has been fixed.</p>
<p>Today there are no good way of solving this problem
and therefore Eval Scheme may be removed in future
@@ -498,14 +496,14 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
for parsed files, for example:
</p>
<pre>
-\011text/x-server-parsed-html shtml shtm
+ text/x-server-parsed-html shtml shtm
</pre>
<p>This makes files ending with <c>.shtml</c> and <c>.shtm</c>
into parsed files. Alternatively, if the performance hit is not a
problem, <em>all</em> HTML pages can be marked as parsed:
</p>
<pre>
-\011text/x-server-parsed-html html htm
+ text/x-server-parsed-html html htm
</pre>
</section>
@@ -518,7 +516,7 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
unparsed. Each directive has the following format:
</p>
<pre>
-\011&lt;!--#command tag1="value1" tag2="value2" --&gt;
+ &lt;!--#command tag1="value1" tag2="value2" --&gt;
</pre>
<p>Each command takes different arguments, most only accept one
tag at a time. Here is a breakdown of the commands and their
@@ -612,7 +610,7 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
<item>
<p>The unescaped version of any search query the client
sent, with all shell-special characters escaped with
- <c>\\</c>.</p>
+ <c>\</c>.</p>
</item>
<tag><c>DATE_LOCAL</c></tag>
<item>
@@ -753,24 +751,28 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
schema and the tables already is created. </p>
<code>
- -module(mnesia_test).
- -export([start/0,load_data/0]).
- -include("mod_auth.hrl").
-
- first_start()->
- mnesia:create_schema([node()]),
- mnesia:start(),
- mnesia:create_table(httpd_user,
- [{type,bag},{disc_copies,[node()]},
- {attributes,record_info(fields,httpd_user)}]),
- mnesia:create_table(httpd_group,
- [{type,bag},{disc_copies,[node()]},
- {attributes,record_info(fields,httpd_group)}]),
- mnesia:wait_for_tables([httpd_user,httpd_group],60000).
-
- start()->
- mnesia:start(),
- mnesia:wait_for_tables([httpd_user,httpd_group],60000).
+-module(mnesia_test).
+-export([start/0,load_data/0]).
+-include("mod_auth.hrl").
+
+first_start() ->
+ mnesia:create_schema([node()]),
+ mnesia:start(),
+ mnesia:create_table(httpd_user,
+ [{type, bag},
+ {disc_copies, [node()]},
+ {attributes, record_info(fields,
+ httpd_user)}]),
+ mnesia:create_table(httpd_group,
+ [{type, bag},
+ {disc_copies, [node()]},
+ {attributes, record_info(fields,
+ httpd_group)}]),
+ mnesia:wait_for_tables([httpd_user, httpd_group], 60000).
+
+start() ->
+ mnesia:start(),
+ mnesia:wait_for_tables([httpd_user, httpd_group], 60000).
</code>
<p>To create the Mnesia tables we use two records defined in
diff --git a/lib/inets/doc/src/http.xml b/lib/inets/doc/src/httpc.xml
index f6f8338113..680473cc38 100644
--- a/lib/inets/doc/src/http.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>http</title>
@@ -28,11 +28,11 @@
<date></date>
<rev></rev>
</header>
- <module>http</module>
+ <module>httpc</module>
<modulesummary>An HTTP/1.1 client </modulesummary>
<description>
- <p>This module provides the API to a HTTP/1.1 client according to
- RFC 2616, caching is currently not supported.</p>
+ <p>This module provides the API to a HTTP/1.1 compatible client according
+ to RFC 2616, caching is currently not supported.</p>
<note>
<p>When starting the Inets application a manager process for the
default profile will be started. The functions in this API
@@ -130,38 +130,23 @@ ssl_options() = {verify, code()} |
<p>The client can be stopped using inets:stop(httpc, Pid) or
inets:stop(httpc, Profile).</p>
- <marker id="cancel_request"></marker>
+ <marker id="request1"></marker>
</section>
<funcs>
<func>
- <name>cancel_request(RequestId) -> </name>
- <name>cancel_request(RequestId, Profile) -> ok</name>
- <fsummary>Cancels an asynchronous HTTP-request.</fsummary>
- <type>
- <v>RequestId = request_id() - A unique identifier as returned
- by request/4</v>
- <v>Profile = profile()</v>
- </type>
- <desc>
- <p>Cancels an asynchronous HTTP-request. </p>
-
- <marker id="request1"></marker>
- </desc>
- </func>
-
- <func>
<name>request(Url) -> </name>
<name>request(Url, Profile) -> {ok, Result} | {error, Reason}</name>
<fsummary>Sends a get HTTP-request</fsummary>
<type>
- <v>Url = url() </v> <v>Result = {status_line(), headers(),
- body()} | {status_code(), body()} | request_id() </v>
+ <v>Url = url() </v>
+ <v>Result = {status_line(), headers(), body()} |
+ {status_code(), body()} | request_id() </v>
<v>Profile = profile()</v>
<v>Reason = term() </v>
</type>
<desc>
- <p>Equivalent to http:request(get, {Url, []}, [], []).</p>
+ <p>Equivalent to httpc:request(get, {Url, []}, [], []).</p>
<marker id="request2"></marker>
</desc>
@@ -187,13 +172,18 @@ ssl_options() = {verify, code()} |
<v>timeout() = integer() >= 0 | infinity</v>
<v>Options = options()</v>
<v>options() = [option()]</v>
- <v>option() = {sync, boolean()} |
- {stream, stream_to()} |
- {body_format, body_format()} |
- {full_result, boolean()} |
- {headers_as_is, boolean()}</v>
- <v>stream_to() = self | {self, once} | filename() </v>
- <v>body_format() = string() | binary() </v>
+ <v>option() = {sync, boolean()} |
+ {stream, stream_to()} |
+ {body_format, body_format()} |
+ {full_result, boolean()} |
+ {headers_as_is, boolean() |
+ {receiver, receiver()}}</v>
+ <v>stream_to() = none | self | {self, once} | filename() </v>
+ <v>receiver() = pid() | function()/1 | {Module, Function, Args} </v>
+ <v>Module = atom() </v>
+ <v>Function = atom() </v>
+ <v>Args = list() </v>
+ <v>body_format() = string | binary </v>
<v>Result = {status_line(), headers(), body()} |
{status_code(), body()} | request_id() </v>
<v>Profile = profile() </v>
@@ -203,29 +193,25 @@ ssl_options() = {verify, code()} |
<desc>
<p>Sends a HTTP-request. The function can be both synchronous
and asynchronous. In the later case the function will return
- {ok, RequestId} and later on message(s) will be sent to the
- calling process on the format: </p>
-<pre>
- {http, {RequestId, Result}}
- {http, {RequestId, {error, Reason}}}
- {http, {RequestId, stream_start, Headers}
- {http, {RequestId, stream, BinBodyPart}
- {http, {RequestId, stream_end, Headers}
- {http, {RequestId, saved_to_file}}.
-</pre>
+ {ok, RequestId} and later on the information will be delivered
+ to the <c>receiver</c> depending on that value. </p>
<p>Http option (<c>http_option()</c>) details: </p>
<taglist>
<tag><c><![CDATA[timeout]]></c></tag>
<item>
<p>Timeout time for the request. </p>
+ <p>The clock start ticking as soon as the request has been
+ sent. </p>
+ <p>Time is in milliseconds. </p>
<p>Defaults to <c>infinity</c>. </p>
</item>
<tag><c><![CDATA[connect_timeout]]></c></tag>
<item>
<p>Connection timeout time, used during the initial request,
- when the client is connecting to the server. </p>
+ when the client is <em>connecting</em> to the server. </p>
+ <p>Time is in milliseconds. </p>
<p>Defaults to the value of the <c>timeout</c> option. </p>
</item>
@@ -329,8 +315,74 @@ ssl_options() = {verify, code()} |
<p>Defaults to <c>false</c>. </p>
</item>
+ <tag><c><![CDATA[receiver]]></c></tag>
+ <item>
+ <p>Defines how the client will deliver the result for a
+ asynchroneous request (<c>sync</c> has the value
+ <c>false</c>). </p>
+
+ <taglist>
+ <tag><c><![CDATA[pid()]]></c></tag>
+ <item>
+ <p>Message(s) will be sent to this process in the format: </p>
+<pre>
+{http, ReplyInfo}
+</pre>
+ </item>
+
+ <tag><c><![CDATA[function/1]]></c></tag>
+ <item>
+ <p>Information will be delivered to the receiver via calls
+ to the provided fun: </p>
+<pre>
+Receiver(ReplyInfo)
+</pre>
+ </item>
+
+ <tag><c><![CDATA[{Module, Funcion, Args}]]></c></tag>
+ <item>
+ <p>Information will be delivered to the receiver via calls
+ to the callback function: </p>
+<pre>
+apply(Module, Function, [ReplyInfo | Args])
+</pre>
+ </item>
+
+ </taglist>
+ <p>In all cases above, <c>ReplyInfo</c> has the following
+ structure: </p>
+
+<pre>
+{RequestId, saved_to_file}
+{RequestId, {error, Reason}}
+{RequestId, Result}
+{RequestId, stream_start, Headers}
+{RequestId, stream_start, Headers, HandlerPid}
+{RequestId, stream, BinBodyPart}
+{RequestId, stream_end, Headers}
+</pre>
+
+ <p>Defaults to the <c>pid()</c> of the process calling the request
+ function (<c>self()</c>). </p>
+ </item>
</taglist>
+ <marker id="cancel_request"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>cancel_request(RequestId) -> </name>
+ <name>cancel_request(RequestId, Profile) -> ok</name>
+ <fsummary>Cancels an asynchronous HTTP-request.</fsummary>
+ <type>
+ <v>RequestId = request_id() - A unique identifier as returned
+ by request/4</v>
+ <v>Profile = profile()</v>
+ </type>
+ <desc>
+ <p>Cancels an asynchronous HTTP-request. </p>
+
<marker id="set_options"></marker>
</desc>
</func>
@@ -439,12 +491,13 @@ ssl_options() = {verify, code()} |
same behavior as active once for sockets.</p>
<marker id="verify_cookie"></marker>
+ <marker id="store_cookie"></marker>
</desc>
</func>
<func>
- <name>verify_cookie(SetCookieHeaders, Url) -> </name>
- <name>verify_cookie(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name>
+ <name>store_cookie(SetCookieHeaders, Url) -> </name>
+ <name>store_cookie(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name>
<fsummary>Saves the cookies defined in SetCookieHeaders in the client profile's cookie database.</fsummary>
<type>
<v>SetCookieHeaders = headers() - where field = "set-cookie"</v>
@@ -454,7 +507,7 @@ ssl_options() = {verify, code()} |
<desc>
<p>Saves the cookies defined in SetCookieHeaders
in the client profile's cookie database. You need to
- call this function if you set the option cookies to verify.
+ call this function if you set the option cookies to <c>verify</c>.
If no profile is specified the default profile will be used.
</p>
@@ -476,6 +529,43 @@ ssl_options() = {verify, code()} |
when making a request to Url using the profile Profile.
If no profile is specified the default profile will be used.
</p>
+
+ <marker id="reset_cookies"></marker>
+ </desc>
+ </func>
+
+
+ <func>
+ <name>reset_cookies() -> void()</name>
+ <name>reset_cookies(Profile) -> void()</name>
+ <fsummary>Reset the cookie database.</fsummary>
+ <type>
+ <v>Profile = profile()</v>
+ </type>
+ <desc>
+ <p>Resets (clears) the cookie database for the specified Profile.
+ If no profile is specified the default profile will be used.
+ </p>
+ </desc>
+ </func>
+
+
+ <func>
+ <name>which_cookies() -> cookies()</name>
+ <name>which_cookies(Profile) -> cookies()</name>
+ <fsummary>Dumps out the entire cookie database.</fsummary>
+ <type>
+ <v>Profile = profile()</v>
+ <v>cookies() = [cooie_stores()]</v>
+ <v>cookie_stores() = {cookies, icookies()} | {session_cookies, icookies()}</v>
+ <v>icookies() = [icookie()]</v>
+ <v>cookie() = term()</v>
+ </type>
+ <desc>
+ <p>This function produces a list of the entire cookie database.
+ It is intended for debugging/testing purposes.
+ If no profile is specified the default profile will be used.
+ </p>
</desc>
</func>
</funcs>
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index 60afcc6cfe..7dabeb33e9 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2009</year>
+ <year>1997</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>httpd</title>
@@ -93,7 +93,7 @@
followed by the value followed by a new line. Ex:
<code>
- {server_root, "/urs/local/www"} -> ServerRoot /usr/local/www
+{server_root, "/urs/local/www"} -> ServerRoot /usr/local/www
</code>
<p>With a few exceptions, that are documented
@@ -103,9 +103,9 @@
as:</p>
<pre>
<![CDATA[
- <Directory Dir>
- <Properties handled as described above>
- </Directory>
+<Directory Dir>
+ <Properties handled as described above>
+</Directory>
]]>
</pre>
</item>
@@ -239,9 +239,9 @@
as an Apache like file as well as directly in the property list. Such
a file may look like:</p>
<pre>
- \011 # MIME type\011\011\011Extension
- \011 text/html\011\011\011html htm
- \011 text/plain\011\011\011asc txt
+# MIME type Extension
+text/html html htm
+text/plain asc txt
</pre>
<p>Defaults to [{"html","text/html"},{"htm","text/html"}]</p>
@@ -869,19 +869,19 @@ bytes
ModData = #mod{}
-record(mod, {
-\011\011data = [],
-\011\011socket_type = ip_comm,
-\011\011socket,
-\011\011config_db,
-\011\011method,
-\011\011absolute_uri,
-\011\011request_uri,
-\011\011http_version,
-\011\011request_line,
-\011\011parsed_header = [],
-\011\011entity_body,
-\011\011connection
-\011 }).
+ data = [],
+ socket_type = ip_comm,
+ socket,
+ config_db,
+ method,
+ absolute_uri,
+ request_uri,
+ http_version,
+ request_line,
+ parsed_header = [],
+ entity_body,
+ connection
+ }).
</code>
<p>The fields of the <c>mod</c> record has the following meaning:
</p>
diff --git a/lib/inets/doc/src/httpd_util.xml b/lib/inets/doc/src/httpd_util.xml
index 1566ee29d1..642e5213b0 100644
--- a/lib/inets/doc/src/httpd_util.xml
+++ b/lib/inets/doc/src/httpd_util.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2009</year>
+ <year>1997</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>httpd_util</title>
@@ -218,7 +218,8 @@
<marker id="lookup_mime"></marker>
<p><c>lookup_mime</c> returns the mime type associated with a
specific file suffix as specified in the <c>mime.types</c>
- file (located in the <path unix="$SERVER_ROOT/conf/mime.types" windows="%SERVER_ROOT%\\\\conf\\\\mime.types">config directory</path>).</p>
+ file (located in the
+ <path unix="$SERVER_ROOT/conf/mime.types" windows="%SERVER_ROOT%\conf\mime.types">config directory</path>).</p>
<marker id="lookup_mime_default"></marker>
</desc>
@@ -239,7 +240,7 @@
<p><c>lookup_mime_default</c> returns the mime type associated
with a specific file suffix as specified in the
<c>mime.types</c> file (located in the
- <path unix="$SERVER_ROOT/conf/mime.types" windows="%SERVER_ROOT%\\\\conf\\\\mime.types">config directory</path>).
+ <path unix="$SERVER_ROOT/conf/mime.types" windows="%SERVER_ROOT%\conf\mime.types">config directory</path>).
If no appropriate association can be found
the value of DefaultType is
returned.</p>
diff --git a/lib/inets/doc/src/inets.xml b/lib/inets/doc/src/inets.xml
index e5fe32f32f..81dfe7e944 100644
--- a/lib/inets/doc/src/inets.xml
+++ b/lib/inets/doc/src/inets.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2007</year><year>2009</year>
+ <year>2007</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>inets</title>
@@ -129,7 +129,7 @@
</type>
<desc>
<p>Dynamically starts an inets service after the inets
- application has been started.\011</p>
+ application has been started. </p>
<note>
<p>Dynamically started services will not be handled by
application takeover and failover behavior when inets is
diff --git a/lib/inets/doc/src/make.dep b/lib/inets/doc/src/make.dep
index d96c6dc5b8..8deb7e7a5a 100644
--- a/lib/inets/doc/src/make.dep
+++ b/lib/inets/doc/src/make.dep
@@ -1,3 +1,23 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1999-2010. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+#
+
# ----------------------------------------------------
# >>>> Do not edit this file <<<<
# This file was automaticly generated by
@@ -9,7 +29,7 @@
# TeX files that the DVI file depend on
# ----------------------------------------------------
-book.dvi: book.tex ftp.tex ftp_client.tex http.tex http_client.tex \
+book.dvi: book.tex ftp.tex ftp_client.tex httpc.tex http_client.tex \
http_server.tex httpd.tex httpd_conf.tex httpd_socket.tex \
httpd_util.tex inets.tex inets_services.tex \
mod_alias.tex mod_auth.tex mod_esi.tex mod_security.tex \
diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml
index d4541a1d15..6bad77dc0a 100644
--- a/lib/inets/doc/src/mod_esi.xml
+++ b/lib/inets/doc/src/mod_esi.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2009</year>
+ <year>1997</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>mod_esi</title>
@@ -94,7 +94,7 @@
that the first chunk of data sent to the client must at
least contain all HTTP header fields that the response
will generate. If the first chunk not contains
- <em>End of HTTP header</em> that is <c>"\\r\ \\r\ "</c>
+ <em>End of HTTP header</em> that is <c>"\r\n\r\n"</c>
the server will
assume that no HTTP header fields will be generated.</p>
</desc>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 687e127d0b..ed83708940 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2002</year><year>2009</year>
+ <year>2002</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>Inets Release Notes</title>
@@ -32,21 +32,98 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 5.2.0.1</title>
+ <section><title>Inets 5.3</title>
<section><title>Improvements and New Features</title>
- <p>-</p>
<!--
+ <p>-</p>
+-->
+
<list>
<item>
- <p>The documentation is now built with open source tools
- (<em>xsltproc</em> and <em>fop</em>) that exists on most
- platforms. One visible change is that the frames are removed.</p>
- <p>Own Id: OTP-8249</p>
+ <p>[httpc] Fix bug crafting Host header when port is not 80. </p>
+ <p>The host header should include the port number as well as the
+ host name when making a request to a server listening on a port
+ other than the HTTP default of 80. Currently, only the host
+ name is included. This is important to make the http client
+ more compliant with the HTTP specification. </p>
+ <p>Own Id: OTP-8371</p>
+ <p>Kelly McLaughlin</p>
</item>
- </list>
+ <item>
+ <p>[httpc|httpd] http_chunk data handling/passing improvement. </p>
+ <p>This is a modification to the http_chunk module to forward any
+ full chunk received, regardless of whether the size field for the
+ following chunk has been received yet. This allows http_chunk to
+ be used in situations where a long term HTTP connection is used to
+ send periodic status updates as individual chunks. Previously a
+ given chunk would not be forwarded to the client process until the
+ size for the next chunk had been read which rendered the module
+ difficult to use for the scenario described. </p>
+ <p>Bernard Duggan</p>
+ <p>Own Id: OTP-8351</p>
+ </item>
+
+ <item>
+ <p>Include the inets test suite in the release of the
+ application. </p>
+ <p>Own Id: OTP-8349</p>
+ </item>
+
+ <item>
+ <p>[httpc] - It is now possible to configure the client to
+ deliver an async reply to more receivers then the calling
+ process. </p>
+ <p>See the
+ <seealso marker="httpc#request2">receiver</seealso>
+ option for more info, </p>
+ <p>Own Id: OTP-8106</p>
+ </item>
+
+ <item>
+ <p>[httpd] - Methods "PUT" and "DELETE" now allowed. </p>
+ <p>Own Id: OTP-8103</p>
+ </item>
+
+ <item>
+ <p>[httpc] Several more or less critical fixes:</p>
+ <p>
+ <list type="bulleted">
+ <item>
+ <p>Initial call between the httpc manager and request
+ handler was synchronous. </p>
+ <p>When the manager starts a new request handler,
+ this is no longer a synchronous operation. Previously,
+ the new request handler made the connection to the
+ server and issuing of the first request (the reason
+ for starting it) in the gen_server init function.
+ If the connection for some reason "took some time",
+ the manager hanged, leaving all other activities by
+ that manager also hanging. </p>
+ </item>
+<!--
+ <item>
+ <p>Copying of data between processes</p>
+ <p>TBD</p>
+ </item>
+ <item>
+ <p>Reading of requests</p>
+ <p>TBD</p>
+ </item>
-->
+ </list>
+ </p>
+ <p>As a side-effect of these changes, some modules was also
+ renamed, and a new api module,
+ <seealso marker="httpc">httpc</seealso>, has been introduced
+ (the old module <c>http</c> is <em>not</em> removed, but is
+ now just wrapper for <c>httpc</c>). </p>
+ <p>Own Id: OTP-8016</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ </item>
+ </list>
</section>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -57,13 +134,32 @@
<list>
<item>
- <p>Fixing minor Dialyzer and copyright problem.</p>
+ <p>[httpd] The server did not fully support the documented module
+ callback api. Specifically, the load function should be able to
+ return the atom <c>ok</c>, but this was not accepted. </p>
+ <p>Own Id: OTP-8359</p>
</item>
+ <item>
+ <p>Fixing various documentation-related bugs (bad quotes).</p>
+ <p>Own Id: OTP-8327</p>
+ </item>
+
+ <item>
+ <p>Fixing minor Dialyzer and copyright problem(s). </p>
+ <p>Own Id: OTP-8315</p>
+ </item>
+
+ <item>
+ <p>[httpc] - Added basic sanity check of option value
+ combinations.</p>
+ <p>Own Id: OTP-8056</p>
+ </item>
</list>
</section>
- </section> <!-- 5.2.0.1 -->
+ </section> <!-- 5.3 -->
<section><title>Inets 5.2</title>
@@ -173,7 +269,7 @@
<p>Timeout out requests are retried. </p>
</item>
</list>
- <p>Jean-S�bastien P�dron</p>
+ <p>Jean-S&#233;bastien P&#233;dron</p>
<p>Own Id: OTP-8248</p>
</item>
diff --git a/lib/inets/doc/src/notes_history.xml b/lib/inets/doc/src/notes_history.xml
index 53375c9aa7..6480bad758 100644
--- a/lib/inets/doc/src/notes_history.xml
+++ b/lib/inets/doc/src/notes_history.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>Inets Release Notes History</title>
@@ -385,8 +385,7 @@
</item>
<item>
<p>[httpc, httpd] - In some cases if a body contained the
- sequence "\\r\
- 0" and was chunked encoded this sequence
+ sequence "\r\n0" and was chunked encoded this sequence
was incorrectly interpreted as the last chunk.</p>
<p>Own Id: OTP-6264 Aux Id: OTP-6005 </p>
</item>
@@ -968,8 +967,7 @@
design was changed to use gen_tcp active once semantics.
The API is not effected except for the function
ftp:quote/2 which now returns a list of strings (ftp
- result lines) where the line endings "\\r\
- " has been
+ result lines) where the line endings "\r\n" has been
removed. This was the original intention for the return
value of ftp:quote/2 but it was non trivial to make a
good such solution with the old design and a compromise
@@ -1055,13 +1053,11 @@
<p>Own Id: OTP-5551 Aux Id: seq9854 </p>
</item>
<item>
- <p>The HTTP server now handles "GET /\\r\
- \\r\
- " as well as
- "GET / \\r\
- \\r\
- ". According to the RFC the whitespace is
- not needed.</p>
+ <p>The HTTP server now handles
+ "GET /\r\n\r\n"
+ as well as
+ "GET / \r\n\r\n".
+ According to the RFC the whitespace is not needed.</p>
<p>Own Id: OTP-5552 Aux Id: seq8426 </p>
</item>
</list>
diff --git a/lib/inets/doc/src/ref_man.xml b/lib/inets/doc/src/ref_man.xml
index 7ec2c041c8..45d5dfcd0e 100644
--- a/lib/inets/doc/src/ref_man.xml
+++ b/lib/inets/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1997</year><year>2009</year>
+ <year>1997</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>Inets Reference Manual</title>
@@ -36,7 +36,7 @@
<xi:include href="inets.xml"/>
<xi:include href="ftp.xml"/>
<xi:include href="tftp.xml"/>
- <xi:include href="http.xml"/>
+ <xi:include href="httpc.xml"/>
<xi:include href="httpd.xml"/>
<xi:include href="httpd_conf.xml"/>
<xi:include href="httpd_socket.xml"/>
diff --git a/lib/inets/src/ftp/Makefile b/lib/inets/src/ftp/Makefile
index 70d51115e6..0c15277a18 100644
--- a/lib/inets/src/ftp/Makefile
+++ b/lib/inets/src/ftp/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -32,7 +32,8 @@ VSN = $(INETS_VSN)
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
# ----------------------------------------------------
# Target Specs
@@ -49,15 +50,17 @@ ERL_FILES = $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
# ----------------------------------------------------
# INETS FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"'
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
ifeq ($(FTP_DEBUG),true)
INETS_FLAGS += -Dftp_debug
endif
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
@@ -94,6 +97,7 @@ release_spec: opt
release_docs_spec:
info:
+ @echo "APPLICATION = $(APPLICATION)"
@echo "INETS_DEBUG = $(INETS_DEBUG)"
@echo "INETS_FLAGS = $(INETS_FLAGS)"
@echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
diff --git a/lib/inets/src/http_client/Makefile b/lib/inets/src/http_client/Makefile
index 23170f439f..628c91421f 100644
--- a/lib/inets/src/http_client/Makefile
+++ b/lib/inets/src/http_client/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -22,6 +22,7 @@ include $(ERL_TOP)/make/target.mk
EBIN = ../../ebin
include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
# ----------------------------------------------------
# Application version
# ----------------------------------------------------
@@ -29,17 +30,20 @@ include ../../vsn.mk
VSN = $(INETS_VSN)
+
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
MODULES = \
http \
- http_cookie \
+ httpc \
+ httpc_cookie \
httpc_handler \
httpc_manager \
httpc_sup \
@@ -55,20 +59,24 @@ ERL_FILES = $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
# ----------------------------------------------------
# INETS FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"' \
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
INETS_ERL_FLAGS += -I ../http_lib -I ../inets_app -pa ../../ebin
-ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS)\
+ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \
$(INETS_FLAGS) \
+'{parse_transform,sys_pre_attributes}' \
+'{attribute,insert,app_vsn,$(APP_VSN)}'
+
+
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
@@ -94,6 +102,7 @@ release_spec: opt
release_docs_spec:
info:
+ @echo "APPLICATION = $(APPLICATION)"
@echo "INETS_DEBUG = $(INETS_DEBUG)"
@echo "INETS_FLAGS = $(INETS_FLAGS)"
@echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
diff --git a/lib/inets/src/http_client/http.erl b/lib/inets/src/http_client/http.erl
index ce5d7723f0..7e1e90b50e 100644
--- a/lib/inets/src/http_client/http.erl
+++ b/lib/inets/src/http_client/http.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -24,7 +24,6 @@
%%% - RFC 2818 HTTP Over TLS
-module(http).
--behaviour(inets_service).
%% API
-export([request/1, request/2, request/4, request/5,
@@ -35,15 +34,6 @@
cookie_header/2, stream_next/1,
default_profile/0]).
-%% Behavior callbacks
--export([start_standalone/1, start_service/1,
- stop_service/1, services/0, service_info/1]).
-
--include("http_internal.hrl").
--include("httpc_internal.hrl").
-
--define(DEFAULT_PROFILE, default).
-
%%%=========================================================================
%%% API
@@ -51,751 +41,75 @@
%%--------------------------------------------------------------------------
%% request(Url [, Profile]) ->
-%% {ok, {StatusLine, Headers, Body}} | {error,Reason}
-%%
-%% Url - string()
-%% Description: Calls request/4 with default values.
+%% request(Method, Request, HTTPOptions, Options [, Profile])
%%--------------------------------------------------------------------------
-request(Url) ->
- request(Url, default_profile()).
-
-request(Url, Profile) ->
- request(get, {Url, []}, [], [], Profile).
-
-
-%%--------------------------------------------------------------------------
-%% request(Method, Request, HTTPOptions, Options [, Profile]) ->
-%% {ok, {StatusLine, Headers, Body}} | {ok, {Status, Body}} |
-%% {ok, RequestId} | {error,Reason} | {ok, {saved_as, FilePath}
-%%
-%% Method - atom() = head | get | put | post | trace | options| delete
-%% Request - {Url, Headers} | {Url, Headers, ContentType, Body}
-%% Url - string()
-%% HTTPOptions - [HttpOption]
-%% HTTPOption - {timeout, Time} | {connect_timeout, Time} |
-%% {ssl, SSLOptions} | {proxy_auth, {User, Password}}
-%% Ssloptions = [SSLOption]
-%% SSLOption = {verify, code()} | {depth, depth()} | {certfile, path()} |
-%% {keyfile, path()} | {password, string()} | {cacertfile, path()} |
-%% {ciphers, string()}
-%% Options - [Option]
-%% Option - {sync, Boolean} | {body_format, BodyFormat} |
-%% {full_result, Boolean} | {stream, To} |
-%% {headers_as_is, Boolean}
-%% StatusLine = {HTTPVersion, StatusCode, ReasonPhrase}</v>
-%% HTTPVersion = string()
-%% StatusCode = integer()
-%% ReasonPhrase = string()
-%% Headers = [Header]
-%% Header = {Field, Value}
-%% Field = string()
-%% Value = string()
-%% Body = string() | binary() - HTLM-code
-%%
-%% Description: Sends a HTTP-request. The function can be both
-%% syncronus and asynchronous in the later case the function will
-%% return {ok, RequestId} and later on a message will be sent to the
-%% calling process on the format {http, {RequestId, {StatusLine,
-%% Headers, Body}}} or {http, {RequestId, {error, Reason}}}
-%%--------------------------------------------------------------------------
+request(Url) -> httpc:request(Url).
+request(Url, Profile) -> httpc:request(Url, Profile).
request(Method, Request, HttpOptions, Options) ->
- request(Method, Request, HttpOptions, Options, default_profile()).
+ httpc:request(Method, Request, HttpOptions, Options).
+request(Method, Request, HttpOptions, Options, Profile) ->
+ httpc:request(Method, Request, HttpOptions, Options, Profile).
-request(Method, {Url, Headers}, HTTPOptions, Options, Profile)
- when (Method =:= options) orelse
- (Method =:= get) orelse
- (Method =:= head) orelse
- (Method =:= delete) orelse
- (Method =:= trace) ->
- case http_uri:parse(Url) of
- {error, Reason} ->
- {error, Reason};
- ParsedUrl ->
- handle_request(Method, Url, ParsedUrl, Headers, [], [],
- HTTPOptions, Options, Profile)
- end;
-
-request(Method, {Url,Headers,ContentType,Body}, HTTPOptions, Options, Profile)
- when (Method =:= post) orelse (Method =:= put) ->
- case http_uri:parse(Url) of
- {error, Reason} ->
- {error, Reason};
- ParsedUrl ->
- handle_request(Method, Url,
- ParsedUrl, Headers, ContentType, Body,
- HTTPOptions, Options, Profile)
- end.
%%--------------------------------------------------------------------------
-%% request(RequestId) -> ok
-%% RequestId - As returned by request/4
-%%
-%% Description: Cancels a HTTP-request.
+%% cancel_request(RequestId [, Profile])
%%-------------------------------------------------------------------------
-cancel_request(RequestId) ->
- cancel_request(RequestId, default_profile()).
+cancel_request(RequestId) ->
+ httpc:cancel_request(RequestId).
cancel_request(RequestId, Profile) ->
- ok = httpc_manager:cancel_request(RequestId, profile_name(Profile)),
- receive
- %% If the request was allready fullfilled throw away the
- %% answer as the request has been canceled.
- {http, {RequestId, _}} ->
- ok
- after 0 ->
- ok
- end.
-
-
-set_option(Key, Value) ->
- set_option(Key, Value, default_profile()).
-
-set_option(Key, Value, Profile) ->
- set_options([{Key, Value}], Profile).
+ httpc:cancel_request(RequestId, Profile).
%%--------------------------------------------------------------------------
-%% set_options(Options [, Profile]) -> ok | {error, Reason}
-%% Options - [Option]
-%% Profile - atom()
-%% Option - {proxy, {Proxy, NoProxy}} | {max_sessions, MaxSessions} |
-%% {max_pipeline_length, MaxPipeline} |
-%% {pipeline_timeout, PipelineTimeout} | {cookies, CookieMode} |
-%% {ipfamily, IpFamily}
-%% Proxy - {Host, Port}
-%% NoProxy - [Domain | HostName | IPAddress]
-%% MaxSessions, MaxPipeline, PipelineTimeout = integer()
-%% CookieMode - enabled | disabled | verify
-%% IpFamily - inet | inet6 | inet6fb4
-%% Description: Informs the httpc_manager of the new settings.
+%% set_options(Options [, Profile])
+%% set_option(Key, Value [, Profile])
%%-------------------------------------------------------------------------
+
set_options(Options) ->
- set_options(Options, default_profile()).
+ httpc:set_options(Options).
set_options(Options, Profile) ->
- case validate_options(Options) of
- {ok, Opts} ->
- try httpc_manager:set_options(Opts, profile_name(Profile)) of
- Result ->
- Result
- catch
- exit:{noproc, _} ->
- {error, inets_not_started}
- end;
- {error, Reason} ->
- {error, Reason}
- end.
+ httpc:set_options(Options, Profile).
+
+set_option(Key, Value) ->
+ httpc:set_option(Key, Value).
+set_option(Key, Value, Profile) ->
+ httpc:set_option(Key, Value, Profile).
%%--------------------------------------------------------------------------
-%% verify_cookies(SetCookieHeaders, Url [, Profile]) -> ok | {error, reason}
-%%
-%%
-%% Description: Store the cookies from <SetCookieHeaders>
-%% in the cookie database
-%% for the profile <Profile>. This function shall be used when the option
-%% cookie is set to verify.
+%% verify_cookies(SetCookieHeaders, Url [, Profile])
%%-------------------------------------------------------------------------
-verify_cookies(SetCookieHeaders, Url) ->
- verify_cookies(SetCookieHeaders, Url, default_profile()).
+verify_cookies(SetCookieHeaders, Url) ->
+ httpc:store_cookies(SetCookieHeaders, Url).
verify_cookies(SetCookieHeaders, Url, Profile) ->
- {_, _, Host, Port, Path, _} = http_uri:parse(Url),
- ProfileName = profile_name(Profile),
- Cookies = http_cookie:cookies(SetCookieHeaders, Path, Host),
- try httpc_manager:store_cookies(Cookies, {Host, Port}, ProfileName) of
- _ ->
- ok
- catch
- exit:{noproc, _} ->
- {error, {not_started, Profile}}
- end.
+ httpc:store_cookies(SetCookieHeaders, Url, Profile).
+
%%--------------------------------------------------------------------------
-%% cookie_header(Url [, Profile]) -> Header | {error, Reason}
-%%
-%% Description: Returns the cookie header that would be sent when making
-%% a request to <Url>.
+%% cookie_header(Url [, Profile])
%%-------------------------------------------------------------------------
-cookie_header(Url) ->
- cookie_header(Url, default_profile()).
+cookie_header(Url) ->
+ httpc:cookie_header(Url).
cookie_header(Url, Profile) ->
- try httpc_manager:cookies(Url, profile_name(Profile)) of
- Header ->
- Header
- catch
- exit:{noproc, _} ->
- {error, {not_started, Profile}}
- end.
-
-
-stream_next(Pid) ->
- httpc_handler:stream_next(Pid).
-
-%%%========================================================================
-%%% Behavior callbacks
-%%%========================================================================
-start_standalone(PropList) ->
- case proplists:get_value(profile, PropList) of
- undefined ->
- {error, no_profile};
- Profile ->
- Dir =
- proplists:get_value(data_dir, PropList, only_session_cookies),
- httpc_manager:start_link({Profile, Dir}, stand_alone)
- end.
-
-start_service(Config) ->
- httpc_profile_sup:start_child(Config).
-
-stop_service(Profile) when is_atom(Profile) ->
- httpc_profile_sup:stop_child(Profile);
-stop_service(Pid) when is_pid(Pid) ->
- case service_info(Pid) of
- {ok, [{profile, Profile}]} ->
- stop_service(Profile);
- Error ->
- Error
- end.
-
-services() ->
- [{httpc, Pid} || {_, Pid, _, _} <-
- supervisor:which_children(httpc_profile_sup)].
-service_info(Pid) ->
- try [{ChildName, ChildPid} ||
- {ChildName, ChildPid, _, _} <-
- supervisor:which_children(httpc_profile_sup)] of
- Children ->
- child_name2info(child_name(Pid, Children))
- catch
- exit:{noproc, _} ->
- {error, service_not_available}
- end.
-
-
-%%%========================================================================
-%%% Internal functions
-%%%========================================================================
-handle_request(Method, Url,
- {Scheme, UserInfo, Host, Port, Path, Query},
- Headers, ContentType, Body,
- HTTPOptions0, Options, Profile) ->
-
- HTTPOptions = http_options(HTTPOptions0),
- Sync = proplists:get_value(sync, Options, true),
- NewHeaders = lists:map(fun({Key, Val}) ->
- {http_util:to_lower(Key), Val} end,
- Headers),
- Stream = proplists:get_value(stream, Options, none),
- case {Sync, Stream} of
- {true, self} ->
- {error, streaming_error};
- _ ->
- RecordHeaders = header_record(NewHeaders,
- #http_request_h{},
- Host,
- HTTPOptions#http_options.version),
- Request = #request{from = self(),
- scheme = Scheme,
- address = {Host,Port},
- path = Path,
- pquery = Query,
- method = Method,
- headers = RecordHeaders,
- content = {ContentType,Body},
- settings = HTTPOptions,
- abs_uri = Url,
- userinfo = UserInfo,
- stream = Stream,
- headers_as_is = headers_as_is(Headers, Options)},
- try httpc_manager:request(Request, profile_name(Profile)) of
- {ok, RequestId} ->
- handle_answer(RequestId, Sync, Options);
- {error, Reason} ->
- {error, Reason}
- catch
- error:{noproc, _} ->
- {error, {not_started, Profile}}
- end
- end.
-
-
-handle_answer(RequestId, false, _) ->
- {ok, RequestId};
-handle_answer(RequestId, true, Options) ->
- receive
- {http, {RequestId, saved_to_file}} ->
- {ok, saved_to_file};
- {http, {RequestId, Result = {_,_,_}}} ->
- return_answer(Options, Result);
- {http, {RequestId, {error, Reason}}} ->
- {error, Reason}
- end.
-
-return_answer(Options, {{"HTTP/0.9",_,_}, _, BinBody}) ->
- Body = format_body(BinBody, Options),
- {ok, Body};
-
-return_answer(Options, {StatusLine, Headers, BinBody}) ->
-
- Body = format_body(BinBody, Options),
-
- case proplists:get_value(full_result, Options, true) of
- true ->
- {ok, {StatusLine, Headers, Body}};
- false ->
- {_, Status, _} = StatusLine,
- {ok, {Status, Body}}
- end.
-
-format_body(BinBody, Options) ->
- case proplists:get_value(body_format, Options, string) of
- string ->
- binary_to_list(BinBody);
- _ ->
- BinBody
- end.
+ httpc:cookie_header(Url, Profile).
-%% This options is a workaround for http servers that do not follow the
-%% http standard and have case sensative header parsing. Should only be
-%% used if there is no other way to communicate with the server or for
-%% testing purpose.
-headers_as_is(Headers, Options) ->
- case proplists:get_value(headers_as_is, Options, false) of
- false ->
- [];
- true ->
- Headers
- end.
+%%--------------------------------------------------------------------------
+%% stream_next(Pid)
+%%-------------------------------------------------------------------------
-http_options(HttpOptions) ->
- HttpOptionsDefault = http_options_default(),
- http_options(HttpOptionsDefault, HttpOptions, #http_options{}).
-
-http_options([], [], Acc) ->
- Acc;
-http_options([], HttpOptions, Acc) ->
- Fun = fun(BadOption) ->
- Report = io_lib:format("Invalid option ~p ignored ~n",
- [BadOption]),
- error_logger:info_report(Report)
- end,
- lists:foreach(Fun, HttpOptions),
- Acc;
-http_options([{Tag, Default, Idx, Post} | Defaults], HttpOptions, Acc) ->
- case lists:keysearch(Tag, 1, HttpOptions) of
- {value, {Tag, Val0}} ->
- case Post(Val0) of
- {ok, Val} ->
- Acc2 = setelement(Idx, Acc, Val),
- HttpOptions2 = lists:keydelete(Tag, 1, HttpOptions),
- http_options(Defaults, HttpOptions2, Acc2);
- error ->
- Report = io_lib:format("Invalid option ~p:~p ignored ~n",
- [Tag, Val0]),
- error_logger:info_report(Report),
- HttpOptions2 = lists:keydelete(Tag, 1, HttpOptions),
- http_options(Defaults, HttpOptions2, Acc)
- end;
- false ->
- DefaultVal =
- case Default of
- {value, Val} ->
- Val;
- {field, DefaultIdx} ->
- element(DefaultIdx, Acc)
- end,
- Acc2 = setelement(Idx, Acc, DefaultVal),
- http_options(Defaults, HttpOptions, Acc2)
- end.
-
-http_options_default() ->
- VersionPost =
- fun(Value) when is_atom(Value) ->
- {ok, http_util:to_upper(atom_to_list(Value))};
- (Value) when is_list(Value) ->
- {ok, http_util:to_upper(Value)};
- (_) ->
- error
- end,
- TimeoutPost = fun(Value) when is_integer(Value) andalso (Value >= 0) ->
- {ok, Value};
- (infinity = Value) ->
- {ok, Value};
- (_) ->
- error
- end,
- AutoRedirectPost = fun(Value) when (Value =:= true) orelse
- (Value =:= false) ->
- {ok, Value};
- (_) ->
- error
- end,
- SslPost = fun(Value) when is_list(Value) ->
- {ok, Value};
- (_) ->
- error
- end,
- ProxyAuthPost = fun({User, Passwd} = Value) when is_list(User) andalso
- is_list(Passwd) ->
- {ok, Value};
- (_) ->
- error
- end,
- RelaxedPost = fun(Value) when (Value =:= true) orelse
- (Value =:= false) ->
- {ok, Value};
- (_) ->
- error
- end,
- ConnTimeoutPost =
- fun(Value) when is_integer(Value) andalso (Value >= 0) ->
- {ok, Value};
- (infinity = Value) ->
- {ok, Value};
- (_) ->
- error
- end,
- [
- {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
- {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
- {autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
- {ssl, {value, []}, #http_options.ssl, SslPost},
- {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
- {relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
- %% this field has to be *after* the timeout field (as that field is used for the default value)
- {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost}
- ].
-
-validate_options(Options) ->
- (catch validate_options(Options, [])).
-
-validate_options([], ValidateOptions) ->
- {ok, lists:reverse(ValidateOptions)};
-
-validate_options([{proxy, Proxy} = Opt| Tail], Acc) ->
- validate_proxy(Proxy),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{max_sessions, Value} = Opt| Tail], Acc) ->
- validate_max_sessions(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{keep_alive_timeout, Value} = Opt| Tail], Acc) ->
- validate_keep_alive_timeout(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{max_keep_alive_length, Value} = Opt| Tail], Acc) ->
- validate_max_keep_alive_length(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{pipeline_timeout, Value} = Opt| Tail], Acc) ->
- validate_pipeline_timeout(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{max_pipeline_length, Value} = Opt| Tail], Acc) ->
- validate_max_pipeline_length(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{cookies, Value} = Opt| Tail], Acc) ->
- validate_cookies(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{ipfamily, Value} = Opt| Tail], Acc) ->
- validate_ipfamily(Value),
- validate_options(Tail, [Opt | Acc]);
-
-%% For backward compatibillity
-validate_options([{ipv6, Value}| Tail], Acc) ->
- NewValue = validate_ipv6(Value),
- Opt = {ipfamily, NewValue},
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{ip, Value} = Opt| Tail], Acc) ->
- validate_ip(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{port, Value} = Opt| Tail], Acc) ->
- validate_port(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{verbose, Value} = Opt| Tail], Acc) ->
- validate_verbose(Value),
- validate_options(Tail, [Opt | Acc]);
-
-validate_options([{_, _} = Opt| _], _Acc) ->
- {error, {not_an_option, Opt}}.
-
-
-validate_proxy({{ProxyHost, ProxyPort}, NoProxy} = Proxy)
- when is_list(ProxyHost) andalso
- is_integer(ProxyPort) andalso
- is_list(NoProxy) ->
- Proxy;
-validate_proxy(BadProxy) ->
- bad_option(proxy, BadProxy).
-
-validate_max_sessions(Value) when is_integer(Value) andalso (Value >= 0) ->
- Value;
-validate_max_sessions(BadValue) ->
- bad_option(max_sessions, BadValue).
-
-validate_keep_alive_timeout(Value) when is_integer(Value) andalso (Value >= 0) ->
- Value;
-validate_keep_alive_timeout(infinity = Value) ->
- Value;
-validate_keep_alive_timeout(BadValue) ->
- bad_option(keep_alive_timeout, BadValue).
-
-validate_max_keep_alive_length(Value) when is_integer(Value) andalso (Value >= 0) ->
- Value;
-validate_max_keep_alive_length(BadValue) ->
- bad_option(max_keep_alive_length, BadValue).
-
-validate_pipeline_timeout(Value) when is_integer(Value) ->
- Value;
-validate_pipeline_timeout(infinity = Value) ->
- Value;
-validate_pipeline_timeout(BadValue) ->
- bad_option(pipeline_timeout, BadValue).
-
-validate_max_pipeline_length(Value) when is_integer(Value) ->
- Value;
-validate_max_pipeline_length(BadValue) ->
- bad_option(max_pipeline_length, BadValue).
-
-validate_cookies(Value)
- when ((Value =:= enabled) orelse
- (Value =:= disabled) orelse
- (Value =:= verify)) ->
- Value;
-validate_cookies(BadValue) ->
- bad_option(cookies, BadValue).
-
-validate_ipv6(Value) when (Value =:= enabled) orelse (Value =:= disabled) ->
- case Value of
- enabled ->
- inet6fb4;
- disabled ->
- inet
- end;
-validate_ipv6(BadValue) ->
- bad_option(ipv6, BadValue).
-
-validate_ipfamily(Value)
- when (Value =:= inet) orelse (Value =:= inet6) orelse (Value =:= inet6fb4) ->
- Value;
-validate_ipfamily(BadValue) ->
- bad_option(ipfamily, BadValue).
-
-validate_ip(Value)
- when is_tuple(Value) andalso ((size(Value) =:= 4) orelse (size(Value) =:= 8)) ->
- Value;
-validate_ip(BadValue) ->
- bad_option(ip, BadValue).
-
-validate_port(Value) when is_integer(Value) ->
- Value;
-validate_port(BadValue) ->
- bad_option(port, BadValue).
-
-validate_verbose(Value)
- when ((Value =:= false) orelse
- (Value =:= verbose) orelse
- (Value =:= debug) orelse
- (Value =:= trace)) ->
- ok;
-validate_verbose(BadValue) ->
- bad_option(verbose, BadValue).
-
-bad_option(Option, BadValue) ->
- throw({error, {bad_option, Option, BadValue}}).
-
-
-
-header_record([], RequestHeaders, Host, Version) ->
- validate_headers(RequestHeaders, Host, Version);
-header_record([{"cache-control", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'cache-control' = Val},
- Host, Version);
-header_record([{"connection", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{connection = Val}, Host,
- Version);
-header_record([{"date", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{date = Val}, Host,
- Version);
-header_record([{"pragma", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{pragma = Val}, Host,
- Version);
-header_record([{"trailer", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{trailer = Val}, Host,
- Version);
-header_record([{"transfer-encoding", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest,
- RequestHeaders#http_request_h{'transfer-encoding' = Val},
- Host, Version);
-header_record([{"upgrade", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{upgrade = Val}, Host,
- Version);
-header_record([{"via", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{via = Val}, Host,
- Version);
-header_record([{"warning", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{warning = Val}, Host,
- Version);
-header_record([{"accept", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{accept = Val}, Host,
- Version);
-header_record([{"accept-charset", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'accept-charset' = Val},
- Host, Version);
-header_record([{"accept-encoding", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'accept-encoding' = Val},
- Host, Version);
-header_record([{"accept-language", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'accept-language' = Val},
- Host, Version);
-header_record([{"authorization", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{authorization = Val},
- Host, Version);
-header_record([{"expect", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{expect = Val}, Host,
- Version);
-header_record([{"from", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{from = Val}, Host,
- Version);
-header_record([{"host", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{host = Val}, Host,
- Version);
-header_record([{"if-match", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'if-match' = Val},
- Host, Version);
-header_record([{"if-modified-since", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest,
- RequestHeaders#http_request_h{'if-modified-since' = Val},
- Host, Version);
-header_record([{"if-none-match", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'if-none-match' = Val},
- Host, Version);
-header_record([{"if-range", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'if-range' = Val},
- Host, Version);
-
-header_record([{"if-unmodified-since", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'if-unmodified-since'
- = Val}, Host, Version);
-header_record([{"max-forwards", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'max-forwards' = Val},
- Host, Version);
-header_record([{"proxy-authorization", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'proxy-authorization'
- = Val}, Host, Version);
-header_record([{"range", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{range = Val}, Host,
- Version);
-header_record([{"referer", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{referer = Val}, Host,
- Version);
-header_record([{"te", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{te = Val}, Host,
- Version);
-header_record([{"user-agent", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'user-agent' = Val},
- Host, Version);
-header_record([{"allow", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{allow = Val}, Host,
- Version);
-header_record([{"content-encoding", Val} | Rest], RequestHeaders, Host,
- Version) ->
- header_record(Rest,
- RequestHeaders#http_request_h{'content-encoding' = Val},
- Host, Version);
-header_record([{"content-language", Val} | Rest], RequestHeaders,
- Host, Version) ->
- header_record(Rest,
- RequestHeaders#http_request_h{'content-language' = Val},
- Host, Version);
-header_record([{"content-length", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'content-length' = Val},
- Host, Version);
-header_record([{"content-location", Val} | Rest], RequestHeaders,
- Host, Version) ->
- header_record(Rest,
- RequestHeaders#http_request_h{'content-location' = Val},
- Host, Version);
-header_record([{"content-md5", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'content-md5' = Val},
- Host, Version);
-header_record([{"content-range", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'content-range' = Val},
- Host, Version);
-header_record([{"content-type", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'content-type' = Val},
- Host, Version);
-header_record([{"expires", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{expires = Val}, Host,
- Version);
-header_record([{"last-modified", Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{'last-modified' = Val},
- Host, Version);
-header_record([{Key, Val} | Rest], RequestHeaders, Host, Version) ->
- header_record(Rest, RequestHeaders#http_request_h{
- other = [{Key, Val} |
- RequestHeaders#http_request_h.other]},
- Host, Version).
+stream_next(Pid) ->
+ httpc:stream_next(Pid).
-validate_headers(RequestHeaders = #http_request_h{te = undefined}, Host,
- "HTTP/1.1" = Version) ->
- validate_headers(RequestHeaders#http_request_h{te = ""}, Host,
- "HTTP/1.1" = Version);
-validate_headers(RequestHeaders = #http_request_h{host = undefined},
- Host, "HTTP/1.1" = Version) ->
- validate_headers(RequestHeaders#http_request_h{host = Host}, Host, Version);
-validate_headers(RequestHeaders, _, _) ->
- RequestHeaders.
+%%--------------------------------------------------------------------------
+%% default_profile()
+%%-------------------------------------------------------------------------
default_profile() ->
- ?DEFAULT_PROFILE.
-
-profile_name(?DEFAULT_PROFILE) ->
- httpc_manager;
-profile_name(Pid) when is_pid(Pid) ->
- Pid;
-profile_name(Profile) ->
- list_to_atom("httpc_manager_" ++ atom_to_list(Profile)).
-
-child_name2info(undefined) ->
- {error, no_such_service};
-child_name2info(httpc_manager) ->
- {ok, [{profile, default}]};
-child_name2info({http, Profile}) ->
- {ok, [{profile, Profile}]}.
-
-child_name(_, []) ->
- undefined;
-child_name(Pid, [{Name, Pid} | _]) ->
- Name;
-child_name(Pid, [_ | Children]) ->
- child_name(Pid, Children).
-
-%% d(F) ->
-%% d(F, []).
-
-%% d(F, A) ->
-%% d(get(dbg), F, A).
-
-%% d(true, F, A) ->
-%% io:format(user, "~w:~w:" ++ F ++ "~n", [self(), ?MODULE | A]);
-%% d(_, _, _) ->
-%% ok.
-
+ httpc:default_profile().
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
new file mode 100644
index 0000000000..c4ee4f1fda
--- /dev/null
+++ b/lib/inets/src/http_client/httpc.erl
@@ -0,0 +1,1030 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+%% Description:
+%%% This version of the HTTP/1.1 client supports:
+%%% - RFC 2616 HTTP 1.1 client part
+%%% - RFC 2818 HTTP Over TLS
+
+-module(httpc).
+
+-behaviour(inets_service).
+
+%% API
+-export([request/1, request/2, request/4, request/5,
+ cancel_request/1, cancel_request/2,
+ set_option/2, set_option/3,
+ set_options/1, set_options/2,
+ store_cookies/2, store_cookies/3,
+ cookie_header/1, cookie_header/2,
+ which_cookies/0, which_cookies/1,
+ reset_cookies/0, reset_cookies/1,
+ stream_next/1,
+ default_profile/0,
+ profile_name/1, profile_name/2]).
+
+%% Behavior callbacks
+-export([start_standalone/1, start_service/1,
+ stop_service/1,
+ services/0, service_info/1]).
+
+-include("http_internal.hrl").
+-include("httpc_internal.hrl").
+
+-define(DEFAULT_PROFILE, default).
+
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+default_profile() ->
+ ?DEFAULT_PROFILE.
+
+
+profile_name(?DEFAULT_PROFILE) ->
+ httpc_manager;
+profile_name(Profile) ->
+ profile_name("httpc_manager_", Profile).
+
+profile_name(Prefix, Profile) when is_atom(Profile) ->
+ list_to_atom(Prefix ++ atom_to_list(Profile));
+profile_name(Prefix, Profile) when is_pid(Profile) ->
+ ProfileStr0 =
+ string:strip(string:strip(erlang:pid_to_list(Profile), left, $<), right, $>),
+ F = fun($.) -> $_; (X) -> X end,
+ ProfileStr = [F(C) || C <- ProfileStr0],
+ list_to_atom(Prefix ++ "pid_" ++ ProfileStr).
+
+
+%%--------------------------------------------------------------------------
+%% request(Url) -> {ok, {StatusLine, Headers, Body}} | {error,Reason}
+%% request(Url Profile) ->
+%% {ok, {StatusLine, Headers, Body}} | {error,Reason}
+%%
+%% Url - string()
+%% Description: Calls request/4 with default values.
+%%--------------------------------------------------------------------------
+
+request(Url) ->
+ request(Url, default_profile()).
+
+request(Url, Profile) ->
+ request(get, {Url, []}, [], [], Profile).
+
+
+%%--------------------------------------------------------------------------
+%% request(Method, Request, HTTPOptions, Options [, Profile]) ->
+%% {ok, {StatusLine, Headers, Body}} | {ok, {Status, Body}} |
+%% {ok, RequestId} | {error,Reason} | {ok, {saved_as, FilePath}
+%%
+%% Method - atom() = head | get | put | post | trace | options| delete
+%% Request - {Url, Headers} | {Url, Headers, ContentType, Body}
+%% Url - string()
+%% HTTPOptions - [HttpOption]
+%% HTTPOption - {timeout, Time} | {connect_timeout, Time} |
+%% {ssl, SSLOptions} | {proxy_auth, {User, Password}}
+%% Ssloptions = [SSLOption]
+%% SSLOption = {verify, code()} | {depth, depth()} | {certfile, path()} |
+%% {keyfile, path()} | {password, string()} | {cacertfile, path()} |
+%% {ciphers, string()}
+%% Options - [Option]
+%% Option - {sync, Boolean} | {body_format, BodyFormat} |
+%% {full_result, Boolean} | {stream, To} |
+%% {headers_as_is, Boolean}
+%% StatusLine = {HTTPVersion, StatusCode, ReasonPhrase}</v>
+%% HTTPVersion = string()
+%% StatusCode = integer()
+%% ReasonPhrase = string()
+%% Headers = [Header]
+%% Header = {Field, Value}
+%% Field = string()
+%% Value = string()
+%% Body = string() | binary() - HTLM-code
+%%
+%% Description: Sends a HTTP-request. The function can be both
+%% syncronus and asynchronous in the later case the function will
+%% return {ok, RequestId} and later on a message will be sent to the
+%% calling process on the format {http, {RequestId, {StatusLine,
+%% Headers, Body}}} or {http, {RequestId, {error, Reason}}}
+%%--------------------------------------------------------------------------
+
+request(Method, Request, HttpOptions, Options) ->
+ request(Method, Request, HttpOptions, Options, default_profile()).
+
+request(Method, {Url, Headers}, HTTPOptions, Options, Profile)
+ when (Method =:= options) orelse
+ (Method =:= get) orelse
+ (Method =:= head) orelse
+ (Method =:= delete) orelse
+ (Method =:= trace) andalso
+ (is_atom(Profile) orelse is_pid(Profile)) ->
+ ?hcrt("request", [{method, Method},
+ {url, Url},
+ {headers, Headers},
+ {http_options, HTTPOptions},
+ {options, Options},
+ {profile, Profile}]),
+ case http_uri:parse(Url) of
+ {error, Reason} ->
+ {error, Reason};
+ ParsedUrl ->
+ handle_request(Method, Url, ParsedUrl, Headers, [], [],
+ HTTPOptions, Options, Profile)
+ end;
+
+request(Method, {Url,Headers,ContentType,Body}, HTTPOptions, Options, Profile)
+ when ((Method =:= post) orelse (Method =:= put)) andalso
+ (is_atom(Profile) orelse is_pid(Profile)) ->
+ ?hcrt("request", [{method, Method},
+ {url, Url},
+ {headers, Headers},
+ {content_type, ContentType},
+ {body, Body},
+ {http_options, HTTPOptions},
+ {options, Options},
+ {profile, Profile}]),
+ case http_uri:parse(Url) of
+ {error, Reason} ->
+ {error, Reason};
+ ParsedUrl ->
+ handle_request(Method, Url,
+ ParsedUrl, Headers, ContentType, Body,
+ HTTPOptions, Options, Profile)
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% cancel_request(RequestId) -> ok
+%% cancel_request(RequestId, Profile) -> ok
+%% RequestId - As returned by request/4
+%%
+%% Description: Cancels a HTTP-request.
+%%-------------------------------------------------------------------------
+cancel_request(RequestId) ->
+ cancel_request(RequestId, default_profile()).
+
+cancel_request(RequestId, Profile)
+ when is_atom(Profile) orelse is_pid(Profile) ->
+ ?hcrt("cancel request", [{request_id, RequestId}, {profile, Profile}]),
+ ok = httpc_manager:cancel_request(RequestId, profile_name(Profile)),
+ receive
+ %% If the request was already fulfilled throw away the
+ %% answer as the request has been canceled.
+ {http, {RequestId, _}} ->
+ ok
+ after 0 ->
+ ok
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% set_options(Options) -> ok | {error, Reason}
+%% set_options(Options, Profile) -> ok | {error, Reason}
+%% Options - [Option]
+%% Profile - atom()
+%% Option - {proxy, {Proxy, NoProxy}} | {max_sessions, MaxSessions} |
+%% {max_pipeline_length, MaxPipeline} |
+%% {pipeline_timeout, PipelineTimeout} | {cookies, CookieMode} |
+%% {ipfamily, IpFamily}
+%% Proxy - {Host, Port}
+%% NoProxy - [Domain | HostName | IPAddress]
+%% MaxSessions, MaxPipeline, PipelineTimeout = integer()
+%% CookieMode - enabled | disabled | verify
+%% IpFamily - inet | inet6 | inet6fb4
+%% Description: Informs the httpc_manager of the new settings.
+%%-------------------------------------------------------------------------
+set_options(Options) ->
+ set_options(Options, default_profile()).
+set_options(Options, Profile) when is_atom(Profile) orelse is_pid(Profile) ->
+ ?hcrt("set cookies", [{options, Options}, {profile, Profile}]),
+ case validate_options(Options) of
+ {ok, Opts} ->
+ try
+ begin
+ httpc_manager:set_options(Opts, profile_name(Profile))
+ end
+ catch
+ exit:{noproc, _} ->
+ {error, inets_not_started}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+set_option(Key, Value) ->
+ set_option(Key, Value, default_profile()).
+
+set_option(Key, Value, Profile) ->
+ set_options([{Key, Value}], Profile).
+
+
+%%--------------------------------------------------------------------------
+%% store_cookies(SetCookieHeaders, Url [, Profile]) -> ok | {error, reason}
+%%
+%%
+%% Description: Store the cookies from <SetCookieHeaders>
+%% in the cookie database
+%% for the profile <Profile>. This function shall be used when the option
+%% cookie is set to verify.
+%%-------------------------------------------------------------------------
+store_cookies(SetCookieHeaders, Url) ->
+ store_cookies(SetCookieHeaders, Url, default_profile()).
+
+store_cookies(SetCookieHeaders, Url, Profile)
+ when is_atom(Profile) orelse is_pid(Profile) ->
+ ?hcrt("store cookies", [{set_cookie_headers, SetCookieHeaders},
+ {url, Url},
+ {profile, Profile}]),
+ try
+ begin
+ {_, _, Host, Port, Path, _} = http_uri:parse(Url),
+ Address = {Host, Port},
+ ProfileName = profile_name(Profile),
+ Cookies = httpc_cookie:cookies(SetCookieHeaders, Path, Host),
+ httpc_manager:store_cookies(Cookies, Address, ProfileName),
+ ok
+ end
+ catch
+ exit:{noproc, _} ->
+ {error, {not_started, Profile}};
+ error:{badmatch, Bad} ->
+ {error, {parse_failed, Bad}}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% cookie_header(Url [, Profile]) -> Header | {error, Reason}
+%%
+%% Description: Returns the cookie header that would be sent when making
+%% a request to <Url>.
+%%-------------------------------------------------------------------------
+cookie_header(Url) ->
+ cookie_header(Url, default_profile()).
+
+cookie_header(Url, Profile) ->
+ ?hcrt("cookie header", [{url, Url},
+ {profile, Profile}]),
+ try
+ begin
+ httpc_manager:which_cookies(Url, profile_name(Profile))
+ end
+ catch
+ exit:{noproc, _} ->
+ {error, {not_started, Profile}}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% which_cookies() -> [cookie()]
+%% which_cookies(Profile) -> [cookie()]
+%%
+%% Description: Debug function, dumping the cookie database
+%%-------------------------------------------------------------------------
+which_cookies() ->
+ which_cookies(default_profile()).
+
+which_cookies(Profile) ->
+ ?hcrt("which cookies", [{profile, Profile}]),
+ try
+ begin
+ httpc_manager:which_cookies(profile_name(Profile))
+ end
+ catch
+ exit:{noproc, _} ->
+ {error, {not_started, Profile}}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% reset_cookies() -> void()
+%% reset_cookies(Profile) -> void()
+%%
+%% Description: Debug function, reset the cookie database
+%%-------------------------------------------------------------------------
+reset_cookies() ->
+ reset_cookies(default_profile()).
+
+reset_cookies(Profile) ->
+ ?hcrt("reset cookies", [{profile, Profile}]),
+ try
+ begin
+ httpc_manager:reset_cookies(profile_name(Profile))
+ end
+ catch
+ exit:{noproc, _} ->
+ {error, {not_started, Profile}}
+ end.
+
+
+%%--------------------------------------------------------------------------
+%% stream_next(Pid) -> Header | {error, Reason}
+%%
+%% Description: Triggers the next message to be streamed, e.i.
+%% same behavior as active once for sockets.
+%%-------------------------------------------------------------------------
+stream_next(Pid) ->
+ ?hcrt("stream next", [{handler, Pid}]),
+ httpc_handler:stream_next(Pid).
+
+
+%%%========================================================================
+%%% Behaviour callbacks
+%%%========================================================================
+start_standalone(PropList) ->
+ ?hcrt("start standalone", [{proplist, PropList}]),
+ case proplists:get_value(profile, PropList) of
+ undefined ->
+ {error, no_profile};
+ Profile ->
+ Dir =
+ proplists:get_value(data_dir, PropList, only_session_cookies),
+ httpc_manager:start_link(Profile, Dir, stand_alone)
+ end.
+
+start_service(Config) ->
+ ?hcrt("start service", [{config, Config}]),
+ httpc_profile_sup:start_child(Config).
+
+stop_service(Profile) when is_atom(Profile) ->
+ ?hcrt("stop service", [{profile, Profile}]),
+ httpc_profile_sup:stop_child(Profile);
+stop_service(Pid) when is_pid(Pid) ->
+ ?hcrt("stop service", [{pid, Pid}]),
+ case service_info(Pid) of
+ {ok, [{profile, Profile}]} ->
+ stop_service(Profile);
+ Error ->
+ Error
+ end.
+
+services() ->
+ [{httpc, Pid} || {_, Pid, _, _} <-
+ supervisor:which_children(httpc_profile_sup)].
+service_info(Pid) ->
+ try [{ChildName, ChildPid} ||
+ {ChildName, ChildPid, _, _} <-
+ supervisor:which_children(httpc_profile_sup)] of
+ Children ->
+ child_name2info(child_name(Pid, Children))
+ catch
+ exit:{noproc, _} ->
+ {error, service_not_available}
+ end.
+
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+
+handle_request(Method, Url,
+ {Scheme, UserInfo, Host, Port, Path, Query},
+ Headers, ContentType, Body,
+ HTTPOptions0, Options0, Profile) ->
+
+ Started = http_util:timestamp(),
+ NewHeaders = [{http_util:to_lower(Key), Val} || {Key, Val} <- Headers],
+
+ try
+ begin
+ HTTPOptions = http_options(HTTPOptions0),
+ Options = request_options(Options0),
+ Sync = proplists:get_value(sync, Options),
+ Stream = proplists:get_value(stream, Options),
+ HeadersRecord =
+ header_record(NewHeaders,
+ #http_request_h{},
+ header_host(Host, Port),
+ HTTPOptions#http_options.version),
+ Receiver = proplists:get_value(receiver, Options),
+ Request = #request{from = Receiver,
+ scheme = Scheme,
+ address = {Host,Port},
+ path = Path,
+ pquery = Query,
+ method = Method,
+ headers = HeadersRecord,
+ content = {ContentType,Body},
+ settings = HTTPOptions,
+ abs_uri = Url,
+ userinfo = UserInfo,
+ stream = Stream,
+ headers_as_is = headers_as_is(Headers, Options),
+ started = Started},
+ case httpc_manager:request(Request, profile_name(Profile)) of
+ {ok, RequestId} ->
+ handle_answer(RequestId, Sync, Options);
+ {error, Reason} ->
+ {error, Reason}
+ end
+ end
+ catch
+ error:{noproc, _} ->
+ {error, {not_started, Profile}};
+ throw:Error ->
+ Error
+ end.
+
+
+handle_answer(RequestId, false, _) ->
+ {ok, RequestId};
+handle_answer(RequestId, true, Options) ->
+ receive
+ {http, {RequestId, saved_to_file}} ->
+ {ok, saved_to_file};
+ {http, {RequestId, {_,_,_} = Result}} ->
+ return_answer(Options, Result);
+ {http, {RequestId, {error, Reason}}} ->
+ {error, Reason}
+ end.
+
+return_answer(Options, {{"HTTP/0.9",_,_}, _, BinBody}) ->
+ Body = maybe_format_body(BinBody, Options),
+ {ok, Body};
+
+return_answer(Options, {StatusLine, Headers, BinBody}) ->
+
+ Body = maybe_format_body(BinBody, Options),
+
+ case proplists:get_value(full_result, Options, true) of
+ true ->
+ {ok, {StatusLine, Headers, Body}};
+ false ->
+ {_, Status, _} = StatusLine,
+ {ok, {Status, Body}}
+ end.
+
+maybe_format_body(BinBody, Options) ->
+ case proplists:get_value(body_format, Options, string) of
+ string ->
+ binary_to_list(BinBody);
+ _ ->
+ BinBody
+ end.
+
+%% This options is a workaround for http servers that do not follow the
+%% http standard and have case sensative header parsing. Should only be
+%% used if there is no other way to communicate with the server or for
+%% testing purpose.
+headers_as_is(Headers, Options) ->
+ case proplists:get_value(headers_as_is, Options, false) of
+ false ->
+ [];
+ true ->
+ Headers
+ end.
+
+
+http_options(HttpOptions) ->
+ HttpOptionsDefault = http_options_default(),
+ http_options(HttpOptionsDefault, HttpOptions, #http_options{}).
+
+http_options([], [], Acc) ->
+ Acc;
+http_options([], HttpOptions, Acc) ->
+ Fun = fun(BadOption) ->
+ Report = io_lib:format("Invalid option ~p ignored ~n",
+ [BadOption]),
+ error_logger:info_report(Report)
+ end,
+ lists:foreach(Fun, HttpOptions),
+ Acc;
+http_options([{Tag, Default, Idx, Post} | Defaults], HttpOptions, Acc) ->
+ case lists:keysearch(Tag, 1, HttpOptions) of
+ {value, {Tag, Val0}} ->
+ case Post(Val0) of
+ {ok, Val} ->
+ Acc2 = setelement(Idx, Acc, Val),
+ HttpOptions2 = lists:keydelete(Tag, 1, HttpOptions),
+ http_options(Defaults, HttpOptions2, Acc2);
+ error ->
+ Report = io_lib:format("Invalid option ~p:~p ignored ~n",
+ [Tag, Val0]),
+ error_logger:info_report(Report),
+ HttpOptions2 = lists:keydelete(Tag, 1, HttpOptions),
+ http_options(Defaults, HttpOptions2, Acc)
+ end;
+ false ->
+ DefaultVal =
+ case Default of
+ {value, Val} ->
+ Val;
+ {field, DefaultIdx} ->
+ element(DefaultIdx, Acc)
+ end,
+ Acc2 = setelement(Idx, Acc, DefaultVal),
+ http_options(Defaults, HttpOptions, Acc2)
+ end.
+
+http_options_default() ->
+ VersionPost =
+ fun(Value) when is_atom(Value) ->
+ {ok, http_util:to_upper(atom_to_list(Value))};
+ (Value) when is_list(Value) ->
+ {ok, http_util:to_upper(Value)};
+ (_) ->
+ error
+ end,
+ TimeoutPost = fun(Value) when is_integer(Value) andalso (Value >= 0) ->
+ {ok, Value};
+ (infinity = Value) ->
+ {ok, Value};
+ (_) ->
+ error
+ end,
+ AutoRedirectPost = fun(Value) when (Value =:= true) orelse
+ (Value =:= false) ->
+ {ok, Value};
+ (_) ->
+ error
+ end,
+ SslPost = fun(Value) when is_list(Value) ->
+ {ok, Value};
+ (_) ->
+ error
+ end,
+ ProxyAuthPost = fun({User, Passwd} = Value) when is_list(User) andalso
+ is_list(Passwd) ->
+ {ok, Value};
+ (_) ->
+ error
+ end,
+ RelaxedPost = fun(Value) when (Value =:= true) orelse
+ (Value =:= false) ->
+ {ok, Value};
+ (_) ->
+ error
+ end,
+ ConnTimeoutPost =
+ fun(Value) when is_integer(Value) andalso (Value >= 0) ->
+ {ok, Value};
+ (infinity = Value) ->
+ {ok, Value};
+ (_) ->
+ error
+ end,
+ [
+ {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
+ {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
+ {autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
+ {ssl, {value, []}, #http_options.ssl, SslPost},
+ {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
+ {relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
+ %% this field has to be *after* the timeout field (as that field is used for the default value)
+ {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost}
+ ].
+
+request_options_defaults() ->
+ VerifyBoolean =
+ fun(Value) when ((Value =:= true) orelse (Value =:= false)) ->
+ ok;
+ (_) ->
+ error
+ end,
+
+ VerifySync = VerifyBoolean,
+
+ VerifyStream =
+ fun(none = _Value) ->
+ ok;
+ (self = _Value) ->
+ ok;
+ ({self, once} = _Value) ->
+ ok;
+ (Value) when is_list(Value) ->
+ ok;
+ (_) ->
+ error
+ end,
+
+ VerifyBodyFormat =
+ fun(string = _Value) ->
+ ok;
+ (binary = _Value) ->
+ ok;
+ (_) ->
+ error
+ end,
+
+ VerifyFullResult = VerifyBoolean,
+
+ VerifyHeaderAsIs = VerifyBoolean,
+
+ VerifyReceiver =
+ fun(Value) when is_pid(Value) ->
+ ok;
+ ({M, F, A}) when (is_atom(M) andalso
+ is_atom(F) andalso
+ is_list(A)) ->
+ ok;
+ (Value) when is_function(Value, 1) ->
+ ok;
+ (_) ->
+ error
+ end,
+
+ [
+ {sync, true, VerifySync},
+ {stream, none, VerifyStream},
+ {body_format, string, VerifyBodyFormat},
+ {full_result, true, VerifyFullResult},
+ {headers_as_is, false, VerifyHeaderAsIs},
+ {receiver, self(), VerifyReceiver}
+ ].
+
+request_options(Options) ->
+ Defaults = request_options_defaults(),
+ request_options(Defaults, Options, []).
+
+request_options([], [], Acc) ->
+ request_options_sanity_check(Acc),
+ lists:reverse(Acc);
+request_options([], Options, Acc) ->
+ Fun = fun(BadOption) ->
+ Report = io_lib:format("Invalid option ~p ignored ~n",
+ [BadOption]),
+ error_logger:info_report(Report)
+ end,
+ lists:foreach(Fun, Options),
+ Acc;
+request_options([{Key, DefaultVal, Verify} | Defaults], Options, Acc) ->
+ case lists:keysearch(Key, 1, Options) of
+ {value, {Key, Value}} ->
+ case Verify(Value) of
+ ok ->
+ Options2 = lists:keydelete(Key, 1, Options),
+ request_options(Defaults, Options2, [{Key, Value} | Acc]);
+ error ->
+ Report = io_lib:format("Invalid option ~p:~p ignored ~n",
+ [Key, Value]),
+ error_logger:info_report(Report),
+ Options2 = lists:keydelete(Key, 1, Options),
+ request_options(Defaults, Options2, Acc)
+ end;
+ false ->
+ request_options(Defaults, Options, [{Key, DefaultVal} | Acc])
+ end.
+
+request_options_sanity_check(Opts) ->
+ case proplists:get_value(sync, Opts) of
+ Sync when (Sync =:= true) ->
+ case proplists:get_value(receiver, Opts) of
+ Pid when is_pid(Pid) andalso (Pid =:= self()) ->
+ ok;
+ BadReceiver ->
+ throw({error, {bad_options_combo,
+ [{sync, true}, {receiver, BadReceiver}]}})
+ end,
+ case proplists:get_value(stream, Opts) of
+ Stream when (Stream =:= self) orelse
+ (Stream =:= {self, once}) ->
+ throw({error, streaming_error});
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ ok.
+
+validate_options(Options) ->
+ (catch validate_options(Options, [])).
+
+validate_options([], ValidateOptions) ->
+ {ok, lists:reverse(ValidateOptions)};
+
+validate_options([{proxy, Proxy} = Opt| Tail], Acc) ->
+ validate_proxy(Proxy),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{max_sessions, Value} = Opt| Tail], Acc) ->
+ validate_max_sessions(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{keep_alive_timeout, Value} = Opt| Tail], Acc) ->
+ validate_keep_alive_timeout(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{max_keep_alive_length, Value} = Opt| Tail], Acc) ->
+ validate_max_keep_alive_length(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{pipeline_timeout, Value} = Opt| Tail], Acc) ->
+ validate_pipeline_timeout(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{max_pipeline_length, Value} = Opt| Tail], Acc) ->
+ validate_max_pipeline_length(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{cookies, Value} = Opt| Tail], Acc) ->
+ validate_cookies(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{ipfamily, Value} = Opt| Tail], Acc) ->
+ validate_ipfamily(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+%% For backward compatibillity
+validate_options([{ipv6, Value}| Tail], Acc) ->
+ NewValue = validate_ipv6(Value),
+ Opt = {ipfamily, NewValue},
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{ip, Value} = Opt| Tail], Acc) ->
+ validate_ip(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{port, Value} = Opt| Tail], Acc) ->
+ validate_port(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{verbose, Value} = Opt| Tail], Acc) ->
+ validate_verbose(Value),
+ validate_options(Tail, [Opt | Acc]);
+
+validate_options([{_, _} = Opt| _], _Acc) ->
+ {error, {not_an_option, Opt}}.
+
+
+validate_proxy({{ProxyHost, ProxyPort}, NoProxy} = Proxy)
+ when is_list(ProxyHost) andalso
+ is_integer(ProxyPort) andalso
+ is_list(NoProxy) ->
+ Proxy;
+validate_proxy(BadProxy) ->
+ bad_option(proxy, BadProxy).
+
+validate_max_sessions(Value) when is_integer(Value) andalso (Value >= 0) ->
+ Value;
+validate_max_sessions(BadValue) ->
+ bad_option(max_sessions, BadValue).
+
+validate_keep_alive_timeout(Value) when is_integer(Value) andalso (Value >= 0) ->
+ Value;
+validate_keep_alive_timeout(infinity = Value) ->
+ Value;
+validate_keep_alive_timeout(BadValue) ->
+ bad_option(keep_alive_timeout, BadValue).
+
+validate_max_keep_alive_length(Value) when is_integer(Value) andalso (Value >= 0) ->
+ Value;
+validate_max_keep_alive_length(BadValue) ->
+ bad_option(max_keep_alive_length, BadValue).
+
+validate_pipeline_timeout(Value) when is_integer(Value) ->
+ Value;
+validate_pipeline_timeout(infinity = Value) ->
+ Value;
+validate_pipeline_timeout(BadValue) ->
+ bad_option(pipeline_timeout, BadValue).
+
+validate_max_pipeline_length(Value) when is_integer(Value) ->
+ Value;
+validate_max_pipeline_length(BadValue) ->
+ bad_option(max_pipeline_length, BadValue).
+
+validate_cookies(Value)
+ when ((Value =:= enabled) orelse
+ (Value =:= disabled) orelse
+ (Value =:= verify)) ->
+ Value;
+validate_cookies(BadValue) ->
+ bad_option(cookies, BadValue).
+
+validate_ipv6(Value) when (Value =:= enabled) orelse (Value =:= disabled) ->
+ case Value of
+ enabled ->
+ inet6fb4;
+ disabled ->
+ inet
+ end;
+validate_ipv6(BadValue) ->
+ bad_option(ipv6, BadValue).
+
+validate_ipfamily(Value)
+ when (Value =:= inet) orelse (Value =:= inet6) orelse (Value =:= inet6fb4) ->
+ Value;
+validate_ipfamily(BadValue) ->
+ bad_option(ipfamily, BadValue).
+
+validate_ip(Value)
+ when is_tuple(Value) andalso ((size(Value) =:= 4) orelse (size(Value) =:= 8)) ->
+ Value;
+validate_ip(BadValue) ->
+ bad_option(ip, BadValue).
+
+validate_port(Value) when is_integer(Value) ->
+ Value;
+validate_port(BadValue) ->
+ bad_option(port, BadValue).
+
+validate_verbose(Value)
+ when ((Value =:= false) orelse
+ (Value =:= verbose) orelse
+ (Value =:= debug) orelse
+ (Value =:= trace)) ->
+ ok;
+validate_verbose(BadValue) ->
+ bad_option(verbose, BadValue).
+
+bad_option(Option, BadValue) ->
+ throw({error, {bad_option, Option, BadValue}}).
+
+
+header_host(Host, 80 = _Port) ->
+ Host;
+header_host(Host, Port) ->
+ Host ++ ":" ++ integer_to_list(Port).
+
+
+header_record([], RequestHeaders, Host, Version) ->
+ validate_headers(RequestHeaders, Host, Version);
+header_record([{"cache-control", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'cache-control' = Val},
+ Host, Version);
+header_record([{"connection", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{connection = Val}, Host,
+ Version);
+header_record([{"date", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{date = Val}, Host,
+ Version);
+header_record([{"pragma", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{pragma = Val}, Host,
+ Version);
+header_record([{"trailer", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{trailer = Val}, Host,
+ Version);
+header_record([{"transfer-encoding", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest,
+ RequestHeaders#http_request_h{'transfer-encoding' = Val},
+ Host, Version);
+header_record([{"upgrade", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{upgrade = Val}, Host,
+ Version);
+header_record([{"via", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{via = Val}, Host,
+ Version);
+header_record([{"warning", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{warning = Val}, Host,
+ Version);
+header_record([{"accept", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{accept = Val}, Host,
+ Version);
+header_record([{"accept-charset", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'accept-charset' = Val},
+ Host, Version);
+header_record([{"accept-encoding", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'accept-encoding' = Val},
+ Host, Version);
+header_record([{"accept-language", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'accept-language' = Val},
+ Host, Version);
+header_record([{"authorization", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{authorization = Val},
+ Host, Version);
+header_record([{"expect", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{expect = Val}, Host,
+ Version);
+header_record([{"from", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{from = Val}, Host,
+ Version);
+header_record([{"host", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{host = Val}, Host,
+ Version);
+header_record([{"if-match", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'if-match' = Val},
+ Host, Version);
+header_record([{"if-modified-since", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest,
+ RequestHeaders#http_request_h{'if-modified-since' = Val},
+ Host, Version);
+header_record([{"if-none-match", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'if-none-match' = Val},
+ Host, Version);
+header_record([{"if-range", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'if-range' = Val},
+ Host, Version);
+
+header_record([{"if-unmodified-since", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'if-unmodified-since'
+ = Val}, Host, Version);
+header_record([{"max-forwards", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'max-forwards' = Val},
+ Host, Version);
+header_record([{"proxy-authorization", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'proxy-authorization'
+ = Val}, Host, Version);
+header_record([{"range", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{range = Val}, Host,
+ Version);
+header_record([{"referer", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{referer = Val}, Host,
+ Version);
+header_record([{"te", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{te = Val}, Host,
+ Version);
+header_record([{"user-agent", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'user-agent' = Val},
+ Host, Version);
+header_record([{"allow", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{allow = Val}, Host,
+ Version);
+header_record([{"content-encoding", Val} | Rest], RequestHeaders, Host,
+ Version) ->
+ header_record(Rest,
+ RequestHeaders#http_request_h{'content-encoding' = Val},
+ Host, Version);
+header_record([{"content-language", Val} | Rest], RequestHeaders,
+ Host, Version) ->
+ header_record(Rest,
+ RequestHeaders#http_request_h{'content-language' = Val},
+ Host, Version);
+header_record([{"content-length", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'content-length' = Val},
+ Host, Version);
+header_record([{"content-location", Val} | Rest], RequestHeaders,
+ Host, Version) ->
+ header_record(Rest,
+ RequestHeaders#http_request_h{'content-location' = Val},
+ Host, Version);
+header_record([{"content-md5", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'content-md5' = Val},
+ Host, Version);
+header_record([{"content-range", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'content-range' = Val},
+ Host, Version);
+header_record([{"content-type", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'content-type' = Val},
+ Host, Version);
+header_record([{"expires", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{expires = Val}, Host,
+ Version);
+header_record([{"last-modified", Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{'last-modified' = Val},
+ Host, Version);
+header_record([{Key, Val} | Rest], RequestHeaders, Host, Version) ->
+ header_record(Rest, RequestHeaders#http_request_h{
+ other = [{Key, Val} |
+ RequestHeaders#http_request_h.other]},
+ Host, Version).
+
+validate_headers(RequestHeaders = #http_request_h{te = undefined}, Host,
+ "HTTP/1.1" = Version) ->
+ validate_headers(RequestHeaders#http_request_h{te = ""}, Host,
+ "HTTP/1.1" = Version);
+validate_headers(RequestHeaders = #http_request_h{host = undefined},
+ Host, "HTTP/1.1" = Version) ->
+ validate_headers(RequestHeaders#http_request_h{host = Host}, Host, Version);
+validate_headers(RequestHeaders, _, _) ->
+ RequestHeaders.
+
+
+child_name2info(undefined) ->
+ {error, no_such_service};
+child_name2info(httpc_manager) ->
+ {ok, [{profile, default}]};
+child_name2info({httpc, Profile}) ->
+ {ok, [{profile, Profile}]}.
+
+child_name(_, []) ->
+ undefined;
+child_name(Pid, [{Name, Pid} | _]) ->
+ Name;
+child_name(Pid, [_ | Children]) ->
+ child_name(Pid, Children).
+
+%% d(F) ->
+%% d(F, []).
+
+%% d(F, A) ->
+%% d(get(dbg), F, A).
+
+%% d(true, F, A) ->
+%% io:format(user, "~w:~w:" ++ F ++ "~n", [self(), ?MODULE | A]);
+%% d(_, _, _) ->
+%% ok.
+
diff --git a/lib/inets/src/http_client/http_cookie.erl b/lib/inets/src/http_client/httpc_cookie.erl
index e091070f72..586701b4a1 100644
--- a/lib/inets/src/http_client/http_cookie.erl
+++ b/lib/inets/src/http_client/httpc_cookie.erl
@@ -1,164 +1,260 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%% Description: Cookie handling according to RFC 2109
--module(http_cookie).
+-module(httpc_cookie).
-include("httpc_internal.hrl").
--export([header/4, cookies/3, open_cookie_db/1, close_cookie_db/1, insert/2]).
+-export([open_db/3, close_db/1, insert/2, header/4, cookies/3]).
+-export([reset_db/1, which_cookies/1]).
+
+-record(cookie_db, {db, session_db}).
+
%%%=========================================================================
%%% API
%%%=========================================================================
-header(Scheme, {Host, _}, Path, CookieDb) ->
- case lookup_cookies(Host, Path, CookieDb) of
- [] ->
- {"cookie", ""};
- Cookies ->
- {"cookie", cookies_to_string(Scheme, Cookies)}
+
+%%--------------------------------------------------------------------
+%% Func: open_db(DbName, DbDir, SessionDbName) -> #cookie_db{}
+%% Purpose: Create the cookie db
+%%--------------------------------------------------------------------
+
+open_db(_, only_session_cookies, SessionDbName) ->
+ ?hcrt("open (session cookies only) db",
+ [{session_db_name, SessionDbName}]),
+ SessionDb = ets:new(SessionDbName,
+ [protected, bag, {keypos, #http_cookie.domain}]),
+ #cookie_db{session_db = SessionDb};
+
+open_db(Name, Dir, SessionDbName) ->
+ ?hcrt("open db",
+ [{name, Name}, {dir, Dir}, {session_db_name, SessionDbName}]),
+ File = filename:join(Dir, atom_to_list(Name)),
+ case dets:open_file(Name, [{keypos, #http_cookie.domain},
+ {type, bag},
+ {file, File},
+ {ram_file, true}]) of
+ {ok, Db} ->
+ SessionDb = ets:new(SessionDbName,
+ [protected, bag,
+ {keypos, #http_cookie.domain}]),
+ #cookie_db{db = Db, session_db = SessionDb};
+ {error, Reason} ->
+ throw({error, {failed_open_file, Name, File, Reason}})
end.
-cookies(Headers, RequestPath, RequestHost) ->
- Cookies = parse_set_cookies(Headers, {RequestPath, RequestHost}),
- accept_cookies(Cookies, RequestPath, RequestHost).
-
-open_cookie_db({{_, only_session_cookies}, SessionDbName}) ->
- EtsDb = ets:new(SessionDbName, [protected, bag,
- {keypos, #http_cookie.domain}]),
- {undefined, EtsDb};
-
-open_cookie_db({{DbName, Dbdir}, SessionDbName}) ->
- File = filename:join(Dbdir, atom_to_list(DbName)),
- {ok, DetsDb} = dets:open_file(DbName, [{keypos, #http_cookie.domain},
- {type, bag},
- {file, File},
- {ram_file, true}]),
- EtsDb = ets:new(SessionDbName, [protected, bag,
- {keypos, #http_cookie.domain}]),
- {DetsDb, EtsDb}.
-
-close_cookie_db({undefined, EtsDb}) ->
- ets:delete(EtsDb);
-
-close_cookie_db({DetsDb, EtsDb}) ->
- dets:close(DetsDb),
- ets:delete(EtsDb).
+
+%%--------------------------------------------------------------------
+%% Func: reset_db(CookieDb) -> void()
+%% Purpose: Reset (empty) the cookie database
+%%
+%%--------------------------------------------------------------------
+
+reset_db(#cookie_db{db = undefined, session_db = SessionDb}) ->
+ ets:delete_all_objects(SessionDb),
+ ok;
+reset_db(#cookie_db{db = Db, session_db = SessionDb}) ->
+ dets:delete_all_objects(Db),
+ ets:delete_all_objects(SessionDb),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% Func: close_db(CookieDb) -> ok
+%% Purpose: Close the cookie db
+%%--------------------------------------------------------------------
+
+close_db(#cookie_db{db = Db, session_db = SessionDb}) ->
+ ?hcrt("close db", []),
+ maybe_dets_close(Db),
+ ets:delete(SessionDb),
+ ok.
+
+maybe_dets_close(undefined) ->
+ ok;
+maybe_dets_close(Db) ->
+ dets:close(Db).
+
+
+%%--------------------------------------------------------------------
+%% Func: insert(CookieDb) -> ok
+%% Purpose: Close the cookie db
+%%--------------------------------------------------------------------
%% If no persistent cookie database is defined we
%% treat all cookies as if they where session cookies.
-insert(Cookie = #http_cookie{max_age = Int},
- Dbs = {undefined, _}) when is_integer(Int) ->
- insert(Cookie#http_cookie{max_age = session}, Dbs);
-
-insert(Cookie = #http_cookie{domain = Key, name = Name,
- path = Path, max_age = session},
- Db = {_, CookieDb}) ->
- case ets:match_object(CookieDb, #http_cookie{domain = Key,
- name = Name,
- path = Path,
- _ = '_'}) of
+insert(#cookie_db{db = undefined} = CookieDb,
+ #http_cookie{max_age = Int} = Cookie) when is_integer(Int) ->
+ insert(CookieDb, Cookie#http_cookie{max_age = session});
+
+insert(#cookie_db{session_db = SessionDb} = CookieDb,
+ #http_cookie{domain = Key,
+ name = Name,
+ path = Path,
+ max_age = session} = Cookie) ->
+ ?hcrt("insert session cookie", [{cookie, Cookie}]),
+ Pattern = #http_cookie{domain = Key, name = Name, path = Path, _ = '_'},
+ case ets:match_object(SessionDb, Pattern) of
[] ->
- ets:insert(CookieDb, Cookie);
+ ets:insert(SessionDb, Cookie);
[NewCookie] ->
- delete(NewCookie, Db),
- ets:insert(CookieDb, Cookie)
+ delete(CookieDb, NewCookie),
+ ets:insert(SessionDb, Cookie)
end,
ok;
-insert(#http_cookie{domain = Key, name = Name,
- path = Path, max_age = 0},
- Db = {CookieDb, _}) ->
- case dets:match_object(CookieDb, #http_cookie{domain = Key,
- name = Name,
- path = Path,
- _ = '_'}) of
+insert(#cookie_db{db = Db} = CookieDb,
+ #http_cookie{domain = Key,
+ name = Name,
+ path = Path,
+ max_age = 0}) ->
+ ?hcrt("insert", [{domain, Key}, {name, Name}, {path, Path}]),
+ Pattern = #http_cookie{domain = Key, name = Name, path = Path, _ = '_'},
+ case dets:match_object(Db, Pattern) of
[] ->
ok;
[NewCookie] ->
- delete(NewCookie, Db)
+ delete(CookieDb, NewCookie)
end,
ok;
-insert(Cookie = #http_cookie{domain = Key, name = Name, path = Path},
- Db = {CookieDb, _}) ->
- case dets:match_object(CookieDb, #http_cookie{domain = Key,
- name = Name,
- path = Path,
- _ = '_'}) of
+insert(#cookie_db{db = Db} = CookieDb,
+ #http_cookie{domain = Key, name = Name, path = Path} = Cookie) ->
+ ?hcrt("insert", [{cookie, Cookie}]),
+ Pattern = #http_cookie{domain = Key,
+ name = Name,
+ path = Path,
+ _ = '_'},
+ case dets:match_object(Db, Pattern) of
[] ->
- dets:insert(CookieDb, Cookie);
- [NewCookie] ->
- delete(NewCookie, Db),
- dets:insert(CookieDb, Cookie)
+ dets:insert(Db, Cookie);
+ [OldCookie] ->
+ delete(CookieDb, OldCookie),
+ dets:insert(Db, Cookie)
end,
ok.
+
+
+%%--------------------------------------------------------------------
+%% Func: header(CookieDb) -> ok
+%% Purpose: Cookies
+%%--------------------------------------------------------------------
+
+header(CookieDb, Scheme, {Host, _}, Path) ->
+ ?hcrd("header", [{scheme, Scheme}, {host, Host}, {path, Path}]),
+ case lookup_cookies(CookieDb, Host, Path) of
+ [] ->
+ {"cookie", ""};
+ Cookies ->
+ {"cookie", cookies_to_string(Scheme, Cookies)}
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Func: cookies(Headers, RequestPath, RequestHost) -> [cookie()]
+%% Purpose: Which cookies are stored
+%%--------------------------------------------------------------------
+
+cookies(Headers, RequestPath, RequestHost) ->
+ ?hcrt("cookies", [{headers, Headers},
+ {request_path, RequestPath},
+ {request_host, RequestHost}]),
+ Cookies = parse_set_cookies(Headers, {RequestPath, RequestHost}),
+ accept_cookies(Cookies, RequestPath, RequestHost).
+
+
+%%--------------------------------------------------------------------
+%% Func: which_cookies(CookieDb) -> [cookie()]
+%% Purpose: For test and debug purpose,
+%% dump the entire cookie database
+%%--------------------------------------------------------------------
+
+which_cookies(#cookie_db{db = undefined, session_db = SessionDb}) ->
+ SessionCookies = ets:tab2list(SessionDb),
+ [{session_cookies, SessionCookies}];
+which_cookies(#cookie_db{db = Db, session_db = SessionDb}) ->
+ Cookies = dets:match_object(Db, '_'),
+ SessionCookies = ets:tab2list(SessionDb),
+ [{cookies, Cookies}, {session_cookies, SessionCookies}].
+
+
%%%========================================================================
%%% Internal functions
%%%========================================================================
-lookup_cookies(Key, {undefined, Ets}) ->
- ets:match_object(Ets, #http_cookie{domain = Key,
- _ = '_'});
-lookup_cookies(Key, {Dets,Ets}) ->
- SessionCookies = ets:match_object(Ets, #http_cookie{domain = Key,
- _ = '_'}),
- Cookies = dets:match_object(Dets, #http_cookie{domain = Key,
- _ = '_'}),
+
+delete(#cookie_db{session_db = SessionDb},
+ #http_cookie{max_age = session} = Cookie) ->
+ ets:delete_object(SessionDb, Cookie);
+delete(#cookie_db{db = Db}, Cookie) ->
+ dets:delete_object(Db, Cookie).
+
+
+lookup_cookies(#cookie_db{db = undefined, session_db = SessionDb}, Key) ->
+ Pattern = #http_cookie{domain = Key, _ = '_'},
+ Cookies = ets:match_object(SessionDb, Pattern),
+ ?hcrt("lookup cookies", [{cookies, Cookies}]),
+ Cookies;
+
+lookup_cookies(#cookie_db{db = Db, session_db = SessionDb}, Key) ->
+ Pattern = #http_cookie{domain = Key, _ = '_'},
+ SessionCookies = ets:match_object(SessionDb, Pattern),
+ ?hcrt("lookup cookies", [{session_cookies, SessionCookies}]),
+ Cookies = dets:match_object(Db, Pattern),
+ ?hcrt("lookup cookies", [{cookies, Cookies}]),
Cookies ++ SessionCookies.
-delete(Cookie = #http_cookie{max_age = session}, {_, CookieDb}) ->
- ets:delete_object(CookieDb, Cookie);
-delete(Cookie, {CookieDb, _}) ->
- dets:delete_object(CookieDb, Cookie).
-lookup_cookies(Host, Path, Db) ->
+lookup_cookies(CookieDb, Host, Path) ->
Cookies =
case http_util:is_hostname(Host) of
true ->
- HostCookies = lookup_cookies(Host, Db),
+ HostCookies = lookup_cookies(CookieDb, Host),
[_| DomainParts] = string:tokens(Host, "."),
- lookup_domain_cookies(DomainParts, Db, HostCookies);
+ lookup_domain_cookies(CookieDb, DomainParts, HostCookies);
false -> % IP-adress
- lookup_cookies(Host, Db)
+ lookup_cookies(CookieDb, Host)
end,
- ValidCookies = valid_cookies(Cookies, [], Db),
+ ValidCookies = valid_cookies(CookieDb, Cookies),
lists:filter(fun(Cookie) ->
lists:prefix(Cookie#http_cookie.path, Path)
end, ValidCookies).
%% For instance if Host=localhost
-lookup_domain_cookies([], _, AccCookies) ->
+lookup_domain_cookies(_CookieDb, [], AccCookies) ->
lists:flatten(AccCookies);
+
%% Top domains can not have cookies
-lookup_domain_cookies([_], _, AccCookies) ->
+lookup_domain_cookies(_CookieDb, [_], AccCookies) ->
lists:flatten(AccCookies);
-lookup_domain_cookies([Next | DomainParts], CookieDb, AccCookies) ->
+
+lookup_domain_cookies(CookieDb, [Next | DomainParts], AccCookies) ->
Domain = merge_domain_parts(DomainParts, [Next ++ "."]),
- lookup_domain_cookies(DomainParts, CookieDb,
- [lookup_cookies(Domain, CookieDb)
- | AccCookies]).
+ lookup_domain_cookies(CookieDb, DomainParts,
+ [lookup_cookies(CookieDb, Domain) | AccCookies]).
merge_domain_parts([Part], Merged) ->
lists:flatten(["." | lists:reverse([Part | Merged])]);
merge_domain_parts([Part| Rest], Merged) ->
merge_domain_parts(Rest, [".", Part | Merged]).
-cookies_to_string(Scheme, Cookies = [Cookie | _]) ->
+cookies_to_string(Scheme, [Cookie | _] = Cookies) ->
Version = "$Version=" ++ Cookie#http_cookie.version ++ "; ",
cookies_to_string(Scheme, path_sort(Cookies), [Version]).
@@ -170,7 +266,7 @@ cookies_to_string(_, [], CookieStrs) ->
lists:flatten(lists:reverse(CookieStrs))
end;
-cookies_to_string(https, [Cookie = #http_cookie{secure = true}| Cookies],
+cookies_to_string(https, [#http_cookie{secure = true} = Cookie| Cookies],
CookieStrs) ->
Str = case Cookies of
[] ->
@@ -193,7 +289,7 @@ cookies_to_string(Scheme, [Cookie | Cookies], CookieStrs) ->
end,
cookies_to_string(Scheme, Cookies, [Str | CookieStrs]).
-cookie_to_string(Cookie = #http_cookie{name = Name, value = Value}) ->
+cookie_to_string(#http_cookie{name = Name, value = Value} = Cookie) ->
Str = Name ++ "=" ++ Value,
add_domain(add_path(Str, Cookie), Cookie).
@@ -208,19 +304,19 @@ add_domain(Str, #http_cookie{domain = Domain}) ->
Str ++ "; $Domain=" ++ Domain.
parse_set_cookies(OtherHeaders, DefaultPathDomain) ->
- SetCookieHeaders = lists:foldl(fun({"set-cookie", Value}, Acc) ->
- [string:tokens(Value, ",")| Acc];
- (_, Acc) ->
- Acc
- end, [], OtherHeaders),
+ SetCookieHeaders =
+ lists:foldl(fun({"set-cookie", Value}, Acc) ->
+ [string:tokens(Value, ",")| Acc];
+ (_, Acc) ->
+ Acc
+ end, [], OtherHeaders),
- lists:flatten(lists:map(fun(CookieHeader) ->
- NewHeader =
- fix_netscape_cookie(CookieHeader,
- []),
- parse_set_cookie(NewHeader, [],
- DefaultPathDomain) end,
- SetCookieHeaders)).
+ lists:flatten(
+ lists:map(fun(CookieHeader) ->
+ NewHeader = fix_netscape_cookie(CookieHeader, []),
+ parse_set_cookie(NewHeader, [], DefaultPathDomain)
+ end,
+ SetCookieHeaders)).
parse_set_cookie([], AccCookies, _) ->
AccCookies;
@@ -282,16 +378,16 @@ cookie_attributes([{"expires", Value}| Attributes], Cookie) ->
Time = http_util:convert_netscapecookie_date(Value),
ExpireTime = calendar:datetime_to_gregorian_seconds(Time),
cookie_attributes(Attributes,
- Cookie#http_cookie{max_age = ExpireTime});
+ Cookie#http_cookie{max_age = ExpireTime});
cookie_attributes([{"path", Value}| Attributes], Cookie) ->
cookie_attributes(Attributes,
- Cookie#http_cookie{path = Value});
+ Cookie#http_cookie{path = Value});
cookie_attributes([{"secure", _}| Attributes], Cookie) ->
cookie_attributes(Attributes,
- Cookie#http_cookie{secure = true});
+ Cookie#http_cookie{secure = true});
cookie_attributes([{"version", Value}| Attributes], Cookie) ->
cookie_attributes(Attributes,
- Cookie#http_cookie{version = Value});
+ Cookie#http_cookie{version = Value});
%% Disregard unknown attributes.
cookie_attributes([_| Attributes], Cookie) ->
cookie_attributes(Attributes, Cookie).
@@ -302,8 +398,7 @@ domain_default(Cookie = #http_cookie{domain = undefined},
domain_default(Cookie, _) ->
Cookie.
-path_default(Cookie = #http_cookie{path = undefined},
- DefaultPath) ->
+path_default(#http_cookie{path = undefined} = Cookie, DefaultPath) ->
Cookie#http_cookie{path = skip_right_most_slash(DefaultPath),
path_default = true};
path_default(Cookie, _) ->
@@ -321,7 +416,10 @@ accept_cookies(Cookies, RequestPath, RequestHost) ->
end, Cookies).
accept_cookie(Cookie, RequestPath, RequestHost) ->
- accept_path(Cookie, RequestPath) and accept_domain(Cookie, RequestHost).
+ Accepted =
+ accept_path(Cookie, RequestPath) andalso
+ accept_domain(Cookie, RequestHost),
+ Accepted.
accept_path(#http_cookie{path = Path}, RequestPath) ->
lists:prefix(Path, RequestPath).
@@ -330,18 +428,20 @@ accept_domain(#http_cookie{domain = RequestHost}, RequestHost) ->
true;
accept_domain(#http_cookie{domain = Domain}, RequestHost) ->
- HostCheck = case http_util:is_hostname(RequestHost) of
- true ->
- (lists:suffix(Domain, RequestHost) andalso
- (not
- lists:member($.,
- string:substr(RequestHost, 1,
- (length(RequestHost) -
- length(Domain))))));
- false ->
- false
- end,
- HostCheck andalso (hd(Domain) == $.)
+ HostCheck =
+ case http_util:is_hostname(RequestHost) of
+ true ->
+ (lists:suffix(Domain, RequestHost) andalso
+ (not
+ lists:member($.,
+ string:substr(RequestHost, 1,
+ (length(RequestHost) -
+ length(Domain))))));
+ false ->
+ false
+ end,
+ HostCheck
+ andalso (hd(Domain) =:= $.)
andalso (length(string:tokens(Domain, ".")) > 1).
cookie_expires(0) ->
@@ -356,16 +456,20 @@ is_cookie_expired(#http_cookie{max_age = ExpireTime}) ->
NowSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
ExpireTime - NowSec =< 0.
-valid_cookies([], Valid, _) ->
+
+valid_cookies(Db, Cookies) ->
+ valid_cookies(Db, Cookies, []).
+
+valid_cookies(_Db, [], Valid) ->
Valid;
-valid_cookies([Cookie | Cookies], Valid, Db) ->
+valid_cookies(Db, [Cookie | Cookies], Valid) ->
case is_cookie_expired(Cookie) of
true ->
- delete(Cookie, Db),
- valid_cookies(Cookies, Valid, Db);
+ delete(Db, Cookie),
+ valid_cookies(Db, Cookies, Valid);
false ->
- valid_cookies(Cookies, [Cookie | Valid], Db)
+ valid_cookies(Db, Cookies, [Cookie | Valid])
end.
path_sort(Cookies)->
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index 7b737c2f86..25f9b0777f 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -28,7 +28,8 @@
%%--------------------------------------------------------------------
%% Internal Application API
--export([start_link/3, send/2, cancel/2, stream/3, stream_next/1]).
+-export([start_link/2, connect_and_send/2,
+ send/2, cancel/2, stream/3, stream_next/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -50,7 +51,7 @@
mfa, % {Moduel, Function, Args}
pipeline = queue:new(), % queue()
keep_alive = queue:new(), % queue()
- status = new, % new | pipeline | keep_alive | close | ssl_tunnel
+ status, % undefined | new | pipeline | keep_alive | close | ssl_tunnel
canceled = [], % [RequestId]
max_header_size = nolimit, % nolimit | integer()
max_body_size = nolimit, % nolimit | integer()
@@ -85,9 +86,13 @@
%% the reply or part of it has arrived.)
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
-start_link(Request, Options, ProfileName) ->
- {ok, proc_lib:spawn_link(?MODULE, init, [[Request, Options,
- ProfileName]])}.
+
+start_link(Options, ProfileName) ->
+ Args = [Options, ProfileName],
+ gen_server:start_link(?MODULE, Args, []).
+
+connect_and_send(Request, HandlerPid) ->
+ call({connect_and_send, Request}, HandlerPid).
%%--------------------------------------------------------------------
@@ -192,10 +197,9 @@ stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: init([Request, Options, ProfileName]) -> {ok, State} |
-%% {ok, State, Timeout} | ignore |{stop, Reason}
+%% Function: init([Options, ProfileName]) -> {ok, State} |
+%% {ok, State, Timeout} | ignore | {stop, Reason}
%%
-%% Request = #request{}
%% Options = #options{}
%% ProfileName = atom() - id of httpc manager process
%%
@@ -206,30 +210,16 @@ stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed
%% but we do not want that so errors will be handled by the process
%% sending an init_error message to itself.
%%--------------------------------------------------------------------
-init([Request, Options, ProfileName]) ->
+init([Options, ProfileName]) ->
+ ?hcrv("init - starting", [{options, Options}, {profile, ProfileName}]),
process_flag(trap_exit, true),
-
handle_verbose(Options#options.verbose),
- Address = handle_proxy(Request#request.address, Options#options.proxy),
- {ok, State} =
- case {Address /= Request#request.address, Request#request.scheme} of
- {true, https} ->
- Error = https_through_proxy_is_not_currently_supported,
- self() ! {init_error,
- Error, httpc_response:error(Request, Error)},
- {ok, #state{request = Request, options = Options,
- status = ssl_tunnel}};
- %% This is what we should do if and when ssl supports
- %% "socket upgrading"
- %%send_ssl_tunnel_request(Address, Request,
- %% #state{options = Options,
- %% status = ssl_tunnel});
- {_, _} ->
- send_first_request(Address, Request,
- #state{options = Options,
- profile_name = ProfileName})
- end,
- gen_server:enter_loop(?MODULE, [], State).
+ State = #state{status = undefined,
+ options = Options,
+ profile_name = ProfileName},
+ ?hcrd("init - started", []),
+ {ok, State}.
+
%%--------------------------------------------------------------------
%% Function: handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -240,39 +230,85 @@ init([Request, Options, ProfileName]) ->
%% {stop, Reason, State} (terminate/2 is called)
%% Description: Handling call messages
%%--------------------------------------------------------------------
-handle_call(Request, _, State = #state{session = Session =
- #tcp_session{socket = Socket,
- type = pipeline},
- timers = Timers,
- options = Options,
- profile_name = ProfileName}) ->
+
+
+%% This is the first request, the reason the proc was started
+handle_call({connect_and_send, #request{address = Address0,
+ scheme = Scheme} = Request},
+ _From,
+ #state{options = #options{proxy = Proxy},
+ status = undefined,
+ session = undefined} = State) ->
+ ?hcrv("connect and send", [{address0, Address0}, {proxy, Proxy}]),
+ Address = handle_proxy(Address0, Proxy),
+ if
+ ((Address =/= Address0) andalso (Scheme =:= https)) ->
+ %% This is what we should do if and when ssl supports
+ %% "socket upgrading"
+ %%send_ssl_tunnel_request(Address, Request,
+ %% #state{options = Options,
+ %% status = ssl_tunnel});
+ Reason = https_through_proxy_is_not_currently_supported,
+ Error = {error, Reason},
+ {stop, Error, Error, State};
+ true ->
+ case connect_and_send_first_request(Address, Request, State) of
+ {ok, NewState} ->
+ {reply, ok, NewState};
+ {stop, Error, NewState} ->
+ {stop, Error, Error, NewState}
+ end
+ end;
+
+handle_call(Request, _,
+ #state{status = Status,
+ session = #tcp_session{socket = Socket,
+ type = pipeline} = Session,
+ timers = Timers,
+ options = Options,
+ profile_name = ProfileName} = State)
+ when Status =/= undefined ->
+
+ ?hcrv("new request", [{request, Request},
+ {profile, ProfileName},
+ {status, Status},
+ {session_type, pipeline},
+ {timers, Timers}]),
+
Address = handle_proxy(Request#request.address, Options#options.proxy),
case httpc_request:send(Address, Request, Socket) of
ok ->
+
+ ?hcrd("request sent", []),
+
%% Activate the request time out for the new request
- NewState = activate_request_timeout(State#state{request =
- Request}),
+ NewState =
+ activate_request_timeout(State#state{request = Request}),
+
+ ClientClose =
+ httpc_request:is_client_closing(Request#request.headers),
- ClientClose = httpc_request:is_client_closing(
- Request#request.headers),
case State#state.request of
- #request{} -> %% Old request no yet finished
+ #request{} -> %% Old request not yet finished
+ ?hcrd("old request still not finished", []),
%% Make sure to use the new value of timers in state
- NewTimers = NewState#state.timers,
+ NewTimers = NewState#state.timers,
NewPipeline = queue:in(Request, State#state.pipeline),
- NewSession =
+ NewSession =
Session#tcp_session{queue_length =
%% Queue + current
queue:len(NewPipeline) + 1,
client_close = ClientClose},
httpc_manager:insert_session(NewSession, ProfileName),
+ ?hcrd("session updated", []),
{reply, ok, State#state{pipeline = NewPipeline,
- session = NewSession,
- timers = NewTimers}};
+ session = NewSession,
+ timers = NewTimers}};
undefined ->
- %% Note: tcp-message reciving has already been
+ %% Note: tcp-message receiving has already been
%% activated by handle_pipeline/2.
+ ?hcrd("no current request", []),
cancel_timer(Timers#timers.queue_timer,
timeout_queue),
NewSession =
@@ -281,54 +317,67 @@ handle_call(Request, _, State = #state{session = Session =
httpc_manager:insert_session(NewSession, ProfileName),
Relaxed =
(Request#request.settings)#http_options.relaxed,
- {reply, ok,
- NewState#state{request = Request,
- session = NewSession,
- mfa = {httpc_response, parse,
- [State#state.max_header_size,
- Relaxed]},
- timers =
- Timers#timers{queue_timer =
- undefined}}}
+ MFA = {httpc_response, parse,
+ [State#state.max_header_size, Relaxed]},
+ NewTimers = Timers#timers{queue_timer = undefined},
+ ?hcrd("session created", []),
+ {reply, ok, NewState#state{request = Request,
+ session = NewSession,
+ mfa = MFA,
+ timers = NewTimers}}
end;
{error, Reason} ->
+ ?hcri("failed sending request", [{reason, Reason}]),
{reply, {pipeline_failed, Reason}, State}
end;
-handle_call(Request, _, #state{session = Session =
- #tcp_session{type = keep_alive,
- socket = Socket},
- timers = Timers,
- options = Options,
- profile_name = ProfileName} = State) ->
-
- ClientClose = httpc_request:is_client_closing(Request#request.headers),
+handle_call(Request, _,
+ #state{status = Status,
+ session = #tcp_session{socket = Socket,
+ type = keep_alive} = Session,
+ timers = Timers,
+ options = Options,
+ profile_name = ProfileName} = State)
+ when Status =/= undefined ->
- Address = handle_proxy(Request#request.address,
- Options#options.proxy),
+ ?hcrv("new request", [{request, Request},
+ {profile, ProfileName},
+ {status, Status},
+ {session_type, keep_alive}]),
+
+ Address = handle_proxy(Request#request.address, Options#options.proxy),
case httpc_request:send(Address, Request, Socket) of
ok ->
+
+ ?hcrd("request sent", []),
+
+ %% Activate the request time out for the new request
NewState =
- activate_request_timeout(State#state{request =
- Request}),
+ activate_request_timeout(State#state{request = Request}),
+
+ ClientClose =
+ httpc_request:is_client_closing(Request#request.headers),
case State#state.request of
#request{} -> %% Old request not yet finished
%% Make sure to use the new value of timers in state
- NewTimers = NewState#state.timers,
+ ?hcrd("old request still not finished", []),
+ NewTimers = NewState#state.timers,
NewKeepAlive = queue:in(Request, State#state.keep_alive),
- NewSession =
+ NewSession =
Session#tcp_session{queue_length =
%% Queue + current
queue:len(NewKeepAlive) + 1,
client_close = ClientClose},
httpc_manager:insert_session(NewSession, ProfileName),
+ ?hcrd("session updated", []),
{reply, ok, State#state{keep_alive = NewKeepAlive,
- session = NewSession,
- timers = NewTimers}};
+ session = NewSession,
+ timers = NewTimers}};
undefined ->
%% Note: tcp-message reciving has already been
%% activated by handle_pipeline/2.
+ ?hcrd("no current request", []),
cancel_timer(Timers#timers.queue_timer,
timeout_queue),
NewSession =
@@ -337,17 +386,19 @@ handle_call(Request, _, #state{session = Session =
httpc_manager:insert_session(NewSession, ProfileName),
Relaxed =
(Request#request.settings)#http_options.relaxed,
- {reply, ok,
- NewState#state{request = Request,
- session = NewSession,
- mfa = {httpc_response, parse,
- [State#state.max_header_size,
- Relaxed]}}}
+ MFA = {httpc_response, parse,
+ [State#state.max_header_size, Relaxed]},
+ {reply, ok, NewState#state{request = Request,
+ session = NewSession,
+ mfa = MFA}}
end;
- {error, Reason} ->
+
+ {error, Reason} ->
+ ?hcri("failed sending request", [{reason, Reason}]),
{reply, {request_failed, Reason}, State}
end.
+
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
@@ -367,16 +418,28 @@ handle_call(Request, _, #state{session = Session =
%% handle_keep_alive_queue/2 on the other hand will just skip the
%% request as if it was never issued as in this case the request will
%% not have been sent.
-handle_cast({cancel, RequestId}, State = #state{request = Request =
- #request{id = RequestId},
- profile_name = ProfileName}) ->
+handle_cast({cancel, RequestId},
+ #state{request = #request{id = RequestId} = Request,
+ profile_name = ProfileName,
+ canceled = Canceled} = State) ->
+ ?hcrv("cancel current request", [{request_id, RequestId},
+ {profile, ProfileName},
+ {canceled, Canceled}]),
httpc_manager:request_canceled(RequestId, ProfileName),
+ ?hcrv("canceled", []),
{stop, normal,
- State#state{canceled = [RequestId | State#state.canceled],
- request = Request#request{from = answer_sent}}};
-handle_cast({cancel, RequestId}, State = #state{profile_name = ProfileName}) ->
+ State#state{canceled = [RequestId | Canceled],
+ request = Request#request{from = answer_sent}}};
+handle_cast({cancel, RequestId},
+ #state{profile_name = ProfileName,
+ canceled = Canceled} = State) ->
+ ?hcrv("cancel", [{request_id, RequestId},
+ {profile, ProfileName},
+ {canceled, Canceled}]),
httpc_manager:request_canceled(RequestId, ProfileName),
- {noreply, State#state{canceled = [RequestId | State#state.canceled]}};
+ ?hcrv("canceled", []),
+ {noreply, State#state{canceled = [RequestId | Canceled]}};
+
handle_cast(stream_next, #state{session = Session} = State) ->
http_transport:setopts(socket_type(Session#tcp_session.scheme),
Session#tcp_session.socket, [{active, once}]),
@@ -399,7 +462,13 @@ handle_info({Proto, _Socket, Data},
(Proto =:= ssl) orelse
(Proto =:= httpc_handler) ->
- ?hcri("received data", [{proto, Proto}, {data, Data}, {mfa, MFA}, {method, Method}, {stream, Stream}, {session, Session}, {status_line, StatusLine}]),
+ ?hcri("received data", [{proto, Proto},
+ {data, Data},
+ {mfa, MFA},
+ {method, Method},
+ {stream, Stream},
+ {session, Session},
+ {status_line, StatusLine}]),
FinalResult =
try Module:Function([Data | Args]) of
@@ -410,22 +479,23 @@ handle_info({Proto, _Socket, Data},
?hcrd("data processed - whole body", []),
handle_response(State#state{body = <<>>});
{Module, whole_body, [Body, Length]} ->
- ?hcrd("data processed - whole body", [{module, Module}, {body, Body}, {length, Length}]),
+ ?hcrd("data processed - whole body",
+ [{module, Module}, {body, Body}, {length, Length}]),
{_, Code, _} = StatusLine,
{NewBody, NewRequest} = stream(Body, Request, Code),
%% When we stream we will not keep the already
%% streamed data, that would be a waste of memory.
- NewLength = case Stream of
- none ->
- Length;
- _ ->
- Length - size(Body)
- end,
+ NewLength =
+ case Stream of
+ none ->
+ Length;
+ _ ->
+ Length - size(Body)
+ end,
NewState = next_body_chunk(State),
-
- {noreply, NewState#state{mfa = {Module, whole_body,
- [NewBody, NewLength]},
+ NewMFA = {Module, whole_body, [NewBody, NewLength]},
+ {noreply, NewState#state{mfa = NewMFA,
request = NewRequest}};
NewMFA ->
?hcrd("data processed", [{new_mfa, NewMFA}]),
@@ -435,16 +505,14 @@ handle_info({Proto, _Socket, Data},
{noreply, State#state{mfa = NewMFA}}
catch
exit:_ ->
- ClientErrMsg = httpc_response:error(Request,
- {could_not_parse_as_http,
- Data}),
- NewState = answer_request(Request, ClientErrMsg, State),
+ ClientReason = {could_not_parse_as_http, Data},
+ ClientErrMsg = httpc_response:error(Request, ClientReason),
+ NewState = answer_request(Request, ClientErrMsg, State),
{stop, normal, NewState};
- error:_ ->
- ClientErrMsg = httpc_response:error(Request,
- {could_not_parse_as_http,
- Data}),
- NewState = answer_request(Request, ClientErrMsg, State),
+ error:_ ->
+ ClientReason = {could_not_parse_as_http, Data},
+ ClientErrMsg = httpc_response:error(Request, ClientReason),
+ NewState = answer_request(Request, ClientErrMsg, State),
{stop, normal, NewState}
end,
@@ -453,10 +521,10 @@ handle_info({Proto, _Socket, Data},
handle_info({Proto, Socket, Data},
- #state{mfa = MFA,
- request = Request,
- session = Session,
- status = Status,
+ #state{mfa = MFA,
+ request = Request,
+ session = Session,
+ status = Status,
status_line = StatusLine,
profile_name = Profile} = State)
when (Proto =:= tcp) orelse
@@ -474,6 +542,7 @@ handle_info({Proto, Socket, Data},
"~n",
[Proto, Socket, Data, MFA,
Request, Session, Status, StatusLine, Profile]),
+
{noreply, State};
@@ -513,28 +582,35 @@ handle_info({ssl_error, _, _} = Reason, State) ->
handle_info({timeout, RequestId},
#state{request = #request{id = RequestId} = Request,
canceled = Canceled} = State) ->
+ ?hcri("timeout of current request", [{id, RequestId}]),
httpc_response:send(Request#request.from,
- httpc_response:error(Request,timeout)),
+ httpc_response:error(Request, timeout)),
+ ?hcrv("response (timeout) sent - now terminate", []),
{stop, normal,
State#state{request = Request#request{from = answer_sent},
canceled = [RequestId | Canceled]}};
handle_info({timeout, RequestId}, #state{canceled = Canceled} = State) ->
+ ?hcri("timeout", [{id, RequestId}]),
Filter =
fun(#request{id = Id, from = From} = Request) when Id =:= RequestId ->
+ ?hcrv("found request", [{id, Id}, {from, From}]),
%% Notify the owner
Response = httpc_response:error(Request, timeout),
httpc_response:send(From, Response),
+ ?hcrv("response (timeout) sent", []),
[Request#request{from = answer_sent}];
(_) ->
true
end,
case State#state.status of
pipeline ->
+ ?hcrd("pipeline", []),
Pipeline = queue:filter(Filter, State#state.pipeline),
{noreply, State#state{canceled = [RequestId | Canceled],
pipeline = Pipeline}};
keep_alive ->
+ ?hcrd("keep_alive", []),
KeepAlive = queue:filter(Filter, State#state.keep_alive),
{noreply, State#state{canceled = [RequestId | Canceled],
keep_alive = KeepAlive}}
@@ -577,9 +653,10 @@ terminate(normal, #state{session = undefined}) ->
%% Init error sending, no session information has been setup but
%% there is a socket that needs closing.
-terminate(normal, #state{request = Request,
- session = #tcp_session{id = undefined,
- socket = Socket}}) ->
+terminate(normal,
+ #state{request = Request,
+ session = #tcp_session{id = undefined,
+ socket = Socket}}) ->
http_transport:close(socket_type(Request), Socket);
%% Socket closed remotely
@@ -605,23 +682,28 @@ terminate(normal,
%% And, just in case, close our side (**really** overkill)
http_transport:close(socket_type(Request), Socket);
-terminate(_, State = #state{session = Session,
- request = undefined,
- profile_name = ProfileName,
- timers = Timers,
- pipeline = Pipeline,
- keep_alive = KeepAlive}) ->
- catch httpc_manager:delete_session(Session#tcp_session.id,
- ProfileName),
+terminate(_, #state{session = #tcp_session{id = Id,
+ socket = Socket,
+ scheme = Scheme},
+ request = undefined,
+ profile_name = ProfileName,
+ timers = Timers,
+ pipeline = Pipeline,
+ keep_alive = KeepAlive} = State) ->
+ (catch httpc_manager:delete_session(Id, ProfileName)),
maybe_retry_queue(Pipeline, State),
maybe_retry_queue(KeepAlive, State),
cancel_timer(Timers#timers.queue_timer, timeout_queue),
- Socket = Session#tcp_session.socket,
- http_transport:close(socket_type(Session#tcp_session.scheme), Socket);
+ http_transport:close(socket_type(Scheme), Socket);
-terminate(Reason, State = #state{request = Request}) ->
+terminate(Reason, #state{request = undefined}) ->
+ ?hcrt("terminate", [{reason, Reason}]),
+ ok;
+
+terminate(Reason, #state{request = Request} = State) ->
+ ?hcrd("terminate", [{reason, Reason}, {request, Request}]),
NewState = maybe_send_answer(Request,
httpc_response:error(Request, Reason),
State),
@@ -641,13 +723,16 @@ maybe_send_answer(Request, Answer, State) ->
answer_request(Request, Answer, State).
deliver_answers([]) ->
+ ?hcrd("deliver answer done", []),
ok;
-deliver_answers([#request{from = From} = Request | Requests])
+deliver_answers([#request{id = Id, from = From} = Request | Requests])
when is_pid(From) ->
Response = httpc_response:error(Request, socket_closed_remotely),
+ ?hcrd("deliver answer", [{id, Id}, {from, From}, {response, Response}]),
httpc_response:send(From, Response),
deliver_answers(Requests);
-deliver_answers([_|Requests]) ->
+deliver_answers([Request|Requests]) ->
+ ?hcrd("skip deliver answer", [{request, Request}]),
deliver_answers(Requests).
@@ -728,77 +813,58 @@ connect(SocketType, ToAddress, #options{ipfamily = IpFamily,
http_transport:connect(SocketType, ToAddress, Opts3, Timeout)
end.
-
-send_first_request(Address, Request, #state{options = Options} = State) ->
- SocketType = socket_type(Request),
- ConnTimeout = (Request#request.settings)#http_options.connect_timeout,
- ?hcri("connect",
+connect_and_send_first_request(Address,
+ #request{settings = Settings,
+ headers = Headers,
+ address = OrigAddress,
+ scheme = Scheme} = Request,
+ #state{options = Options} = State) ->
+
+ ?hcrd("connect",
[{address, Address}, {request, Request}, {options, Options}]),
+
+ SocketType = socket_type(Request),
+ ConnTimeout = Settings#http_options.connect_timeout,
case connect(SocketType, Address, Options, ConnTimeout) of
{ok, Socket} ->
- ?hcri("connected - now send first request", [{socket, Socket}]),
+ ?hcrd("connected - now send first request", [{socket, Socket}]),
case httpc_request:send(Address, Request, Socket) of
ok ->
- ?hcri("first request sent", []),
+ ?hcrd("first request sent", []),
ClientClose =
- httpc_request:is_client_closing(
- Request#request.headers),
+ httpc_request:is_client_closing(Headers),
SessionType = httpc_manager:session_type(Options),
Session =
- #tcp_session{id = {Request#request.address, self()},
- scheme = Request#request.scheme,
- socket = Socket,
+ #tcp_session{id = {OrigAddress, self()},
+ scheme = Scheme,
+ socket = Socket,
client_close = ClientClose,
- type = SessionType},
- TmpState = State#state{request = Request,
- session = Session,
- mfa = init_mfa(Request, State),
- status_line =
- init_status_line(Request),
- headers = undefined,
- body = undefined,
- status = new},
- http_transport:setopts(SocketType,
- Socket, [{active, once}]),
+ type = SessionType},
+ TmpState =
+ State#state{request = Request,
+ session = Session,
+ mfa = init_mfa(Request, State),
+ status_line = init_status_line(Request),
+ headers = undefined,
+ body = undefined,
+ status = new},
+ ?hcrt("activate socket", []),
+ activate_once(Session),
NewState = activate_request_timeout(TmpState),
{ok, NewState};
- {error, Reason} ->
- %% Commented out in wait of ssl support to avoid
- %% dialyzer warning
- %%case State#state.status of
- %% new -> % Called from init/1
- self() ! {init_error, error_sending,
- httpc_response:error(Request, Reason)},
- {ok, State#state{request = Request,
- session =
- #tcp_session{socket = Socket}}}
- %%ssl_tunnel -> % Not called from init/1
- %% NewState =
- %% answer_request(Request,
- %%httpc_response:error(Request,
- %%Reason),
- %% State),
- %% {stop, normal, NewState}
- %% end
+ {error, Reason} ->
+ ?hcrv("failed sending request", [{reason, Reason}]),
+ Error = {error, {send_failed,
+ httpc_response:error(Request, Reason)}},
+ {stop, Error, State#state{request = Request}}
end;
- {error, Reason} ->
- %% Commented out in wait of ssl support to avoid
- %% dialyzer warning
- %% case State#state.status of
- %% new -> % Called from init/1
- self() ! {init_error, error_connecting,
- httpc_response:error(Request, Reason)},
- {ok, State#state{request = Request}}
- %% ssl_tunnel -> % Not called from init/1
- %% NewState =
- %% answer_request(Request,
- %% httpc_response:error(Request,
- %% Reason),
- %% State),
- %% {stop, normal, NewState}
- %%end
+ {error, Reason} ->
+ ?hcri("connect failed", [{reason, Reason}]),
+ Error = {error, {connect_failed,
+ httpc_response:error(Request, Reason)}},
+ {stop, Error, State#state{request = Request}}
end.
handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body},
@@ -806,23 +872,23 @@ handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body},
?hcrt("handle_http_msg", [{body, Body}]),
case Headers#http_response_h.'content-type' of
"multipart/byteranges" ++ _Param ->
- exit(not_yet_implemented);
+ exit({not_yet_implemented, multypart_nyteranges});
_ ->
- StatusLine = {Version, StatusCode, ReasonPharse},
+ StatusLine = {Version, StatusCode, ReasonPharse},
{ok, NewRequest} = start_stream(StatusLine, Headers, Request),
handle_http_body(Body,
State#state{request = NewRequest,
status_line = StatusLine,
headers = Headers})
end;
-handle_http_msg({ChunkedHeaders, Body},
- State = #state{headers = Headers}) ->
- ?hcrt("handle_http_msg", [{chunked_headers, ChunkedHeaders}, {body, Body}]),
+handle_http_msg({ChunkedHeaders, Body}, #state{headers = Headers} = State) ->
+ ?hcrt("handle_http_msg",
+ [{chunked_headers, ChunkedHeaders}, {body, Body}]),
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
handle_response(State#state{headers = NewHeaders, body = Body});
-handle_http_msg(Body, State = #state{status_line = {_,Code, _}}) ->
+handle_http_msg(Body, #state{status_line = {_,Code, _}} = State) ->
?hcrt("handle_http_msg", [{body, Body}, {code, Code}]),
- {NewBody, NewRequest}= stream(Body, State#state.request, Code),
+ {NewBody, NewRequest} = stream(Body, State#state.request, Code),
handle_response(State#state{body = NewBody, request = NewRequest}).
handle_http_body(<<>>, State = #state{status_line = {_,304, _}}) ->
@@ -837,11 +903,15 @@ handle_http_body(<<>>, State = #state{request = #request{method = head}}) ->
?hcrt("handle_http_body - head", []),
handle_response(State#state{body = <<>>});
-handle_http_body(Body, State = #state{headers = Headers,
+handle_http_body(Body, State = #state{headers = Headers,
max_body_size = MaxBodySize,
- status_line = {_,Code, _},
- request = Request}) ->
- ?hcrt("handle_http_body", [{body, Body}, {max_body_size, MaxBodySize}, {code, Code}]),
+ status_line = {_,Code, _},
+ request = Request}) ->
+ ?hcrt("handle_http_body",
+ [{headers, Headers},
+ {body, Body},
+ {max_body_size, MaxBodySize},
+ {code, Code}]),
TransferEnc = Headers#http_response_h.'transfer-encoding',
case case_insensitive_header(TransferEnc) of
"chunked" ->
@@ -850,12 +920,17 @@ handle_http_body(Body, State = #state{headers = Headers,
State#state.max_header_size,
{Code, Request}) of
{Module, Function, Args} ->
- ?hcrt("handle_http_body - new mfa", [{module, Module}, {function, Function}, {args, Args}]),
+ ?hcrt("handle_http_body - new mfa",
+ [{module, Module},
+ {function, Function},
+ {args, Args}]),
NewState = next_body_chunk(State),
{noreply, NewState#state{mfa =
{Module, Function, Args}}};
{ok, {ChunkedHeaders, NewBody}} ->
- ?hcrt("handle_http_body - nyew body", [{chunked_headers, ChunkedHeaders}, {new_body, NewBody}]),
+ ?hcrt("handle_http_body - new body",
+ [{chunked_headers, ChunkedHeaders},
+ {new_body, NewBody}]),
NewHeaders = http_chunk:handle_headers(Headers,
ChunkedHeaders),
handle_response(State#state{headers = NewHeaders,
@@ -872,12 +947,13 @@ handle_http_body(Body, State = #state{headers = Headers,
?hcrt("handle_http_body - other", []),
Length =
list_to_integer(Headers#http_response_h.'content-length'),
- case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
+ case ((Length =< MaxBodySize) orelse (MaxBodySize =:= nolimit)) of
true ->
case httpc_response:whole_body(Body, Length) of
{ok, Body} ->
- {NewBody, NewRequest}= stream(Body, Request, Code),
- handle_response(State#state{body = NewBody,
+ {NewBody, NewRequest} =
+ stream(Body, Request, Code),
+ handle_response(State#state{body = NewBody,
request = NewRequest});
MFA ->
NewState = next_body_chunk(State),
@@ -893,42 +969,31 @@ handle_http_body(Body, State = #state{headers = Headers,
end
end.
-%%% Normaly I do not comment out code, I throw it away. But this might
-%%% actually be used on day if ssl is improved.
-%% handle_response(State = #state{status = ssl_tunnel,
-%% request = Request,
-%% options = Options,
-%% session = #tcp_session{socket = Socket,
-%% scheme = Scheme},
-%% status_line = {_, 200, _}}) ->
-%% %%% Insert code for upgrading the socket if and when ssl supports this.
-%% Address = handle_proxy(Request#request.address, Options#options.proxy),
-%% send_first_request(Address, Request, State);
-%% handle_response(State = #state{status = ssl_tunnel,
-%% request = Request}) ->
-%% NewState = answer_request(Request,
-%% httpc_response:error(Request,
-%% ssl_proxy_tunnel_failed),
-%% State),
-%% {stop, normal, NewState};
-
-handle_response(State = #state{status = new}) ->
- handle_response(try_to_enable_pipeline_or_keep_alive(State));
-
-handle_response(State =
- #state{request = Request,
+handle_response(#state{status = new} = State) ->
+ ?hcrd("handle response - status = new", []),
+ handle_response(try_to_enable_pipeline_or_keep_alive(State));
+
+handle_response(#state{request = Request,
status = Status,
session = Session,
status_line = StatusLine,
headers = Headers,
body = Body,
options = Options,
- profile_name = ProfileName}) when Status =/= new ->
- ?hcrt("handle response", [{status, Status}, {session, Session}, {status_line, StatusLine}, {profile_name, ProfileName}]),
+ profile_name = ProfileName} = State)
+ when Status =/= new ->
+
+ ?hcrd("handle response", [{profile, ProfileName},
+ {status, Status},
+ {request, Request},
+ {session, Session},
+ {status_line, StatusLine}]),
+
handle_cookies(Headers, Request, Options, ProfileName),
case httpc_response:result({StatusLine, Headers, Body}, Request) of
%% 100-continue
continue ->
+ ?hcrd("handle response - continue", []),
%% Send request body
{_, RequestBody} = Request#request.content,
http_transport:send(socket_type(Session#tcp_session.scheme),
@@ -939,44 +1004,46 @@ handle_response(State =
Session#tcp_session.socket,
[{active, once}]),
Relaxed = (Request#request.settings)#http_options.relaxed,
- {noreply,
- State#state{mfa = {httpc_response, parse,
- [State#state.max_header_size,
- Relaxed]},
- status_line = undefined,
- headers = undefined,
- body = undefined
- }};
+ MFA = {httpc_response, parse,
+ [State#state.max_header_size, Relaxed]},
+ {noreply, State#state{mfa = MFA,
+ status_line = undefined,
+ headers = undefined,
+ body = undefined}};
+
%% Ignore unexpected 100-continue response and receive the
%% actual response that the server will send right away.
{ignore, Data} ->
+ ?hcrd("handle response - ignore", [{data, Data}]),
Relaxed = (Request#request.settings)#http_options.relaxed,
- NewState = State#state{mfa =
- {httpc_response, parse,
- [State#state.max_header_size,
- Relaxed]},
+ MFA = {httpc_response, parse,
+ [State#state.max_header_size, Relaxed]},
+ NewState = State#state{mfa = MFA,
status_line = undefined,
- headers = undefined,
- body = undefined},
+ headers = undefined,
+ body = undefined},
handle_info({httpc_handler, dummy, Data}, NewState);
+
%% On a redirect or retry the current request becomes
%% obsolete and the manager will create a new request
%% with the same id as the current.
{redirect, NewRequest, Data} ->
- ?hcrt("handle response - redirect", [{new_request, NewRequest}, {data, Data}]),
+ ?hcrt("handle response - redirect",
+ [{new_request, NewRequest}, {data, Data}]),
ok = httpc_manager:redirect_request(NewRequest, ProfileName),
handle_queue(State#state{request = undefined}, Data);
{retry, TimeNewRequest, Data} ->
- ?hcrt("handle response - retry", [{time_new_request, TimeNewRequest}, {data, Data}]),
+ ?hcrt("handle response - retry",
+ [{time_new_request, TimeNewRequest}, {data, Data}]),
ok = httpc_manager:retry_request(TimeNewRequest, ProfileName),
handle_queue(State#state{request = undefined}, Data);
{ok, Msg, Data} ->
- ?hcrt("handle response - result ok", [{msg, Msg}, {data, Data}]),
+ ?hcrd("handle response - ok", [{msg, Msg}, {data, Data}]),
end_stream(StatusLine, Request),
NewState = answer_request(Request, Msg, State),
handle_queue(NewState, Data);
{stop, Msg} ->
- ?hcrt("handle response - result stop", [{msg, Msg}]),
+ ?hcrd("handle response - stop", [{msg, Msg}]),
end_stream(StatusLine, Request),
NewState = answer_request(Request, Msg, State),
{stop, normal, NewState}
@@ -990,60 +1057,67 @@ handle_cookies(_,_, #options{cookies = verify}, _) ->
ok;
handle_cookies(Headers, Request, #options{cookies = enabled}, ProfileName) ->
{Host, _ } = Request#request.address,
- Cookies = http_cookie:cookies(Headers#http_response_h.other,
+ Cookies = httpc_cookie:cookies(Headers#http_response_h.other,
Request#request.path, Host),
httpc_manager:store_cookies(Cookies, Request#request.address,
ProfileName).
%% This request could not be pipelined or used as sequential keept alive
%% queue
-handle_queue(State = #state{status = close}, _) ->
+handle_queue(#state{status = close} = State, _) ->
{stop, normal, State};
-handle_queue(State = #state{status = keep_alive}, Data) ->
+handle_queue(#state{status = keep_alive} = State, Data) ->
handle_keep_alive_queue(State, Data);
-handle_queue(State = #state{status = pipeline}, Data) ->
+handle_queue(#state{status = pipeline} = State, Data) ->
handle_pipeline(State, Data).
-handle_pipeline(State =
- #state{status = pipeline, session = Session,
+handle_pipeline(#state{status = pipeline,
+ session = Session,
profile_name = ProfileName,
- options = #options{pipeline_timeout = TimeOut}},
- Data) ->
+ options = #options{pipeline_timeout = TimeOut}} =
+ State,
+ Data) ->
+
+ ?hcrd("handle pipeline", [{profile, ProfileName},
+ {session, Session},
+ {timeout, TimeOut}]),
+
case queue:out(State#state.pipeline) of
{empty, _} ->
+ ?hcrd("epmty pipeline queue", []),
+
%% The server may choose too teminate an idle pipeline
%% in this case we want to receive the close message
%% at once and not when trying to pipeline the next
%% request.
- http_transport:setopts(socket_type(Session#tcp_session.scheme),
- Session#tcp_session.socket,
- [{active, once}]),
+ activate_once(Session),
+
%% If a pipeline that has been idle for some time is not
%% closed by the server, the client may want to close it.
- NewState = activate_queue_timeout(TimeOut, State),
+ NewState = activate_queue_timeout(TimeOut, State),
NewSession = Session#tcp_session{queue_length = 0},
httpc_manager:insert_session(NewSession, ProfileName),
%% Note mfa will be initilized when a new request
%% arrives.
{noreply,
- NewState#state{request = undefined,
- mfa = undefined,
+ NewState#state{request = undefined,
+ mfa = undefined,
status_line = undefined,
- headers = undefined,
- body = undefined
- }
- };
+ headers = undefined,
+ body = undefined}};
{{value, NextRequest}, Pipeline} ->
case lists:member(NextRequest#request.id,
State#state.canceled) of
true ->
+ ?hcrv("next request had been cancelled", []),
%% See comment for handle_cast({cancel, RequestId})
{stop, normal,
State#state{request =
NextRequest#request{from = answer_sent}}};
false ->
+ ?hcrv("next request", [{request, NextRequest}]),
NewSession =
Session#tcp_session{queue_length =
%% Queue + current
@@ -1051,15 +1125,16 @@ handle_pipeline(State =
httpc_manager:insert_session(NewSession, ProfileName),
Relaxed =
(NextRequest#request.settings)#http_options.relaxed,
+ MFA = {httpc_response,
+ parse,
+ [State#state.max_header_size, Relaxed]},
NewState =
- State#state{pipeline = Pipeline,
- request = NextRequest,
- mfa = {httpc_response, parse,
- [State#state.max_header_size,
- Relaxed]},
+ State#state{pipeline = Pipeline,
+ request = NextRequest,
+ mfa = MFA,
status_line = undefined,
- headers = undefined,
- body = undefined},
+ headers = undefined,
+ body = undefined},
case Data of
<<>> ->
http_transport:setopts(
@@ -1076,15 +1151,20 @@ handle_pipeline(State =
end
end.
-handle_keep_alive_queue(State = #state{status = keep_alive,
- session = Session,
- profile_name = ProfileName,
- options = #options{keep_alive_timeout
- = TimeOut}
- },
- Data) ->
+handle_keep_alive_queue(
+ #state{status = keep_alive,
+ session = Session,
+ profile_name = ProfileName,
+ options = #options{keep_alive_timeout = TimeOut}} = State,
+ Data) ->
+
+ ?hcrd("handle keep_alive", [{profile, ProfileName},
+ {session, Session},
+ {timeout, TimeOut}]),
+
case queue:out(State#state.keep_alive) of
{empty, _} ->
+ ?hcrd("epmty keep_alive queue", []),
%% The server may choose too terminate an idle keep_alive session
%% in this case we want to receive the close message
%% at once and not when trying to send the next
@@ -1111,25 +1191,25 @@ handle_keep_alive_queue(State = #state{status = keep_alive,
case lists:member(NextRequest#request.id,
State#state.canceled) of
true ->
- handle_keep_alive_queue(State#state{keep_alive =
- KeepAlive}, Data);
+ ?hcrv("next request has already been canceled", []),
+ handle_keep_alive_queue(
+ State#state{keep_alive = KeepAlive}, Data);
false ->
+ ?hcrv("next request", [{request, NextRequest}]),
Relaxed =
(NextRequest#request.settings)#http_options.relaxed,
+ MFA = {httpc_response, parse,
+ [State#state.max_header_size, Relaxed]},
NewState =
- State#state{request = NextRequest,
- keep_alive = KeepAlive,
- mfa = {httpc_response, parse,
- [State#state.max_header_size,
- Relaxed]},
+ State#state{request = NextRequest,
+ keep_alive = KeepAlive,
+ mfa = MFA,
status_line = undefined,
- headers = undefined,
- body = undefined},
+ headers = undefined,
+ body = undefined},
case Data of
<<>> ->
- http_transport:setopts(
- socket_type(Session#tcp_session.scheme),
- Session#tcp_session.socket, [{active, once}]),
+ activate_once(Session),
{noreply, NewState};
_ ->
%% If we already received some bytes of
@@ -1140,11 +1220,6 @@ handle_keep_alive_queue(State = #state{status = keep_alive,
end
end.
-call(Msg, Pid, Timeout) ->
- gen_server:call(Pid, Msg, Timeout).
-
-cast(Msg, Pid) ->
- gen_server:cast(Pid, Msg).
case_insensitive_header(Str) when is_list(Str) ->
http_util:to_lower(Str);
@@ -1152,20 +1227,34 @@ case_insensitive_header(Str) when is_list(Str) ->
case_insensitive_header(Str) ->
Str.
-activate_request_timeout(State = #state{request = Request}) ->
- Time = (Request#request.settings)#http_options.timeout,
- case Time of
+activate_once(#tcp_session{scheme = Scheme, socket = Socket}) ->
+ SocketType = socket_type(Scheme),
+ http_transport:setopts(SocketType, Socket, [{active, once}]).
+
+activate_request_timeout(
+ #state{request = #request{timer = undefined} = Request} = State) ->
+ Timeout = (Request#request.settings)#http_options.timeout,
+ case Timeout of
infinity ->
State;
_ ->
- Ref = erlang:send_after(Time, self(),
- {timeout, Request#request.id}),
- State#state
- {timers =
- #timers{request_timers =
- [{Request#request.id, Ref}|
- (State#state.timers)#timers.request_timers]}}
- end.
+ ReqId = Request#request.id,
+ ?hcrt("activate request timer",
+ [{request_id, ReqId},
+ {time_consumed, t() - Request#request.started},
+ {timeout, Timeout}]),
+ Msg = {timeout, ReqId},
+ Ref = erlang:send_after(Timeout, self(), Msg),
+ Request2 = Request#request{timer = Ref},
+ ReqTimers = [{Request#request.id, Ref} |
+ (State#state.timers)#timers.request_timers],
+ Timers = #timers{request_timers = ReqTimers},
+ State#state{request = Request2, timers = Timers}
+ end;
+
+%% Timer is already running! This is the case for a redirect or retry
+activate_request_timeout(State) ->
+ State.
activate_queue_timeout(infinity, State) ->
State;
@@ -1191,12 +1280,12 @@ is_keep_alive_connection(Headers, Session) ->
(not ((Session#tcp_session.client_close) or
httpc_response:is_server_closing(Headers))).
-try_to_enable_pipeline_or_keep_alive(State =
- #state{session = Session,
- request = #request{method = Method},
- status_line = {Version, _, _},
- headers = Headers,
- profile_name = ProfileName}) ->
+try_to_enable_pipeline_or_keep_alive(
+ #state{session = Session,
+ request = #request{method = Method},
+ status_line = {Version, _, _},
+ headers = Headers,
+ profile_name = ProfileName} = State) ->
case (is_keep_alive_enabled_server(Version, Headers) andalso
is_keep_alive_connection(Headers, Session)) of
true ->
@@ -1209,15 +1298,16 @@ try_to_enable_pipeline_or_keep_alive(State =
httpc_manager:insert_session(Session, ProfileName),
%% Make sure type is keep_alive in session
%% as it in this case might be pipeline
- State#state{status = keep_alive,
- session =
- Session#tcp_session{type = keep_alive}}
+ NewSession = Session#tcp_session{type = keep_alive},
+ State#state{status = keep_alive,
+ session = NewSession}
end;
false ->
State#state{status = close}
end.
-answer_request(Request, Msg, #state{timers = Timers} = State) ->
+answer_request(Request, Msg, #state{timers = Timers} = State) ->
+ ?hcrt("answer request", [{request, Request}, {msg, Msg}]),
httpc_response:send(Request#request.from, Msg),
RequestTimers = Timers#timers.request_timers,
TimerRef =
@@ -1253,14 +1343,14 @@ retry_pipeline([Request | PipeLine],
case (catch httpc_manager:retry_request(Request, ProfileName)) of
ok ->
RequestTimers = Timers#timers.request_timers,
+ ReqId = Request#request.id,
TimerRef =
- proplists:get_value(Request#request.id, RequestTimers,
- undefined),
- cancel_timer(TimerRef, {timeout, Request#request.id}),
- State#state{timers = Timers#timers{request_timers =
- lists:delete({Request#request.id,
- TimerRef},
- RequestTimers)}};
+ proplists:get_value(ReqId, RequestTimers, undefined),
+ cancel_timer(TimerRef, {timeout, ReqId}),
+ NewReqsTimers = lists:delete({ReqId, TimerRef}, RequestTimers),
+ NewTimers = Timers#timers{request_timers = NewReqsTimers},
+ State#state{timers = NewTimers};
+
Error ->
answer_request(Request#request.from,
httpc_response:error(Request, Error), State)
@@ -1347,10 +1437,12 @@ socket_type(http) ->
socket_type(https) ->
{ssl, []}. %% Dummy value ok for ex setops that does not use this value
-start_stream({_Version, _Code, _ReasonPhrase}, _Headers, #request{stream = none} = Request) ->
+start_stream({_Version, _Code, _ReasonPhrase}, _Headers,
+ #request{stream = none} = Request) ->
?hcrt("start stream - none", []),
{ok, Request};
-start_stream({_Version, Code, _ReasonPhrase}, Headers, #request{stream = self} = Request)
+start_stream({_Version, Code, _ReasonPhrase}, Headers,
+ #request{stream = self} = Request)
when (Code =:= 200) orelse (Code =:= 206) ->
?hcrt("start stream - self", [{code, Code}]),
Msg = httpc_response:stream_start(Headers, Request, ignore),
@@ -1363,7 +1455,8 @@ start_stream({_Version, Code, _ReasonPhrase}, Headers,
Msg = httpc_response:stream_start(Headers, Request, self()),
httpc_response:send(Request#request.from, Msg),
{ok, Request};
-start_stream({_Version, Code, _ReasonPhrase}, _Headers, #request{stream = Filename} = Request)
+start_stream({_Version, Code, _ReasonPhrase}, _Headers,
+ #request{stream = Filename} = Request)
when ((Code =:= 200) orelse (Code =:= 206)) andalso is_list(Filename) ->
?hcrt("start stream", [{code, Code}, {filename, Filename}]),
case file:open(Filename, [write, raw, append, delayed_write]) of
@@ -1497,3 +1590,21 @@ handle_verbose(_) ->
%% d(_, _, _) ->
%% ok.
+
+call(Msg, Pid) ->
+ Timeout = infinity,
+ call(Msg, Pid, Timeout).
+call(Msg, Pid, Timeout) ->
+ gen_server:call(Pid, Msg, Timeout).
+
+cast(Msg, Pid) ->
+ gen_server:cast(Pid, Msg).
+
+
+%% to(To, Start) when is_integer(Start) andalso (Start >= 0) ->
+%% http_util:timeout(To, Start);
+%% to(To, _Start) ->
+%% http_util:timeout(To, t()).
+
+t() ->
+ http_util:timestamp().
diff --git a/lib/inets/src/http_client/httpc_handler_sup.erl b/lib/inets/src/http_client/httpc_handler_sup.erl
index d9edaa0599..2a69fd15d0 100644
--- a/lib/inets/src/http_client/httpc_handler_sup.erl
+++ b/lib/inets/src/http_client/httpc_handler_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -23,7 +23,7 @@
%% API
-export([start_link/0]).
--export([start_child/1]).
+-export([start_child/2]).
%% Supervisor callback
-export([init/1]).
@@ -34,25 +34,28 @@
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-start_child(Args) ->
+start_child(Options, Profile) ->
+ Args = [Options, Profile],
supervisor:start_child(?MODULE, Args).
-
+
+
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
init(Args) ->
+
RestartStrategy = simple_one_for_one,
MaxR = 0,
MaxT = 3600,
- Name = undefined, % As simple_one_for_one is used.
+ Name = undefined, % As simple_one_for_one is used.
StartFunc = {httpc_handler, start_link, Args},
- Restart = temporary, % E.g. should not be restarted
- Shutdown = 4000,
- Modules = [httpc_handler],
- Type = worker,
-
+ Restart = temporary, % E.g. should not be restarted
+ Shutdown = 4000,
+ Modules = [httpc_handler],
+ Type = worker,
ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+
{ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
diff --git a/lib/inets/src/http_client/httpc_internal.hrl b/lib/inets/src/http_client/httpc_internal.hrl
index ec709b9860..4c5e6ed5d8 100644
--- a/lib/inets/src/http_client/httpc_internal.hrl
+++ b/lib/inets/src/http_client/httpc_internal.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -81,23 +81,25 @@
%%% All data associated to a specific HTTP request
-record(request,
{
- id, % ref() - Request Id
- from, % pid() - Caller
- redircount = 0,% Number of redirects made for this request
- scheme, % http | https
- address, % ({Host,Port}) Destination Host and Port
- path, % string() - Path of parsed URL
- pquery, % string() - Rest of parsed URL
- method, % atom() - HTTP request Method
- headers, % #http_request_h{}
- content, % {ContentType, Body} - Current HTTP request
- settings, % #http_options{} - User defined settings
- abs_uri, % string() ex: "http://www.erlang.org"
- userinfo, % string() - optinal "<userinfo>@<host>:<port>"
- stream, % Boolean() - stream async reply?
- headers_as_is % Boolean() - workaround for servers that does
- %% not honor the http standard, can also be used for testing purposes.
- }
+ id, % ref() - Request Id
+ from, % pid() - Caller
+ redircount = 0,% Number of redirects made for this request
+ scheme, % http | https
+ address, % ({Host,Port}) Destination Host and Port
+ path, % string() - Path of parsed URL
+ pquery, % string() - Rest of parsed URL
+ method, % atom() - HTTP request Method
+ headers, % #http_request_h{}
+ content, % {ContentType, Body} - Current HTTP request
+ settings, % #http_options{} - User defined settings
+ abs_uri, % string() ex: "http://www.erlang.org"
+ userinfo, % string() - optinal "<userinfo>@<host>:<port>"
+ stream, % Boolean() - stream async reply?
+ headers_as_is, % Boolean() - workaround for servers that does
+ % not honor the http standard, can also be used for testing purposes.
+ started, % integer() > 0 - When we started processing the request
+ timer % undefined | ref()
+ }
).
-record(tcp_session,
diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl
index 63b00c7dce..915f4c024d 100644
--- a/lib/inets/src/http_client/httpc_manager.erl
+++ b/lib/inets/src/http_client/httpc_manager.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -25,51 +25,84 @@
-include("http_internal.hrl").
%% Internal Application API
--export([start_link/1, start_link/2, request/2, cancel_request/2,
- request_canceled/2, retry_request/2, redirect_request/2,
- insert_session/2, delete_session/2, set_options/2, store_cookies/3,
- cookies/2, session_type/1]).
+-export([
+ start_link/3,
+ request/2,
+ cancel_request/2,
+ request_canceled/2,
+ retry_request/2,
+ redirect_request/2,
+ insert_session/2,
+ delete_session/2,
+ set_options/2,
+ store_cookies/3,
+ which_cookies/1, which_cookies/2,
+ reset_cookies/1,
+ session_type/1
+ ]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
--record(state, {
+-record(state,
+ {
cancel = [], % [{RequestId, HandlerPid, ClientPid}]
- handler_db, % ets() - Entry: {Requestid, HandlerPid, ClientPid}
- cookie_db, % {ets(), dets()} - {session_cookie_db, cookie_db}
+ handler_db, % ets() - Entry: #handler_info{}
+ cookie_db, % cookie_db()
session_db, % ets() - Entry: #tcp_session{}
profile_name, % atom()
options = #options{}
}).
+-record(handler_info,
+ {
+ id, % Id of the request: request_id()
+ starter, % Pid of the handler starter process (temp): pid()
+ handler, % Pid of the handler process: pid()
+ from, % From for the request: from()
+ state % State of the handler: initiating | operational
+ }).
+
+%% Entries in the handler / request cross-ref table
+%% -record(request_info,
+%% {
+%% id, % Id of the request
+%% handler, % Pid of the handler process
+%% from, % The From value for the caller
+%% mref % Monitor ref for the caller
+%% }).
+
%%====================================================================
%% Internal Application API
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: start_link({ProfileName, CookieDir}) -> {ok, Pid}
+%% Function: start_link(ProfileName, CookieDir, ManagedHow) -> {ok, Pid}
%%
%% ProfileName - httpc_manager_<Profile>
%% CookieDir - directory()
+%% ManagedHow - stand_alone | inets
%%
-%% Description: Starts the http request manger process. (Started by
-%% the intes supervisor.)
-%%--------------------------------------------------------------------
-start_link({default, CookieDir}) ->
- gen_server:start_link({local, ?MODULE}, ?MODULE,
- [?MODULE, {http_default_cookie_db, CookieDir}],
- []);
-start_link({Profile, CookieDir}) ->
- ProfileName = list_to_atom("httpc_manager_" ++ atom_to_list(Profile)),
- gen_server:start_link({local, ProfileName}, ?MODULE,
- [ProfileName,
- {http_default_cookie_db, CookieDir}], []).
-start_link({Profile, CookieDir}, stand_alone) ->
- ProfileName = list_to_atom("stand_alone_" ++ atom_to_list(Profile)),
- gen_server:start_link(?MODULE, [ProfileName,
- {http_default_cookie_db, CookieDir}],
- []).
+%% Description: Starts the http request manager process.
+%% (If ManagedHow = inets then started by the inets supervisor.)
+%%--------------------------------------------------------------------
+
+start_link(Profile, CookieDir, stand_alone) ->
+ ProfileName = httpc:profile_name("stand_alone_", Profile),
+ Args = [ProfileName, CookieDir],
+ Opts = [],
+ %% Opts = [{debug, [log, statistics]}],
+ gen_server:start_link(?MODULE, Args, Opts);
+start_link(Profile, CookieDir, _) ->
+ ProfileName = httpc:profile_name(Profile),
+ Server = {local, ProfileName},
+ Args = [ProfileName, CookieDir],
+ Opts = [],
+ %% Opts = [{debug, [log, statistics]}],
+ gen_server:start_link(Server, ?MODULE, Args, Opts).
+
+
%%--------------------------------------------------------------------
%% Function: request(Request, ProfileName) ->
%% {ok, Requestid} | {error, Reason}
@@ -78,8 +111,10 @@ start_link({Profile, CookieDir}, stand_alone) ->
%%
%% Description: Sends a request to the httpc manager process.
%%--------------------------------------------------------------------
+
request(Request, ProfileName) ->
- call(ProfileName, {request, Request}, infinity).
+ call(ProfileName, {request, Request}).
+
%%--------------------------------------------------------------------
%% Function: retry_request(Request, ProfileName) -> _
@@ -90,9 +125,11 @@ request(Request, ProfileName) ->
%% to be called by the httpc handler process if it has to terminate with
%% a non empty pipeline.
%%--------------------------------------------------------------------
+
retry_request(Request, ProfileName) ->
cast(ProfileName, {retry_or_redirect_request, Request}).
+
%%--------------------------------------------------------------------
%% Function: redirect_request(Request, ProfileName) -> _
%% Request = #request{}
@@ -102,9 +139,11 @@ retry_request(Request, ProfileName) ->
%% manager process, intended to be called by the httpc handler process
%% when the automatic redirect option is set.
%%--------------------------------------------------------------------
+
redirect_request(Request, ProfileName) ->
cast(ProfileName, {retry_or_redirect_request, Request}).
+
%%--------------------------------------------------------------------
%% Function: cancel_request(RequestId, ProfileName) -> ok
%% RequestId - ref()
@@ -112,8 +151,10 @@ redirect_request(Request, ProfileName) ->
%%
%% Description: Cancels the request with <RequestId>.
%%--------------------------------------------------------------------
+
cancel_request(RequestId, ProfileName) ->
- call(ProfileName, {cancel_request, RequestId}, infinity).
+ call(ProfileName, {cancel_request, RequestId}).
+
%%--------------------------------------------------------------------
%% Function: request_canceled(RequestId, ProfileName) -> ok
@@ -123,9 +164,11 @@ cancel_request(RequestId, ProfileName) ->
%% Description: Confirms that a request has been canceld. Intended to
%% be called by the httpc handler process.
%%--------------------------------------------------------------------
+
request_canceled(RequestId, ProfileName) ->
cast(ProfileName, {request_canceled, RequestId}).
+
%%--------------------------------------------------------------------
%% Function: insert_session(Session, ProfileName) -> _
%% Session - #tcp_session{}
@@ -135,9 +178,11 @@ request_canceled(RequestId, ProfileName) ->
%% table <ProfileName>_session_db. Intended to be called by
%% the httpc request handler process.
%%--------------------------------------------------------------------
+
insert_session(Session, ProfileName) ->
- Db = list_to_atom(atom_to_list(ProfileName) ++ "_session_db"),
- ets:insert(Db, Session).
+ SessionDbName = session_db_name(ProfileName),
+ ets:insert(SessionDbName, Session).
+
%%--------------------------------------------------------------------
%% Function: delete_session(SessionId, ProfileName) -> _
@@ -148,9 +193,11 @@ insert_session(Session, ProfileName) ->
%% table httpc_manager_session_db_<Profile>. Intended to be called by
%% the httpc request handler process.
%%--------------------------------------------------------------------
+
delete_session(SessionId, ProfileName) ->
- Db = list_to_atom(atom_to_list(ProfileName) ++ "_session_db"),
- ets:delete(Db, SessionId).
+ SessionDbName = session_db_name(ProfileName),
+ ets:delete(SessionDbName, SessionId).
+
%%--------------------------------------------------------------------
%% Function: set_options(Options, ProfileName) -> ok
@@ -166,9 +213,11 @@ delete_session(SessionId, ProfileName) ->
%%
%% Description: Sets the options to be used by the client.
%%--------------------------------------------------------------------
+
set_options(Options, ProfileName) ->
cast(ProfileName, {set_options, Options}).
+
%%--------------------------------------------------------------------
%% Function: store_cookies(Cookies, Address, ProfileName) -> ok
%%
@@ -183,8 +232,22 @@ store_cookies([], _, _) ->
store_cookies(Cookies, Address, ProfileName) ->
cast(ProfileName, {store_cookies, {Cookies, Address}}).
+
%%--------------------------------------------------------------------
-%% Function: cookies(Url, ProfileName) -> ok
+%% Function: reset_cookies(ProfileName) -> void()
+%%
+%% Url = string()
+%% ProfileName = atom()
+%%
+%% Description: Resets the cookie database
+%%--------------------------------------------------------------------
+
+reset_cookies(ProfileName) ->
+ call(ProfileName, reset_cookies).
+
+
+%%--------------------------------------------------------------------
+%% Function: which_cookies(Url, ProfileName) -> [cookie()]
%%
%% Url = string()
%% ProfileName = atom()
@@ -192,8 +255,12 @@ store_cookies(Cookies, Address, ProfileName) ->
%% Description: Retrieves the cookies that would be sent when
%% requesting <Url>.
%%--------------------------------------------------------------------
-cookies(Url, ProfileName) ->
- call(ProfileName, {cookies, Url}, infinity).
+
+which_cookies(ProfileName) ->
+ call(ProfileName, which_cookies).
+which_cookies(Url, ProfileName) ->
+ call(ProfileName, {which_cookies, Url}).
+
%%--------------------------------------------------------------------
%% Function: session_type(Options) -> ok
@@ -202,11 +269,13 @@ cookies(Url, ProfileName) ->
%%
%% Description: Determines if to use pipelined sessions or not.
%%--------------------------------------------------------------------
+
session_type(#options{pipeline_timeout = 0}) ->
keep_alive;
session_type(_) ->
pipeline.
+
%%====================================================================
%% gen_server callback functions
%%====================================================================
@@ -216,19 +285,46 @@ session_type(_) ->
%% {ok, State, Timeout} | ignore |{stop, Reason}
%% Description: Initiates the httpc_manger process
%%--------------------------------------------------------------------
-init([ProfileName, CookiesConf | _]) ->
+init([ProfileName, CookiesDir]) ->
process_flag(trap_exit, true),
- SessionDb = list_to_atom(atom_to_list(ProfileName) ++ "_session_db"),
- ets:new(SessionDb,
+ ?hcrv("starting", [{profile, ProfileName}]),
+ case (catch do_init(ProfileName, CookiesDir)) of
+ {ok, _} = OK ->
+ ?hcrd("started", [OK]),
+ OK;
+ {error, Reason} ->
+ {stop, Reason};
+ Crap ->
+ {stop, Crap}
+ end.
+
+
+do_init(ProfileName, CookiesDir) ->
+ %% Create session db
+ ?hcrt("create session db", []),
+ SessionDbName = session_db_name(ProfileName),
+ ets:new(SessionDbName,
[public, set, named_table, {keypos, #tcp_session.id}]),
- ?hcri("starting", [{profile, ProfileName}]),
- {ok, #state{handler_db = ets:new(handler_db, [protected, set]),
- cookie_db =
- http_cookie:open_cookie_db({CookiesConf,
- http_session_cookie_db}),
- session_db = SessionDb,
- profile_name = ProfileName
- }}.
+
+ %% Create handler db
+ ?hcrt("create handler/request db", []),
+ HandlerDbName = handler_db_name(ProfileName),
+ ets:new(HandlerDbName,
+ [protected, set, named_table, {keypos, #handler_info.id}]),
+
+ %% Cookie DB
+ ?hcrt("create cookie db", []),
+ SessionCookieDbName = session_cookie_db_name(ProfileName),
+ CookieDbName = cookie_db_name(ProfileName),
+ CookieDb = httpc_cookie:open_db(CookieDbName, CookiesDir,
+ SessionCookieDbName),
+
+ State = #state{handler_db = HandlerDbName,
+ cookie_db = CookieDb,
+ session_db = SessionDbName,
+ profile_name = ProfileName},
+ {ok, State}.
+
%%--------------------------------------------------------------------
%% Function: handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -239,45 +335,77 @@ init([ProfileName, CookiesConf | _]) ->
%% {stop, Reason, State} (terminate/2 is called)
%% Description: Handling call messages
%%--------------------------------------------------------------------
-handle_call({request, Request}, _, State) ->
- ?hcri("request", [{request, Request}]),
+handle_call({request, Request}, _From, State) ->
+ ?hcrv("request", [{request, Request}]),
case (catch handle_request(Request, State)) of
- {reply, Msg, NewState} ->
- {reply, Msg, NewState};
+ {ok, ReqId, NewState} ->
+ {reply, {ok, ReqId}, NewState};
+
Error ->
+ %% This is way too severe
+ %% To crash the manager simply because
+ %% it failed to properly handle a request
{stop, Error, httpc_response:error(Request, Error), State}
end;
-handle_call({cancel_request, RequestId}, From, State) ->
- ?hcri("cancel_request", [{request_id, RequestId}]),
+handle_call({cancel_request, RequestId}, From,
+ #state{handler_db = HandlerDb} = State) ->
+ ?hcrv("cancel_request", [{request_id, RequestId}]),
case ets:lookup(State#state.handler_db, RequestId) of
[] ->
- ok, %% Nothing to cancel
- {reply, ok, State};
- [{_, Pid, _}] ->
+ ?hcrd("nothing to cancel", []),
+ Reply = ok, %% Nothing to cancel
+ {reply, Reply, State};
+
+ [#handler_info{handler = Pid}] when is_pid(Pid) ->
+ ?hcrd("found operational handler for this request",
+ [{handler, Pid}]),
httpc_handler:cancel(RequestId, Pid),
{noreply, State#state{cancel =
- [{RequestId, Pid, From} |
+ [{RequestId, Pid, From} |
+ State#state.cancel]}};
+
+ [#handler_info{starter = Pid, state = HandlerState}]
+ when is_pid(Pid) ->
+ ?hcri("found *initiating* handler for this request",
+ [{starter, Pid}, {state, HandlerState}]),
+ ets:update_element(HandlerDb, RequestId,
+ {#handler_info.state, canceled}),
+ {noreply, State#state{cancel =
+ [{RequestId, Pid, From} |
State#state.cancel]}}
+
end;
-handle_call({cookies, Url}, _, State) ->
+handle_call(reset_cookies, _, #state{cookie_db = CookieDb} = State) ->
+ ?hcrv("reset cookies", []),
+ httpc_cookie:reset_db(CookieDb),
+ {reply, ok, State};
+
+handle_call(which_cookies, _, #state{cookie_db = CookieDb} = State) ->
+ ?hcrv("which cookies", []),
+ CookieHeaders = httpc_cookie:which_cookies(CookieDb),
+ {reply, CookieHeaders, State};
+
+handle_call({which_cookies, Url}, _, #state{cookie_db = CookieDb} = State) ->
+ ?hcrv("which cookies", [{url, Url}]),
case http_uri:parse(Url) of
{Scheme, _, Host, Port, Path, _} ->
CookieHeaders =
- http_cookie:header(Scheme, {Host, Port},
- Path, State#state.cookie_db),
+ httpc_cookie:header(CookieDb, Scheme, {Host, Port}, Path),
{reply, CookieHeaders, State};
Msg ->
{reply, Msg, State}
end;
-handle_call(Msg, From, State) ->
- Report = io_lib:format("HTTPC_MANAGER recived unkown call: ~p"
- "from: ~p~n", [Msg, From]),
- error_logger:error_report(Report),
+handle_call(Req, From, #state{profile_name = ProfileName} = State) ->
+ error_report(ProfileName,
+ "received unkown request"
+ "~n Req: ~p"
+ "~n From: ~p", [Req, From]),
{reply, {error, 'API_violation'}, State}.
+
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
@@ -286,29 +414,49 @@ handle_call(Msg, From, State) ->
%%--------------------------------------------------------------------
handle_cast({retry_or_redirect_request, {Time, Request}},
#state{profile_name = ProfileName} = State) ->
- {ok, _} = timer:apply_after(Time, ?MODULE, retry_request, [Request, ProfileName]),
- {noreply, State};
+ ?hcrv("retry or redirect request", [{time, Time}, {request, Request}]),
+ case timer:apply_after(Time, ?MODULE, retry_request,
+ [Request, ProfileName]) of
+ {ok, _} ->
+ {noreply, State};
+ {error, Reason} ->
+ error_report(ProfileName,
+ "failed scheduling retry/redirect request"
+ "~n Time: ~p"
+ "~n Request: ~p"
+ "~n Reason: ~p", [Time, Request, Reason]),
+ {noreply, State}
+ end;
handle_cast({retry_or_redirect_request, Request}, State) ->
+ ?hcrv("retry or redirect request", [{request, Request}]),
case (catch handle_request(Request, State)) of
- {reply, {ok, _}, NewState} ->
+ {ok, _, NewState} ->
{noreply, NewState};
+
Error ->
- httpc_response:error(Request, Error),
+ %% This is *way* too severe.
+ %% To crash the manager simply because
+ %% it failed to properly handle *one* request
{stop, Error, State}
end;
handle_cast({request_canceled, RequestId}, State) ->
+ ?hcrv("request canceled", [{request_id, RequestId}]),
ets:delete(State#state.handler_db, RequestId),
case lists:keysearch(RequestId, 1, State#state.cancel) of
{value, Entry = {RequestId, _, From}} ->
+ ?hcrt("found in cancel", [{from, From}]),
gen_server:reply(From, ok),
{noreply,
State#state{cancel = lists:delete(Entry, State#state.cancel)}};
- _ ->
+ Else ->
+ ?hcrt("not found in cancel", [{else, Else}]),
{noreply, State}
end;
+
handle_cast({set_options, Options}, State = #state{options = OldOptions}) ->
+ ?hcrv("set options", [{options, Options}, {old_options, OldOptions}]),
NewOptions =
#options{proxy = get_proxy(Options, OldOptions),
pipeline_timeout = get_pipeline_timeout(Options, OldOptions),
@@ -345,10 +493,10 @@ handle_cast({store_cookies, {Cookies, _}}, State) ->
ok = do_store_cookies(Cookies, State),
{noreply, State};
-handle_cast(Msg, State) ->
- Report = io_lib:format("HTTPC_MANAGER recived unkown cast: ~p",
- [Msg]),
- error_logger:error_report(Report),
+handle_cast(Msg, #state{profile_name = ProfileName} = State) ->
+ error_report(ProfileName,
+ "recived unknown message"
+ "~n Msg: ~p", [Msg]),
{noreply, State}.
@@ -359,17 +507,32 @@ handle_cast(Msg, State) ->
%% {stop, Reason, State} (terminate/2 is called)
%% Description: Handling all non call/cast messages
%%---------------------------------------------------------
-handle_info({'EXIT', _, _}, State) ->
- %% Handled in DOWN
+handle_info({connect_and_send, StarterPid, ReqId, HandlerPid, Res}, State) ->
+ handle_connect_and_send(StarterPid, ReqId, HandlerPid, Res, State),
{noreply, State};
+
+handle_info({failed_starting_handler, StarterPid, ReqId, Res}, State) ->
+ handle_failed_starting_handler(StarterPid, ReqId, Res, State),
+ {noreply, State};
+
+handle_info({'EXIT', Pid, Reason}, #state{handler_db = HandlerDb} = State) ->
+ maybe_handle_terminating_starter(Pid, Reason, HandlerDb),
+ {noreply, State};
+
handle_info({'DOWN', _, _, Pid, _}, State) ->
- ets:match_delete(State#state.handler_db, {'_', Pid, '_'}),
+
+ %%
+ %% Check what happens to waiting requests! Chall we not send a reply?
+ %%
+
+ Pattern = #handler_info{handler = Pid, _ = '_'},
+ ets:match_delete(State#state.handler_db, Pattern),
%% If there where any canceled request, handled by the
%% the process that now has terminated, the
%% cancelation can be viewed as sucessfull!
- NewCanceldList =
- lists:foldl(fun(Entry = {_, HandlerPid, From}, Acc) ->
+ NewCanceledList =
+ lists:foldl(fun({_, HandlerPid, From} = Entry, Acc) ->
case HandlerPid of
Pid ->
gen_server:reply(From, ok),
@@ -378,21 +541,25 @@ handle_info({'DOWN', _, _, Pid, _}, State) ->
Acc
end
end, State#state.cancel, State#state.cancel),
- {noreply, State#state{cancel = NewCanceldList}};
-handle_info(Info, State) ->
- Report = io_lib:format("Unknown message in "
- "httpc_manager:handle_info ~p~n", [Info]),
- error_logger:error_report(Report),
+ {noreply, State#state{cancel = NewCanceledList}};
+
+handle_info(Info, #state{profile_name = ProfileName} = State) ->
+ error_report(ProfileName,
+ "received unknown info"
+ "~n Info: ~p", [Info]),
{noreply, State}.
+
+
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> _ (ignored by gen_server)
%% Description: Shutdown the httpc_handler
%%--------------------------------------------------------------------
terminate(_, State) ->
- http_cookie:close_cookie_db(State#state.cookie_db),
+ httpc_cookie:close_db(State#state.cookie_db),
ets:delete(State#state.session_db),
ets:delete(State#state.handler_db).
+
%%--------------------------------------------------------------------
%% Func: code_change(_OldVsn, State, Extra) -> {ok, NewState}
%% Purpose: Convert process state when code is changed
@@ -400,70 +567,188 @@ terminate(_, State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
-handle_request(#request{settings =
- #http_options{version = "HTTP/0.9"}} = Request,
- State) ->
- %% Act as an HTTP/0.9 client that does not know anything
- %% about persistent connections
- NewRequest = handle_cookies(generate_request_id(Request), State),
- NewHeaders =
- (NewRequest#request.headers)#http_request_h{connection
- = undefined},
- start_handler(NewRequest#request{headers = NewHeaders}, State),
- {reply, {ok, NewRequest#request.id}, State};
+%%
+%% The request handler process is started asynchronously by a
+%% "starter process". When that process terminates it sends
+%% one of two messages. These ara handled by the two functions
+%% below.
+%%
+
+handle_connect_and_send(_StarterPid, ReqId, HandlerPid, Result,
+ #state{profile_name = Profile,
+ handler_db = HandlerDb}) ->
+ case ets:lookup(HandlerDb, ReqId) of
+ [#handler_info{state = initiating} = HandlerInfo] when Result =:= ok ->
+ ?hcri("received connect-and-send ack for initiating handler", []),
+ HandlerInfo2 = HandlerInfo#handler_info{starter = undefined,
+ handler = HandlerPid,
+ state = operational},
+ ets:insert(HandlerDb, HandlerInfo2),
+ ok;
+
+ [#handler_info{state = canceled} = HandlerInfo] when Result =:= ok ->
+ ?hcri("received connect-and-send ack for canceled handler", []),
+ httpc_handler:cancel(ReqId, HandlerPid),
+ HandlerInfo2 = HandlerInfo#handler_info{starter = undefined,
+ handler = HandlerPid},
+ ets:insert(HandlerDb, HandlerInfo2),
+ ok;
+
+ [#handler_info{from = From}] ->
+ error_report(Profile,
+ "handler (~p) failed to connect and/or "
+ "send request ~p"
+ "~n Error: ~p", [HandlerPid, ReqId, Result]),
+ ?hcri("received connect-and-send error", [{result, Result}]),
+ Reason2 =
+ case Result of
+ {error, Reason} ->
+ {failed_connecting, Reason};
+ _ ->
+ {failed_connecting, Result}
+ end,
+ DummyReq = #request{id = ReqId},
+ httpc_response:send(From, httpc_response:error(DummyReq, Reason2)),
+ %% gen_server:reply(From, Error),
+ ets:delete(HandlerDb, ReqId),
+ ok;
+
+ [] ->
+ error_report(Profile,
+ "handler successfully (~p) started for unknown request ~p",
+ [HandlerPid, ReqId]),
+ httpc_handler:cancel(ReqId, HandlerPid)
+ end.
+
+
+handle_failed_starting_handler(_StarterPid, ReqId, Error,
+ #state{profile_name = Profile,
+ handler_db = HandlerDb}) ->
+ case ets:lookup(HandlerDb, ReqId) of
+ [#handler_info{state = canceled,
+ from = From}] ->
+ error_report(Profile,
+ "failed starting handler for request ~p"
+ "~n Error: ~p", [ReqId, Error]),
+ request_canceled(Profile, ReqId), % Fake signal from handler
+ gen_server:reply(From, Error),
+ ets:delete(HandlerDb, ReqId),
+ ok;
+
+ [#handler_info{from = From}] ->
+ error_report(Profile,
+ "failed starting handler for request ~p"
+ "~n Error: ~p", [ReqId, Error]),
+ gen_server:reply(From, Error),
+ ets:delete(HandlerDb, ReqId),
+ ok;
+
+ [] ->
+ error_report(Profile,
+ "failed starting handler for unknown request ~p"
+ "~n Error: ~p", [ReqId, Error]),
+ ok
+ end.
+
+
+maybe_handle_terminating_starter(MeybeStarterPid, Reason, HandlerDb) ->
+ Pattern = #handler_info{starter = MeybeStarterPid, _ = '_'},
+ case ets:match_object(HandlerDb, Pattern) of
+ [#handler_info{id = ReqId, from = From, state = initiating}] ->
+ Error = {error, {failed_starting_request_handler, Reason}},
+ gen_server:reply(From, Error),
+ ets:delete(HandlerDb, ReqId),
+ ok;
+ _ ->
+ ok
+ end.
+
+
+%% -----
+%% Act as an HTTP/0.9 client that does not know anything
+%% about persistent connections
+handle_request(#request{settings =
+ #http_options{version = "HTTP/0.9"}} = Request0,
+ State) ->
+ Request1 = handle_cookies(generate_request_id(Request0), State),
+ Hdrs0 = Request1#request.headers,
+ Hdrs1 = Hdrs0#http_request_h{connection = undefined},
+ Request2 = Request1#request{headers = Hdrs1},
+ create_handler_starter(Request2, State),
+ {ok, Request2#request.id, State};
+
+%% -----
+%% Act as an HTTP/1.0 client that does not
+%% use persistent connections
handle_request(#request{settings =
- #http_options{version = "HTTP/1.0"}} = Request,
+ #http_options{version = "HTTP/1.0"}} = Request0,
State) ->
- %% Act as an HTTP/1.0 client that does not
- %% use persistent connections
-
- NewRequest = handle_cookies(generate_request_id(Request), State),
- NewHeaders =
- (NewRequest#request.headers)#http_request_h{connection
- = "close"},
- start_handler(NewRequest#request{headers = NewHeaders}, State),
- {reply, {ok, NewRequest#request.id}, State};
-
-handle_request(Request, State = #state{options = Options}) ->
-
- NewRequest = handle_cookies(generate_request_id(Request), State),
- SessionType = session_type(Options),
- case select_session(Request#request.method,
- Request#request.address,
- Request#request.scheme, SessionType, State) of
+ Request1 = handle_cookies(generate_request_id(Request0), State),
+ Hdrs0 = Request1#request.headers,
+ Hdrs1 = Hdrs0#http_request_h{connection = "close"},
+ Request2 = Request1#request{headers = Hdrs1},
+ create_handler_starter(Request2, State),
+ {ok, Request2#request.id, State};
+
+
+%% -----
+handle_request(#request{method = Method,
+ address = Address,
+ scheme = Scheme} = Request0,
+ #state{options = Opts} = State) ->
+ Request1 = handle_cookies(generate_request_id(Request0), State),
+ SessionType = session_type(Opts),
+ case select_session(Method, Address, Scheme, SessionType, State) of
{ok, HandlerPid} ->
- pipeline_or_keep_alive(NewRequest, HandlerPid, State);
+ pipeline_or_keep_alive(Request1, HandlerPid, State);
no_connection ->
- start_handler(NewRequest, State);
- {no_session, OpenSessions} when OpenSessions
- < Options#options.max_sessions ->
- start_handler(NewRequest, State);
+ create_handler_starter(Request1, State);
+ {no_session, OpenSessions}
+ when OpenSessions < Opts#options.max_sessions ->
+ create_handler_starter(Request1, State);
{no_session, _} ->
%% Do not start any more persistent connections
%% towards this server.
- NewHeaders =
- (NewRequest#request.headers)#http_request_h{connection
- = "close"},
- start_handler(NewRequest#request{headers = NewHeaders}, State)
+ Hdrs0 = Request1#request.headers,
+ Hdrs1 = Hdrs0#http_request_h{connection = "close"},
+ Request2 = Request1#request{headers = Hdrs1},
+ create_handler_starter(Request2, State)
end,
- {reply, {ok, NewRequest#request.id}, State}.
+ {ok, Request1#request.id, State}.
-select_session(Method, HostPort, Scheme, SessionTyp,
- #state{options = #options{max_pipeline_length =
- MaxPipe,
+
+select_session(Method, HostPort, Scheme, SessionType,
+ #state{options = #options{max_pipeline_length = MaxPipe,
max_keep_alive_length = MaxKeepAlive},
session_db = SessionDb}) ->
- case httpc_request:is_idempotent(Method) or (SessionTyp == keep_alive) of
+ ?hcrd("select session", [{session_type, SessionType},
+ {max_pipeline_length, MaxPipe},
+ {max_keep_alive_length, MaxKeepAlive}]),
+ case httpc_request:is_idempotent(Method) orelse
+ (SessionType =:= keep_alive) of
true ->
- Candidates = ets:match(SessionDb,
- {'_', {HostPort, '$1'},
- false, Scheme, '_', '$2', SessionTyp}),
- select_session(Candidates, MaxKeepAlive, MaxPipe, SessionTyp);
+ %% Look for handlers connecting to this host (HostPort)
+ %% tcp_session with record name field (tcp_session) and
+ %% socket fields ignored. The fields id (part of: HostPort),
+ %% client_close, scheme and type specified.
+ %% The fields id (part of: HandlerPid) and queue_length
+ %% specified.
+ Pattern = #tcp_session{id = {HostPort, '$1'},
+ client_close = false,
+ scheme = Scheme,
+ socket = '_',
+ queue_length = '$2',
+ type = SessionType},
+ %% {'_', {HostPort, '$1'}, false, Scheme, '_', '$2', SessionTyp},
+ Candidates = ets:match(SessionDb, Pattern),
+ ?hcrd("select session", [{candidates, Candidates}]),
+ select_session(Candidates, MaxKeepAlive, MaxPipe, SessionType);
false ->
no_connection
end.
@@ -473,51 +758,92 @@ select_session(Candidates, Max, _, keep_alive) ->
select_session(Candidates, _, Max, pipeline) ->
select_session(Candidates, Max).
+select_session([] = _Candidates, _Max) ->
+ ?hcrd("select session - no candicate", []),
+ no_connection;
select_session(Candidates, Max) ->
- case Candidates of
+ NewCandidates =
+ [{Pid, Length} || [Pid, Length] <- Candidates, Length =< Max],
+ case lists:keysort(2, NewCandidates) of
[] ->
- no_connection;
- _ ->
- NewCandidates =
- lists:foldl(
- fun([Pid, Length], Acc) when Length =< Max ->
- [{Pid, Length} | Acc];
- (_, Acc) ->
- Acc
- end, [], Candidates),
-
- case lists:keysort(2, NewCandidates) of
- [] ->
- {no_session, length(Candidates)};
- [{HandlerPid, _} | _] ->
- {ok, HandlerPid}
- end
+ {no_session, length(Candidates)};
+ [{HandlerPid, _} | _] ->
+ ?hcrd("select session - found one", [{handler, HandlerPid}]),
+ {ok, HandlerPid}
end.
-pipeline_or_keep_alive(Request, HandlerPid, State) ->
+pipeline_or_keep_alive(#request{id = Id} = Request, HandlerPid, State) ->
+ ?hcrd("pipeline of keep-alive", [{id, Id}, {handler, HandlerPid}]),
case (catch httpc_handler:send(Request, HandlerPid)) of
ok ->
- ets:insert(State#state.handler_db, {Request#request.id,
- HandlerPid,
- Request#request.from});
- _ -> %timeout pipelining failed
- start_handler(Request, State)
+ ?hcrd("pipeline of keep-alive - successfully sent", []),
+ Entry = #handler_info{id = Id,
+ handler = HandlerPid,
+ state = operational},
+ ets:insert(State#state.handler_db, Entry);
+
+ _ -> %% timeout pipelining failed
+ ?hcrd("pipeline of keep-alive - failed sending -> "
+ "start a new handler", []),
+ create_handler_starter(Request, State)
end.
-start_handler(Request, State) ->
- {ok, Pid} =
- case is_inets_manager() of
- true ->
- httpc_handler_sup:start_child([Request, State#state.options,
- State#state.profile_name]);
- false ->
- httpc_handler:start_link(Request, State#state.options,
- State#state.profile_name)
- end,
- ets:insert(State#state.handler_db, {Request#request.id,
- Pid, Request#request.from}),
- erlang:monitor(process, Pid).
+create_handler_starter(#request{id = Id, from = From} = Request,
+ #state{profile_name = ProfileName,
+ options = Options,
+ handler_db = HandlerDb} = _State) ->
+ ?hcrv("create handler starter", [{id, Id}, {profile, ProfileName}]),
+ IsInetsManager = is_inets_manager(),
+ ManagerPid = self(),
+ StarterFun =
+ fun() ->
+ ?hcrd("handler starter - start",
+ [{id, Id},
+ {profile, ProfileName},
+ {inets_manager, IsInetsManager}]),
+ Result1 =
+ case IsInetsManager of
+ true ->
+ httpc_handler_sup:start_child(Options,
+ ProfileName);
+ false ->
+ httpc_handler:start_link(Options,
+ ProfileName)
+ end,
+ ?hcrd("handler starter - maybe connect and send",
+ [{id, Id}, {profile, ProfileName}, {result, Result1}]),
+ case Result1 of
+ {ok, HandlerPid} ->
+ Result2 = httpc_handler:connect_and_send(Request,
+ HandlerPid),
+ ?hcrd("handler starter - connected and sent",
+ [{id, Id}, {profile, ProfileName},
+ {handler, HandlerPid}, {result, Result2}]),
+ ConnAndSendMessage =
+ {connect_and_send,
+ self(), Id, HandlerPid, Result2},
+ ManagerPid ! ConnAndSendMessage;
+ {error, Reason} ->
+ StartFailureMessage =
+ {failed_starting_handler, self(), Id, Reason},
+ ManagerPid ! StartFailureMessage;
+ _ ->
+ StartFailureMessage =
+ {failed_starting_handler, self(), Id, Result1},
+ ManagerPid ! StartFailureMessage
+ end
+ end,
+ Starter = erlang:spawn_link(StarterFun),
+ ?hcrd("create handler starter - started", [{id, Id}, {starter, Starter}]),
+ Entry = #handler_info{id = Id,
+ starter = Starter,
+ from = From,
+ state = initiating},
+ ets:insert(HandlerDb, Entry),
+ ok.
+
+
is_inets_manager() ->
case get('$ancestors') of
[httpc_profile_sup | _] ->
@@ -539,25 +865,48 @@ generate_request_id(Request) ->
handle_cookies(Request, #state{options = #options{cookies = disabled}}) ->
Request;
-handle_cookies(Request = #request{scheme = Scheme, address = Address,
- path = Path, headers =
- Headers = #http_request_h{other = Other}},
- #state{cookie_db = Db}) ->
- case http_cookie:header(Scheme, Address, Path, Db) of
+handle_cookies(
+ #request{scheme = Scheme,
+ address = Address,
+ path = Path,
+ headers = #http_request_h{other = Other} = Hdrs} = Request,
+ #state{cookie_db = CookieDb}) ->
+ case httpc_cookie:header(CookieDb, Scheme, Address, Path) of
{"cookie", ""} ->
Request;
CookieHeader ->
- NewHeaders =
- Headers#http_request_h{other = [CookieHeader | Other]},
+ NewHeaders = Hdrs#http_request_h{other = [CookieHeader | Other]},
Request#request{headers = NewHeaders}
end.
do_store_cookies([], _) ->
ok;
-do_store_cookies([Cookie | Cookies], State) ->
- ok = http_cookie:insert(Cookie, State#state.cookie_db),
+do_store_cookies([Cookie | Cookies], #state{cookie_db = CookieDb} = State) ->
+ ok = httpc_cookie:insert(CookieDb, Cookie),
do_store_cookies(Cookies, State).
+
+
+session_db_name(ProfileName) ->
+ make_db_name(ProfileName, "__session_db").
+
+cookie_db_name(ProfileName) ->
+ make_db_name(ProfileName, "__cookie_db").
+
+session_cookie_db_name(ProfileName) ->
+ make_db_name(ProfileName, "__session_cookie_db").
+
+handler_db_name(ProfileName) ->
+ make_db_name(ProfileName, "__handler_db").
+
+make_db_name(ProfileName, Post) ->
+ list_to_atom(atom_to_list(ProfileName) ++ Post).
+
+
+
+call(ProfileName, Msg) ->
+ Timeout = infinity,
+ call(ProfileName, Msg, Timeout).
call(ProfileName, Msg, Timeout) ->
gen_server:call(ProfileName, Msg, Timeout).
@@ -621,6 +970,12 @@ handle_verbose(trace) ->
handle_verbose(_) ->
ok.
+
+error_report(Profile, F, A) ->
+ Report = io_lib:format("HTTPC-MANAGER<~p> " ++ F ++ "~n", [Profile | A]),
+ error_logger:error_report(Report).
+
+
%% d(F) ->
%% d(F, []).
diff --git a/lib/inets/src/http_client/httpc_profile_sup.erl b/lib/inets/src/http_client/httpc_profile_sup.erl
index 2351083435..29f86aa373 100644
--- a/lib/inets/src/http_client/httpc_profile_sup.erl
+++ b/lib/inets/src/http_client/httpc_profile_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -28,6 +28,7 @@
%% Supervisor callback
-export([init/1]).
+
%%%=========================================================================
%%% API
%%%=========================================================================
@@ -39,7 +40,8 @@ start_child(PropList) ->
undefined ->
{error, no_profile};
Profile ->
- Dir = proplists:get_value(data_dir, PropList, only_session_cookies),
+ Dir =
+ proplists:get_value(data_dir, PropList, only_session_cookies),
Spec = httpc_child_spec(Profile, Dir),
supervisor:start_child(?MODULE, Spec)
end.
@@ -63,12 +65,12 @@ stop_child(Profile) ->
end.
id(Profile) ->
- DefaultProfile = http:default_profile(),
+ DefaultProfile = httpc:default_profile(),
case Profile of
DefaultProfile ->
httpc_manager;
_ ->
- {http, Profile}
+ {httpc, Profile}
end.
@@ -98,7 +100,7 @@ child_spec([{httpc, PropList} | Rest], Acc) when is_list(PropList) ->
httpc_child_spec(Profile, Dir) ->
Name = id(Profile),
- StartFunc = {httpc_manager, start_link, [{Profile, Dir}]},
+ StartFunc = {httpc_manager, start_link, [Profile, Dir, inets]},
Restart = permanent,
Shutdown = 4000,
Modules = [httpc_manager],
diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl
index 3d66638d66..f15c5d4381 100644
--- a/lib/inets/src/http_client/httpc_request.erl
+++ b/lib/inets/src/http_client/httpc_request.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -39,14 +39,34 @@
%%
%% Description: Composes and sends a HTTP-request.
%%-------------------------------------------------------------------------
-send(SendAddr, #request{method = Method, scheme = Scheme,
- path = Path, pquery = Query, headers = Headers,
- content = Content, address = Address,
- abs_uri = AbsUri, headers_as_is = HeadersAsIs,
- settings = HttpOptions,
- userinfo = UserInfo},
+send(SendAddr, #request{method = Method,
+ scheme = Scheme,
+ path = Path,
+ pquery = Query,
+ headers = Headers,
+ content = Content,
+ address = Address,
+ abs_uri = AbsUri,
+ headers_as_is = HeadersAsIs,
+ settings = HttpOptions,
+ userinfo = UserInfo},
Socket) ->
+ ?hcrt("send",
+ [{send_addr, SendAddr},
+ {socket, Socket},
+ {method, Method},
+ {scheme, Scheme},
+ {path, Path},
+ {pquery, Query},
+ {headers, Headers},
+ {content, Content},
+ {address, Address},
+ {abs_uri, AbsUri},
+ {headers_as_is, HeadersAsIs},
+ {settings, HttpOptions},
+ {userinfo, UserInfo}]),
+
TmpHeaders = handle_user_info(UserInfo, Headers),
{TmpHeaders2, Body} =
@@ -70,10 +90,14 @@ send(SendAddr, #request{method = Method, scheme = Scheme,
Version = HttpOptions#http_options.version,
Message = [method(Method), " ", Uri, " ",
- version(Version), ?CRLF, headers(FinalHeaders, Version), ?CRLF, Body],
+ version(Version), ?CRLF,
+ headers(FinalHeaders, Version), ?CRLF, Body],
+
+ ?hcrd("send", [{message, Message}]),
http_transport:send(socket_type(Scheme), Socket, lists:append(Message)).
+
%%-------------------------------------------------------------------------
%% is_idempotent(Method) ->
%% Method = atom()
@@ -123,7 +147,7 @@ is_client_closing(Headers) ->
%%% Internal functions
%%%========================================================================
post_data(Method, Headers, {ContentType, Body}, HeadersAsIs)
- when Method == post; Method == put ->
+ when (Method =:= post) orelse (Method =:= put) ->
ContentLength = body_length(Body),
NewBody = case Headers#http_request_h.expect of
"100-continue" ->
diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl
index e2ba66f730..df7d40a33e 100644
--- a/lib/inets/src/http_client/httpc_response.erl
+++ b/lib/inets/src/http_client/httpc_response.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -132,8 +132,13 @@ result(Response = {{_,Code,_}, _, _}, Request) when (Code div 100) =:= 5 ->
result(Response, Request) ->
transparent(Response, Request).
-send(To, Msg) ->
- To ! {http, Msg}.
+send(Receiver, Msg) when is_pid(Receiver) ->
+ Receiver ! {http, Msg};
+send(Receiver, Msg) when is_function(Receiver) ->
+ (catch Receiver(Msg));
+send({Module, Function, Args}, Msg) ->
+ (catch apply(Module, Function, [Msg | Args])).
+
%%%========================================================================
%%% Internal functions
diff --git a/lib/inets/src/http_lib/Makefile b/lib/inets/src/http_lib/Makefile
index 27e7ee65c5..7f4c92861c 100644
--- a/lib/inets/src/http_lib/Makefile
+++ b/lib/inets/src/http_lib/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -22,6 +22,7 @@ include $(ERL_TOP)/make/target.mk
EBIN = ../../ebin
include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
# ----------------------------------------------------
# Application version
# ----------------------------------------------------
@@ -29,10 +30,12 @@ include ../../vsn.mk
VSN = $(INETS_VSN)
+
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
# ----------------------------------------------------
# Target Specs
@@ -50,10 +53,12 @@ ERL_FILES = $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
# ----------------------------------------------------
# INETS FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"' \
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
+
# ----------------------------------------------------
# FLAGS
@@ -82,6 +87,7 @@ clean:
docs:
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -96,6 +102,8 @@ release_spec: opt
release_docs_spec:
info:
+ @echo "APPLICATION = $(APPLICATION)"
@echo "INETS_DEBUG = $(INETS_DEBUG)"
@echo "INETS_FLAGS = $(INETS_FLAGS)"
@echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
+
diff --git a/lib/inets/src/http_lib/http_chunk.erl b/lib/inets/src/http_lib/http_chunk.erl
index cd20dce9d5..621bc68eae 100644
--- a/lib/inets/src/http_lib/http_chunk.erl
+++ b/lib/inets/src/http_lib/http_chunk.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%% Description: Implements chunked transfer encoding see RFC2616 section
@@ -186,13 +186,6 @@ decode_data(ChunkSize, TotalChunk,
Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize, Stream})
when ChunkSize =< size(TotalChunk) ->
case TotalChunk of
- %% Potential last chunk
- <<_:ChunkSize/binary, ?CR, ?LF, "0">> ->
- {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]};
- <<_:ChunkSize/binary, ?CR, ?LF, "0", ?CR>> ->
- {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]};
- <<_:ChunkSize/binary, ?CR, ?LF>> ->
- {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]};
%% Last chunk
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ";">> ->
%% Note ignore_extensions will call decode_trailer/1
@@ -223,6 +216,10 @@ decode_data(ChunkSize, TotalChunk,
NewBody,
integer_to_list(AccLength));
%% There are more chunks, so here we go agin...
+ <<Data:ChunkSize/binary, ?CR, ?LF>> ->
+ {NewBody, NewStream} =
+ stream(<<BodySoFar/binary, Data/binary>>, Stream),
+ {?MODULE, decode_size, [<<>>, [], {MaxBodySize, NewBody, AccLength, MaxHeaderSize, NewStream}]};
<<Data:ChunkSize/binary, ?CR, ?LF, Rest/binary>>
when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) ->
{NewBody, NewStream} =
diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl
index 8100d7183a..27a950174f 100644
--- a/lib/inets/src/http_lib/http_transport.erl
+++ b/lib/inets/src/http_lib/http_transport.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%
@@ -247,6 +247,7 @@ send(ip_comm, Socket, Message) ->
send({ssl, _}, Socket, Message) ->
ssl:send(Socket, Message).
+
%%-------------------------------------------------------------------------
%% close(SocketType, Socket) -> ok | {error, Reason}
%% SocketType = ip_comm | {ssl, _}
diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl
index b03b780cf8..ddb58c7116 100644
--- a/lib/inets/src/http_lib/http_util.erl
+++ b/lib/inets/src/http_lib/http_util.erl
@@ -1,27 +1,33 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
-module(http_util).
--export([to_upper/1, to_lower/1, convert_netscapecookie_date/1,
+-export([
+ to_upper/1, to_lower/1,
+ convert_netscapecookie_date/1,
hexlist_to_integer/1, integer_to_hexlist/1,
- convert_month/1, is_hostname/1]).
+ convert_month/1,
+ is_hostname/1,
+ timestamp/0, timeout/2
+ ]).
+
%%%=========================================================================
%%% Internal application API
@@ -100,6 +106,21 @@ convert_month("Dec") -> 12.
is_hostname(Dest) ->
inet_parse:domain(Dest).
+
+timestamp() ->
+ {A,B,C} = os:timestamp(),
+ A*1000000000+B*1000+(C div 1000).
+
+timeout(Timeout, Started) ->
+ %% NewTimeout = Timeout - (timestamp() - Started),
+ case Timeout - (timestamp() - Started) of
+ NewTimeout when Timeout > 0 ->
+ NewTimeout;
+ _ ->
+ 0
+ end.
+
+
%%%========================================================================
%%% Internal functions
%%%========================================================================
diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile
index 4bbd23df3f..ce1405011e 100644
--- a/lib/inets/src/http_server/Makefile
+++ b/lib/inets/src/http_server/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -33,7 +33,7 @@ VSN = $(INETS_VSN)
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# ----------------------------------------------------
@@ -92,7 +92,7 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
# INETS FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"'
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
# ----------------------------------------------------
@@ -133,6 +133,7 @@ release_spec: opt
release_docs_spec:
info:
+ @echo "APPLICATION = $(APPLICATION)"
@echo "INETS_DEBUG = $(INETS_DEBUG)"
@echo "INETS_FLAGS = $(INETS_FLAGS)"
@echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl
index 554f162fc5..8fe54ccef6 100644
--- a/lib/inets/src/http_server/httpd.erl
+++ b/lib/inets/src/http_server/httpd.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -358,7 +358,7 @@ foreach([KeyValue|Rest]) ->
get_addr_and_port(ConfigFile) ->
case httpd_conf:load(ConfigFile) of
{ok, ConfigList} ->
- case httpd_conf:validate_properties(ConfigList) of
+ case (catch httpd_conf:validate_properties(ConfigList)) of
{ok, Config} ->
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
@@ -506,7 +506,7 @@ get_status(Addr,Port,Timeout) when is_integer(Port) ->
end.
do_reload_config(ConfigList, Mode) ->
- case httpd_conf:validate_properties(ConfigList) of
+ case (catch httpd_conf:validate_properties(ConfigList)) of
{ok, Config} ->
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl
index 9c93e2c5fe..3e498d1db7 100644
--- a/lib/inets/src/http_server/httpd_conf.erl
+++ b/lib/inets/src/http_server/httpd_conf.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -864,17 +864,22 @@ load_traverse(Line, [Context|Contexts], [Module|Modules], NewContexts,
{'EXIT', {undef, _}} ->
?hdrt("does not implement load", []),
load_traverse(Line, Contexts, Modules,
- [Context|NewContexts], ConfigList,yes);
+ [Context|NewContexts], ConfigList, yes);
{'EXIT', Reason} ->
error_logger:error_report({'EXIT', Reason}),
load_traverse(Line, Contexts, Modules,
[Context|NewContexts], ConfigList, State);
+ ok ->
+ ?hdrt("line processed", []),
+ load_traverse(Line, Contexts, Modules,
+ [Context|NewContexts], ConfigList, yes);
+
{ok, NewContext} ->
?hdrt("line processed", [{new_context, NewContext}]),
load_traverse(Line, Contexts, Modules,
- [NewContext|NewContexts], ConfigList,yes);
+ [NewContext|NewContexts], ConfigList, yes);
{ok, NewContext, ConfigEntry} when is_tuple(ConfigEntry) ->
?hdrt("line processed",
diff --git a/lib/inets/src/http_server/httpd_instance_sup.erl b/lib/inets/src/http_server/httpd_instance_sup.erl
index 3b5464132c..0aaeb838c2 100644
--- a/lib/inets/src/http_server/httpd_instance_sup.erl
+++ b/lib/inets/src/http_server/httpd_instance_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -37,7 +37,7 @@
%%% Internal Application API
%%%=========================================================================
start_link([{_, _}| _] = Config, AcceptTimeout, Debug) ->
- case httpd_conf:validate_properties(Config) of
+ case (catch httpd_conf:validate_properties(Config)) of
{ok, Config2} ->
Address = proplists:get_value(bind_address, Config2),
Port = proplists:get_value(port, Config2),
@@ -66,7 +66,7 @@ start_link(ConfigFile, AcceptTimeout, Debug) ->
start_link([{_, _}| _] = Config, AcceptTimeout, ListenInfo, Debug) ->
- case httpd_conf:validate_properties(Config) of
+ case (catch httpd_conf:validate_properties(Config)) of
{ok, Config2} ->
Address = proplists:get_value(bind_address, Config2),
Port = proplists:get_value(port, Config2),
@@ -154,7 +154,7 @@ make_name(Address,Port) ->
file_2_config(ConfigFile) ->
case httpd_conf:load(ConfigFile) of
{ok, ConfigList} ->
- case httpd_conf:validate_properties(ConfigList) of
+ case (catch httpd_conf:validate_properties(ConfigList)) of
{ok, Config} ->
Address = proplists:get_value(bind_address, ConfigList),
Port = proplists:get_value(port, ConfigList),
diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index ad2cc4bda3..8eee08e766 100644
--- a/lib/inets/src/http_server/httpd_request.erl
+++ b/lib/inets/src/http_server/httpd_request.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -68,10 +68,11 @@ body_data(Headers, Body) ->
{binary_to_list(BodyThisReq), Next}
end.
+
%%-------------------------------------------------------------------------
%% validate(Method, Uri, Version) -> ok | {error, {bad_request, Reason} |
%% {error, {not_supported, {Method, Uri, Version}}
-%% Method = "HEAD" | "GET" | "POST" | "TRACE"
+%% Method = "HEAD" | "GET" | "POST" | "TRACE" | "PUT" | "DELETE"
%% Uri = uri()
%% Version = "HTTP/N.M"
%% Description: Checks that HTTP-request-line is valid.
@@ -84,6 +85,10 @@ validate("GET", Uri, "HTTP/0.9") ->
validate_uri(Uri);
validate("GET", Uri, "HTTP/1." ++ _N) ->
validate_uri(Uri);
+validate("PUT", Uri, "HTTP/1." ++ _N) ->
+ validate_uri(Uri);
+validate("DELETE", Uri, "HTTP/1." ++ _N) ->
+ validate_uri(Uri);
validate("POST", Uri, "HTTP/1." ++ _N) ->
validate_uri(Uri);
validate("TRACE", Uri, "HTTP/1." ++ N) when hd(N) >= $1 ->
diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl
index fc41994727..3399f78b53 100644
--- a/lib/inets/src/http_server/httpd_sup.erl
+++ b/lib/inets/src/http_server/httpd_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -169,7 +169,7 @@ httpd_child_spec([Value| _] = Config, AcceptTimeout, Debug)
httpd_child_spec(ConfigFile, AcceptTimeout, Debug) ->
case httpd_conf:load(ConfigFile) of
{ok, ConfigList} ->
- case httpd_conf:validate_properties(ConfigList) of
+ case (catch httpd_conf:validate_properties(ConfigList)) of
{ok, Config} ->
Address = proplists:get_value(bind_address, Config, any),
Port = proplists:get_value(port, Config, 80),
diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl
index 7073f5405d..ec0a12242f 100644
--- a/lib/inets/src/http_server/mod_alias.erl
+++ b/lib/inets/src/http_server/mod_alias.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -28,44 +28,51 @@
path/3]).
-include("httpd.hrl").
+-include("httpd_internal.hrl").
-define(VMODULE,"ALIAS").
%% do
-do(Info) ->
- case proplists:get_value(status, Info#mod.data) of
+do(#mod{data = Data} = Info) ->
+ ?hdrt("do", []),
+ case proplists:get_value(status, Data) of
%% A status code has been generated!
{_StatusCode, _PhraseArgs, _Reason} ->
- {proceed,Info#mod.data};
+ {proceed, Data};
%% No status code has been generated!
undefined ->
- case proplists:get_value(response, Info#mod.data) of
+ case proplists:get_value(response, Data) of
%% No response has been generated!
undefined ->
do_alias(Info);
%% A response has been generated or sent!
_Response ->
- {proceed, Info#mod.data}
+ {proceed, Data}
end
end.
-do_alias(Info) ->
- {ShortPath, Path, AfterPath} =
- real_name(Info#mod.config_db,
- Info#mod.request_uri,
- httpd_util:multi_lookup(Info#mod.config_db,alias)),
+do_alias(#mod{config_db = ConfigDB,
+ request_uri = ReqURI,
+ data = Data}) ->
+ {ShortPath, Path, AfterPath} =
+ real_name(ConfigDB, ReqURI, which_alias(ConfigDB)),
+ ?hdrt("real name",
+ [{request_uri, ReqURI},
+ {short_path, ShortPath},
+ {path, Path},
+ {after_path, AfterPath}]),
%% Relocate if a trailing slash is missing else proceed!
LastChar = lists:last(ShortPath),
case file:read_file_info(ShortPath) of
- {ok, FileInfo} when FileInfo#file_info.type == directory,
- LastChar /= $/ ->
- ServerName = httpd_util:lookup(Info#mod.config_db, server_name),
- Port = port_string(httpd_util:lookup(Info#mod.config_db,port, 80)),
- URL = "http://" ++ ServerName ++ Port ++
- Info#mod.request_uri ++ "/",
+ {ok, FileInfo} when ((FileInfo#file_info.type =:= directory) andalso
+ (LastChar =/= $/)) ->
+ ?hdrt("directory and last-char is a /", []),
+ ServerName = which_server_name(ConfigDB),
+ Port = port_string( which_port(ConfigDB) ),
+ URL = "http://" ++ ServerName ++ Port ++ ReqURI ++ "/",
ReasonPhrase = httpd_util:reason_phrase(301),
- Message = httpd_util:message(301, URL, Info#mod.config_db),
+ Message = httpd_util:message(301, URL, ConfigDB),
{proceed,
[{response,
{301, ["Location: ", URL, "\r\n"
@@ -76,25 +83,26 @@ do_alias(Info) ->
"<BODY>\n<H1>",ReasonPhrase,
"</H1>\n", Message,
"\n</BODY>\n</HTML>\n"]}}|
- [{real_name, {Path, AfterPath}} | Info#mod.data]]};
+ [{real_name, {Path, AfterPath}} | Data]]};
_NoFile ->
- {proceed,[{real_name, {Path, AfterPath}} | Info#mod.data]}
+ {proceed, [{real_name, {Path, AfterPath}} | Data]}
end.
port_string(80) ->
"";
port_string(Port) ->
- ":"++integer_to_list(Port).
+ ":" ++ integer_to_list(Port).
%% real_name
real_name(ConfigDB, RequestURI, []) ->
- DocumentRoot = httpd_util:lookup(ConfigDB, document_root, ""),
+ DocumentRoot = which_document_root(ConfigDB),
RealName = DocumentRoot ++ RequestURI,
{ShortPath, _AfterPath} = httpd_util:split_path(RealName),
- {Path, AfterPath} = httpd_util:split_path(default_index(ConfigDB,
- RealName)),
+ {Path, AfterPath} =
+ httpd_util:split_path(default_index(ConfigDB, RealName)),
{ShortPath, Path, AfterPath};
+
real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) ->
case inets_regexp:match(RequestURI, "^" ++ FakeName) of
{match, _, _} ->
@@ -105,7 +113,7 @@ real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) ->
httpd_util:split_path(default_index(ConfigDB, ActualName)),
{ShortPath, Path, AfterPath};
nomatch ->
- real_name(ConfigDB,RequestURI,Rest)
+ real_name(ConfigDB, RequestURI, Rest)
end.
%% real_script_name
@@ -113,20 +121,21 @@ real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) ->
real_script_name(_ConfigDB, _RequestURI, []) ->
not_a_script;
real_script_name(ConfigDB, RequestURI, [{FakeName,RealName} | Rest]) ->
- case inets_regexp:match(RequestURI,"^"++FakeName) of
+ case inets_regexp:match(RequestURI, "^" ++ FakeName) of
{match,_,_} ->
- {ok,ActualName,_}=inets_regexp:sub(RequestURI,"^"++FakeName,RealName),
- httpd_util:split_script_path(default_index(ConfigDB,ActualName));
+ {ok, ActualName, _} =
+ inets_regexp:sub(RequestURI, "^" ++ FakeName, RealName),
+ httpd_util:split_script_path(default_index(ConfigDB, ActualName));
nomatch ->
- real_script_name(ConfigDB,RequestURI,Rest)
+ real_script_name(ConfigDB, RequestURI, Rest)
end.
%% default_index
default_index(ConfigDB, Path) ->
case file:read_file_info(Path) of
- {ok, FileInfo} when FileInfo#file_info.type == directory ->
- DirectoryIndex = httpd_util:lookup(ConfigDB, directory_index, []),
+ {ok, FileInfo} when FileInfo#file_info.type =:= directory ->
+ DirectoryIndex = which_directory_index(ConfigDB),
append_index(Path, DirectoryIndex);
_ ->
Path
@@ -147,9 +156,9 @@ append_index(RealName, [Index | Rest]) ->
path(Data, ConfigDB, RequestURI) ->
case proplists:get_value(real_name, Data) of
undefined ->
- DocumentRoot = httpd_util:lookup(ConfigDB, document_root, ""),
+ DocumentRoot = which_document_root(ConfigDB),
{Path, _AfterPath} =
- httpd_util:split_path(DocumentRoot++RequestURI),
+ httpd_util:split_path(DocumentRoot ++ RequestURI),
Path;
{Path, _AfterPath} ->
Path
@@ -164,7 +173,7 @@ path(Data, ConfigDB, RequestURI) ->
load("DirectoryIndex " ++ DirectoryIndex, []) ->
{ok, DirectoryIndexes} = inets_regexp:split(DirectoryIndex," "),
{ok,[], {directory_index, DirectoryIndexes}};
-load("Alias " ++ Alias,[]) ->
+load("Alias " ++ Alias, []) ->
case inets_regexp:split(Alias," ") of
{ok, [FakeName, RealName]} ->
{ok,[],{alias,{FakeName,RealName}}};
@@ -191,13 +200,13 @@ store({directory_index, Value} = Conf, _) when is_list(Value) ->
end;
store({directory_index, Value}, _) ->
{error, {wrong_type, {directory_index, Value}}};
-store({alias, {Fake, Real}} = Conf, _) when is_list(Fake),
- is_list(Real) ->
+store({alias, {Fake, Real}} = Conf, _)
+ when is_list(Fake) andalso is_list(Real) ->
{ok, Conf};
store({alias, Value}, _) ->
{error, {wrong_type, {alias, Value}}};
-store({script_alias, {Fake, Real}} = Conf, _) when is_list(Fake),
- is_list(Real) ->
+store({script_alias, {Fake, Real}} = Conf, _)
+ when is_list(Fake) andalso is_list(Real) ->
{ok, Conf};
store({script_alias, Value}, _) ->
{error, {wrong_type, {script_alias, Value}}}.
@@ -208,3 +217,21 @@ is_directory_index_list([Head | Tail]) when is_list(Head) ->
is_directory_index_list(Tail);
is_directory_index_list(_) ->
false.
+
+
+%% ---------------------------------------------------------------------
+
+which_alias(ConfigDB) ->
+ httpd_util:multi_lookup(ConfigDB, alias).
+
+which_server_name(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, server_name).
+
+which_port(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, port, 80).
+
+which_document_root(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, document_root, "").
+
+which_directory_index(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, directory_index, []).
diff --git a/lib/inets/src/http_server/mod_cgi.erl b/lib/inets/src/http_server/mod_cgi.erl
index ab12a3b57b..33605b9698 100644
--- a/lib/inets/src/http_server/mod_cgi.erl
+++ b/lib/inets/src/http_server/mod_cgi.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -335,6 +335,8 @@ script_elements(#mod{method = "GET"}, {PathInfo, QueryString}) ->
[{query_string, QueryString}, {path_info, PathInfo}];
script_elements(#mod{method = "POST", entity_body = Body}, _) ->
[{entity_body, Body}];
+script_elements(#mod{method = "PUT", entity_body = Body}, _) ->
+ [{entity_body, Body}];
script_elements(_, _) ->
[].
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index dd6f62ae2d..484d4b3fb4 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -249,7 +249,24 @@ erl(#mod{method = Method} = ModData, ESIBody, Modules)
{proceed, [{status,{400, none, BadRequest}} | ModData#mod.data]}
end;
-erl(#mod{method = "POST", entity_body = Body} = ModData, ESIBody, Modules) ->
+erl(#mod{request_uri = ReqUri,
+ method = "PUT",
+ http_version = Version,
+ data = Data}, _ESIBody, _Modules) ->
+ {proceed, [{status,{501,{"PUT", ReqUri, Version},
+ ?NICE("Erl mechanism doesn't support method PUT")}}|
+ Data]};
+
+erl(#mod{request_uri = ReqUri,
+ method = "DELETE",
+ http_version = Version,
+ data = Data}, _ESIBody, _Modules) ->
+ {proceed,[{status,{501,{"DELETE", ReqUri, Version},
+ ?NICE("Erl mechanism doesn't support method DELETE")}}|
+ Data]};
+
+erl(#mod{method = "POST",
+ entity_body = Body} = ModData, ESIBody, Modules) ->
case httpd_util:split(ESIBody,":|%3A|/",2) of
{ok,[ModuleName, Function]} ->
generate_webpage(ModData, ESIBody, Modules,
@@ -444,8 +461,26 @@ input_type([_First|Rest]) ->
%%------------------------ Eval mechanism --------------------------------
-eval(#mod{request_uri = ReqUri, method = "POST",
- http_version = Version, data = Data}, _ESIBody, _Modules) ->
+eval(#mod{request_uri = ReqUri,
+ method = "PUT",
+ http_version = Version,
+ data = Data}, _ESIBody, _Modules) ->
+ {proceed,[{status,{501,{"PUT", ReqUri, Version},
+ ?NICE("Eval mechanism doesn't support method PUT")}}|
+ Data]};
+
+eval(#mod{request_uri = ReqUri,
+ method = "DELETE",
+ http_version = Version,
+ data = Data}, _ESIBody, _Modules) ->
+ {proceed,[{status,{501,{"DELETE", ReqUri, Version},
+ ?NICE("Eval mechanism doesn't support method DELETE")}}|
+ Data]};
+
+eval(#mod{request_uri = ReqUri,
+ method = "POST",
+ http_version = Version,
+ data = Data}, _ESIBody, _Modules) ->
{proceed,[{status,{501,{"POST", ReqUri, Version},
?NICE("Eval mechanism doesn't support method POST")}}|
Data]};
diff --git a/lib/inets/src/inets_app/Makefile b/lib/inets/src/inets_app/Makefile
index 2dab99386a..33c9e34a3a 100644
--- a/lib/inets/src/inets_app/Makefile
+++ b/lib/inets/src/inets_app/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -22,6 +22,7 @@ include $(ERL_TOP)/make/target.mk
EBIN = ../../ebin
include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
# ----------------------------------------------------
# Application version
# ----------------------------------------------------
@@ -32,12 +33,13 @@ VSN = $(INETS_VSN)
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
+
MODULES = \
inets_service \
inets \
@@ -49,7 +51,8 @@ HRL_FILES = inets_internal.hrl
ERL_FILES = $(MODULES:%=%.erl)
-TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) \
+TARGET_FILES= \
+ $(MODULES:%=$(EBIN)/%.$(EMULATOR)) \
$(APP_TARGET) \
$(APPUP_TARGET)
@@ -66,7 +69,7 @@ APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
# ----------------------------------------------------
# INETS FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"' \
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
# ----------------------------------------------------
@@ -108,14 +111,15 @@ $(APPUP_TARGET): $(APPUP_SRC) ../../vsn.mk
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src
$(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
release_docs_spec:
info:
+ @echo "APPLICATION = $(APPLICATION)"
@echo "INETS_DEBUG = $(INETS_DEBUG)"
@echo "INETS_FLAGS = $(INETS_FLAGS)"
@echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src
index 6524c3b19b..04f6365b98 100644
--- a/lib/inets/src/inets_app/inets.app.src
+++ b/lib/inets/src/inets_app/inets.app.src
@@ -1,19 +1,19 @@
%% This is an -*- erlang -*- file.
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -34,7 +34,8 @@
ftp_sup,
%% HTTP client:
- http,
+ http, %% Old client API module
+ httpc, %% New client API module
httpc_handler,
httpc_handler_sup,
httpc_manager,
@@ -42,7 +43,7 @@
httpc_request,
httpc_response,
httpc_sup,
- http_cookie,
+ httpc_cookie,
http_uri, %% Proably will by used by server also in the future
diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src
index 0112a64239..2efa7ccb60 100644
--- a/lib/inets/src/inets_app/inets.appup.src
+++ b/lib/inets/src/inets_app/inets.appup.src
@@ -1,34 +1,31 @@
%% This is an -*- erlang -*- file.
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
{"%VSN%",
[
{"5.2",
[
- {load_module, inets, soft_purge, soft_purge, []}
+ {restart_application, inets}
]
},
{"5.1.3",
[
- {load_module, httpd_response, soft_purge, soft_purge, []},
- {update, ftp, {advanced, upgrade_from_pre_5_12},
- soft_purge, soft_purge, []},
- {update, httpc_handler, soft, soft_purge, soft_purge, []}
+ {restart_application, inets}
]
},
{"5.1.2",
@@ -40,15 +37,12 @@
[
{"5.2",
[
- {load_module, inets, soft_purge, soft_purge, []}
+ {restart_application, inets}
]
},
{"5.1.3",
[
- {load_module, httpd_response, soft_purge, soft_purge, []},
- {update, ftp, {advanced, downgrade_to_pre_5_12},
- soft_purge, soft_purge, []},
- {update, httpc_handler, soft, soft_purge, soft_purge, []}
+ {restart_application, inets}
]
},
{"5.1.2",
diff --git a/lib/inets/src/inets_app/inets.erl b/lib/inets/src/inets_app/inets.erl
index 77cb14cc20..7e3f862ee7 100644
--- a/lib/inets/src/inets_app/inets.erl
+++ b/lib/inets/src/inets_app/inets.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -522,9 +522,7 @@ change_pattern({Mod, Service, Pattern})
catch
exit:{Where, Reason} ->
{error, {Where, Reason}}
- end;
- _ ->
- exit({bad_pattern, Pattern})
+ end
end,
ok.
@@ -728,7 +726,7 @@ call_service(Service, Call, Args) ->
service_module(tftpd) ->
tftp;
service_module(httpc) ->
- http;
+ httpc;
service_module(ftpc) ->
ftp;
service_module(Service) ->
diff --git a/lib/inets/src/tftp/Makefile b/lib/inets/src/tftp/Makefile
index 63f70f7943..b4339da1e2 100644
--- a/lib/inets/src/tftp/Makefile
+++ b/lib/inets/src/tftp/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
#
#
@@ -33,7 +33,8 @@ VSN = $(INETS_VSN)
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
# ----------------------------------------------------
# Target Specs
@@ -53,10 +54,12 @@ ERL_FILES = $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
# ----------------------------------------------------
# INETS FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"' \
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
+
# ----------------------------------------------------
# FLAGS
@@ -64,6 +67,8 @@ INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"' \
ERL_COMPILE_FLAGS += $(INETS_FLAGS) \
+'{parse_transform,sys_pre_attributes}' \
+'{attribute,insert,app_vsn,$(APP_VSN)}'
+
+
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
@@ -90,6 +95,7 @@ release_spec: opt
release_docs_spec:
info:
+ @echo "APPLICATION = $(APPLICATION)"
@echo "INETS_DEBUG = $(INETS_DEBUG)"
@echo "INETS_FLAGS = $(INETS_FLAGS)"
@echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile
new file mode 100644
index 0000000000..668752da9e
--- /dev/null
+++ b/lib/inets/test/Makefile
@@ -0,0 +1,343 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2010. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+#
+# For an outline of how this all_SUITE_data stuff works, see the
+# make file ../../ssl/test/Makefile.
+#
+include $(ERL_TOP)/make/target.mk
+
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN = $(INETS_VSN)
+
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+INCLUDES = -I. \
+ -I$(ERL_TOP)/lib/test_server/include/ \
+ -I$(ERL_TOP)/lib/inets/src/inets_app \
+ -I$(ERL_TOP)/lib/inets/src/http_lib \
+ -I$(ERL_TOP)/lib/inets/src/http_client \
+ -I$(ERL_TOP)/lib/inets/src/ftp
+
+CP = cp
+
+ifeq ($(TESTROOT_DIR),)
+TESTROOT_DIR = /ldisk/tests/$(USER)/inets
+endif
+
+ifeq ($(INETS_DATA_DIR),)
+INETS_DATA_DIR = $(TESTROOT_DIR)/data_dir
+endif
+
+ifeq ($(INETS_PRIV_DIR),)
+INETS_PRIV_DIR = $(TESTROOT_DIR)/priv_dir
+endif
+
+INETS_FLAGS = -Dinets_data_dir='"$(INETS_DATA_DIR)"' \
+ -Dinets_priv_dir='"$(INETS_PRIV_DIR)"'
+
+
+###
+### test suite debug flags
+###
+ifeq ($(FTP_DEBUG_CLIENT),)
+ FTP_DEBUG_CLIENT = y
+endif
+
+ifeq ($(FTP_DEBUG_CLIENT),)
+ FTP_FLAGS += -Dftp_debug_client
+endif
+
+ifeq ($(FTP_TRACE_CLIENT),)
+ FTP_DEBUG_CLIENT = y
+endif
+
+ifeq ($(FTP_TRACE_CLIENT),y)
+ FTP_FLAGS += -Dftp_trace_client
+endif
+
+ifneq ($(FTP_DEBUG),)
+ FTP_DEBUG = s
+endif
+
+ifeq ($(FTP_DEBUG),l)
+ FTP_FLAGS += -Dftp_log
+endif
+
+ifeq ($(FTP_DEBUG),d)
+ FTP_FLAGS += -Dftp_debug -Dftp_log
+endif
+
+ifeq ($(INETS_DEBUG),)
+ INETS_DEBUG = d
+endif
+
+ifeq ($(INETS_DEBUG),l)
+ INETS_FLAGS += -Dinets_log
+endif
+
+ifeq ($(INETS_DEBUG),d)
+ INETS_FLAGS += -Dinets_debug -Dinets_log
+endif
+
+
+###
+### HTTPD verbosity flags
+###
+
+ifneq ($(MANV),)
+ INETS_FLAGS += -Dhttpd_manager_verbosity=$(MANV)
+else
+ INETS_FLAGS += -Dhttpd_manager_verbosity=trace
+endif
+
+ifneq ($(REQV),)
+ INETS_FLAGS += -Dhttpd_request_handler_verbosity=$(REQV)
+else
+ INETS_FLAGS += -Dhttpd_request_handler_verbosity=trace
+endif
+
+ifneq ($(ACCV),)
+ INETS_FLAGS += -Dhttpd_acceptor_verbosity=$(ACCV)
+else
+ INETS_FLAGS += -Dhttpd_acceptor_verbosity=trace
+endif
+
+ifneq ($(AUTHV),)
+ INETS_FLAGS += -Dhttpd_auth_verbosity=$(AUTHV)
+else
+ INETS_FLAGS += -Dhttpd_auth_verbosity=log
+endif
+
+ifneq ($(SECV),)
+ INETS_FLAGS += -Dhttpd_security_verbosity=$(SECV)
+else
+ INETS_FLAGS += -Dhttpd_security_verbosity=log
+endif
+
+INETS_ROOT = ../../inets
+
+MODULES = \
+ inets_test_lib \
+ ftp_SUITE \
+ ftp_format_SUITE \
+ ftp_solaris8_sparc_test \
+ ftp_solaris9_sparc_test \
+ ftp_solaris10_sparc_test \
+ ftp_solaris10_x86_test \
+ ftp_linux_x86_test \
+ ftp_linux_ppc_test \
+ ftp_macosx_x86_test \
+ ftp_macosx_ppc_test \
+ ftp_openbsd_x86_test \
+ ftp_freebsd_x86_test \
+ ftp_netbsd_x86_test \
+ ftp_windows_xp_test \
+ ftp_windows_2003_server_test \
+ ftp_suite_lib \
+ ftp_ticket_test \
+ http_format_SUITE \
+ httpc_SUITE \
+ httpc_cookie_SUITE \
+ httpd_SUITE \
+ httpd_basic_SUITE \
+ httpd_mod \
+ httpd_block \
+ httpd_load \
+ httpd_time_test \
+ httpd_1_1 \
+ httpd_test_lib \
+ inets_sup_SUITE \
+ inets_SUITE \
+ inets_app_test \
+ inets_appup_test \
+ tftp_test_lib \
+ tftp_SUITE
+
+
+EBIN = .
+
+HRL_FILES = inets_test_lib.hrl \
+ inets_internal.hrl \
+ ftp_internal.hrl \
+ httpc_internal.hrl \
+ http_internal.hrl \
+ tftp_test_lib.hrl
+
+ERL_FILES = $(MODULES:%=%.erl)
+
+SOURCE = $(ERL_FILES) $(HRL_FILES)
+
+TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+INETS_SPECS = inets.spec inets.spec.vxworks
+INETS_FILES = inets.config $(INETS_SPECS)
+
+# SUB_SUITES = \
+# inets_sup_suite \
+# inets_httpd_suite \
+# inets_httpc_suite \
+# inets_ftp_suite \
+# inets_tftp_suite
+
+INETS_DATADIRS = inets_SUITE_data inets_sup_SUITE_data
+HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data
+HTTPC_DATADIRS = httpc_SUITE_data
+FTP_DATADIRS = ftp_SUITE_data
+
+DATADIRS = $(INETS_DATADIRS) $(HTTPD_DATADIRS) $(HTTPC_DATADIRS) $(FTP_DATADIRS)
+
+EMAKEFILE = Emakefile
+MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile)
+
+ifeq ($(MAKE_EMAKE),)
+BUILDTARGET = $(TARGET_FILES)
+RELTEST_FILES = $(INETS_SPECS) $(SOURCE)
+else
+BUILDTARGET = emakebuild
+RELTEST_FILES = $(EMAKEFILE) $(INETS_SPECS) $(SOURCE)
+endif
+
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+
+RELTESTSYSDIR = $(RELEASE_PATH)/inets_test
+RELTESTSYSALLDATADIR = $(RELTESTSYSDIR)/all_SUITE_data
+RELTESTSYSBINDIR = $(RELTESTSYSALLDATADIR)/bin
+
+
+# ----------------------------------------------------
+# FLAGS
+# The path to the test_server ebin dir is needed when
+# running the target "targets".
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += -pa ../../../internal_tools/test_server/ebin \
+ $(INCLUDES) $(FTP_FLAGS) $(INETS_FLAGS)
+
+# ----------------------------------------------------
+# Targets
+# erl -sname kalle -pa ../ebin
+# If you intend to run the test suite locally (private), then
+# there is some requirements:
+# 1) INETS_PRIV_DIR must be created
+# ----------------------------------------------------
+
+tests debug opt: $(BUILDTARGET)
+
+targets: $(TARGET_FILES)
+
+.PHONY: emakebuild
+
+emakebuild: $(EMAKEFILE)
+
+$(EMAKEFILE):
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' | grep -v Warning > $(EMAKEFILE)
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) | grep -v Warning >> $(EMAKEFILE)
+
+clean:
+ rm -f $(EMAKEFILE)
+ rm -f $(TARGET_FILES)
+ rm -f core *~
+
+docs:
+
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/test
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/test
+ $(INSTALL_DATA) $(INETS_FILES) $(RELSYSDIR)/test
+ @for d in $(DATADIRS); do \
+ echo "installing data dir $$d"; \
+ echo $$d/TAR.exclude2 > $$d/TAR.exclude2; \
+ cat $$d/TAR.exclude >> $$d/TAR.exclude2; \
+ find $$d -name '*.contrib*' >> $$d/TAR.exclude2; \
+ find $$d -name '*.keep*' >> $$d/TAR.exclude2; \
+ find $$d -name '*.mkelem*' >> $$d/TAR.exclude2; \
+ find $$d -name '*~' >> $$d/TAR.exclude2; \
+ find $$d -name 'erl_crash.dump' >> $$d/TAR.exclude2; \
+ find $$d -name 'core' >> $$d/TAR.exclude2; \
+ find $$d -name '.cmake.state' >> $$d/TAR.exclude2; \
+ tar cfX - $$d/TAR.exclude2 $$d | (cd $(RELSYSDIR)/test; tar xf -); \
+ done
+
+release_tests_spec: opt
+ $(INSTALL_DIR) $(RELTESTSYSDIR)
+ $(INSTALL_DATA) $(RELTEST_FILES) $(RELTESTSYSDIR)
+ chmod -f -R u+w $(RELTESTSYSDIR)
+ tar chf - $(DATADIRS) | (cd $(RELTESTSYSDIR); tar xf -)
+ $(INSTALL_DIR) $(RELTESTSYSALLDATADIR)
+ $(INSTALL_DIR) $(RELTESTSYSBINDIR)
+ chmod -f -R +x $(RELTESTSYSBINDIR)
+ $(INSTALL_DIR) $(RELTESTSYSALLDATADIR)/win32/lib
+
+release_docs_spec:
+
+info:
+ @echo "MAKE_EMAKE = $(MAKE_EMAKE)"
+ @echo "EMAKEFILE = $(EMAKEFILE)"
+ @echo "BUILDTARGET = $(BUILDTARGET)"
+ @echo ""
+ @echo "MODULES = $(MODULES)"
+ @echo "ERL_FILES = $(ERL_FILES)"
+ @echo "SOURCE = $(SOURCE)"
+ @echo "TARGET_FILES = $(TARGET_FILES)"
+ @echo ""
+ @echo "INETS_SPECS = $(INETS_SPECS)"
+ @echo "INETS_FILES = $(INETS_FILES)"
+ @echo ""
+ @echo "RELEASE_PATH = $(RELEASE_PATH)"
+ @echo "RELSYSDIR = $(RELSYSDIR)"
+ @echo "RELTESTSYSDIR = $(RELTESTSYSDIR)"
+ @echo "RELTESTSYSALLDATADIR = $(RELTESTSYSALLDATADIR)"
+ @echo "RELTESTSYSBINDIR = $(RELTESTSYSBINDIR)"
+ @echo ""
+ @echo "DATADIRS = $(DATADIRS)"
+ @echo "REL_DATADIRS = $(REL_DATADIRS)"
+ @echo ""
+ @echo "INETS_DATA_DIR = $(INETS_DATA_DIR)"
+ @echo "INETS_PRIV_DIR = $(INETS_PRIV_DIR)"
+ @echo "INETS_ROOT = $(INETS_ROOT)"
+ @echo "INETS_FLAGS = $(INETS_FLAGS)"
+ @echo "FTP_FLAGS = $(FTP_FLAGS)"
+
+tftp:
+ erlc $(ERL_COMPILE_FLAGS) tftp_test_lib.erl tftp_SUITE.erl && erl -pa ../../inets/ebin -s tftp_SUITE t -s erlang halt
+
+tftp_work:
+ echo "tftp_test_lib:t([{tftp_SUITE, all}])."
+ erlc $(ERL_COMPILE_FLAGS) tftp_test_lib.erl tftp_SUITE.erl && erl -pa ../../inets/ebin
diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl
new file mode 100644
index 0000000000..e7404f945b
--- /dev/null
+++ b/lib/inets/test/ftp_SUITE.erl
@@ -0,0 +1,143 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_SUITE).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% Test server specific exports
+-export([all/1]).
+% -export([init_per_testcase/2, end_per_testcase/2]).
+-export([init_per_suite/1, end_per_suite/1]).
+
+%% Test cases must be exported.
+-export([solaris8_test/1,
+ solaris9_test/1,
+ solaris10_test/1,
+ linux_x86_test/1,
+ linux_ppc_test/1,
+ macosx_x86_test/1,
+ macosx_ppc_test/1,
+ openbsd_test/1,
+ freebsd_test/1,
+ netbsd_test/1,
+ windows_xp_test/1,
+ windows_2003_server_test/1,
+ ticket_tests/1]).
+
+-define(FTP_USER, "anonymous").
+-define(FTP_PASS, passwd()).
+-define(FTP_PORT, 21).
+
+-define(BAD_HOST, "badhostname").
+-define(BAD_USER, "baduser").
+-define(BAD_DIR, "baddirectory").
+
+-ifdef(ftp_debug_client).
+-define(ftp_open(Host, Flags), do_ftp_open(Host, [debug] ++ Flags)).
+-else.
+-ifdef(ftp_trace_client).
+-define(ftp_open(Host, Flags), do_ftp_open(Host, [trace] ++ Flags)).
+-else.
+-define(ftp_open(Host, Flags), do_ftp_open(Host, [verbose] ++ Flags)).
+-endif.
+-endif.
+
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test the ftp client in the inets application."];
+all(suite) ->
+ [
+ solaris8_test,
+ solaris9_test,
+ solaris10_test,
+ linux_x86_test,
+ linux_ppc_test,
+ macosx_x86_test,
+ macosx_ppc_test,
+ openbsd_test,
+ freebsd_test,
+ netbsd_test,
+ windows_xp_test,
+ windows_2003_server_test,
+ ticket_tests
+ ].
+
+solaris8_test(suite) ->
+ [{ftp_solaris8_sparc_test,all}].
+solaris9_test(suite) ->
+ [{ftp_solaris9_sparc_test,all}].
+solaris10_test(suite) ->
+ [{ftp_solaris10_sparc_test,all}, {ftp_solaris10_x86_test,all}].
+linux_x86_test(suite) ->
+ [{ftp_linux_x86_test,all}].
+linux_ppc_test(suite) ->
+ [{ftp_linux_ppc_test,all}].
+macosx_x86_test(suite) ->
+ [{ftp_macosx_x86_test,all}].
+macosx_ppc_test(suite) ->
+ [{ftp_macosx_ppc_test,all}].
+openbsd_test(suite) ->
+ [{ftp_openbsd_x86_test,all}].
+freebsd_test(suite) ->
+ [{ftp_freebsd_x86_test,all}].
+netbsd_test(suite) ->
+ [{ftp_netbsd_x86_test,all}].
+windows_xp_test(suite) ->
+ [{ftp_windows_xp_test,all}].
+windows_2003_server_test(suite) ->
+ [{ftp_windows_2003_server_test,all}].
+
+ticket_tests(suite) ->
+ [{ftp_ticket_test, all}].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ inets:start(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ inets:stop(),
+ ok.
diff --git a/lib/inets/test/ftp_SUITE_data/TAR.exclude b/lib/inets/test/ftp_SUITE_data/TAR.exclude
new file mode 100644
index 0000000000..2078965740
--- /dev/null
+++ b/lib/inets/test/ftp_SUITE_data/TAR.exclude
@@ -0,0 +1,2 @@
+ftp_SUITE_data/TAR.exclude
+ftp_SUITE_data/ftpd_hosts
diff --git a/lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel b/lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel
new file mode 100644
index 0000000000..a820f8d014
--- /dev/null
+++ b/lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel
@@ -0,0 +1,17 @@
+%% Add a host name in the appropriate list
+%% Each "platform" contains a list of hostnames (a string) that can
+%% be used for testing the ftp client.
+[{solaris8_sparc, []},
+ {solaris9_sparc, []},
+ {solaris10_sparc, []},
+ {solaris10_x86, []},
+ {linux_x86, []},
+ {linux_ppc, []},
+ {macosx_ppc, []},
+ {macosx_x86, []},
+ {openbsd_x86, []},
+ {freebsd_x86, []},
+ {netbsd_x86, []},
+ {windows_xp, []},
+ {windows_2003_server, []},
+ {ticket_test, []}].
diff --git a/lib/inets/test/ftp_format_SUITE.erl b/lib/inets/test/ftp_format_SUITE.erl
new file mode 100644
index 0000000000..9ca6575b2d
--- /dev/null
+++ b/lib/inets/test/ftp_format_SUITE.erl
@@ -0,0 +1,341 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(ftp_format_SUITE).
+-author('[email protected]').
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("ftp_internal.hrl").
+
+%% Test server specific exports
+-export([all/1, init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases must be exported.
+-export([ftp_response/1, ftp_150/1,
+ ftp_200/1, ftp_220/1, ftp_226/1, ftp_257/1, ftp_331/1, ftp_425/1,
+ ftp_other_status_codes/1, ftp_multiple_lines/1,
+ ftp_multipel_ctrl_messages/1, format_error/1]).
+
+all(doc) ->
+ ["Test library functions for the ftp client."];
+all(suite) ->
+ [ftp_response, format_error].
+
+init_per_testcase(_, Config) ->
+ Dog = test_server:timetrap(?t:minutes(1)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ [{watchdog, Dog} | NewConfig].
+
+end_per_testcase(_, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+ftp_response(doc) ->
+ ["Test ftp_response:parse_lines/3 and ftp_response:interpret/1."
+ " This test case will simulate that the "
+ "message will be recived a little at the time on a socket and the "
+ "package may be broken up into smaller parts at arbitrary point."];
+ftp_response(suite) ->
+ [ftp_150, ftp_200, ftp_220, ftp_226, ftp_257, ftp_331, ftp_425,
+ ftp_other_status_codes, ftp_multiple_lines, ftp_multipel_ctrl_messages].
+
+ftp_150(doc) ->
+ ["Especially check that respons can be devided in a random place."];
+ftp_150(suite) ->
+ [];
+ftp_150(Config) when is_list(Config) ->
+ FtpResponse = ["150 ASCII data conn", "ection for /bin/ls ",
+ "(134.138.177", ".89,50434) (0 bytes).\r\n"],
+
+ "150 ASCII data connection for /bin/ls "
+ "(134.138.177.89,50434) (0 bytes).\r\n" = Msg =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_prel, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_200(doc) ->
+ ["Especially check that respons can be devided after the first status "
+ "code character and in the end delimiter."];
+ftp_200(suite) ->
+ [];
+ftp_200(Config) when is_list(Config) ->
+ FtpResponse = ["2", "00 PORT command successful.", [?CR], [?LF]],
+
+ "200 PORT command successful.\r\n" = Msg =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_compl, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_220(doc) ->
+ ["Especially check that respons can be devided after the "
+ "first with space "];
+ftp_220(suite) ->
+ [];
+ftp_220(Config) when is_list(Config) ->
+ FtpResponse = ["220 ","fingon FTP server (SunOS 5.8) ready.\r\n"],
+
+ "220 fingon FTP server (SunOS 5.8) ready.\r\n" = Msg =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_compl, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_226(doc) ->
+ ["Especially check that respons can be devided after second status code"
+ " character and in the end delimiter."];
+ftp_226(suite) ->
+ [];
+ftp_226(Config) when is_list(Config) ->
+ FtpResponse = ["22" "6 Transfer complete.\r", [?LF]],
+
+ "226 Transfer complete.\r\n" = Msg =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_compl, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_257(doc) ->
+ ["Especially check that quoted chars do not cause a problem."];
+ftp_257(suite) ->
+ [];
+ftp_257(Config) when is_list(Config) ->
+ FtpResponse = ["257 \"/\" is current directory.\r\n"],
+
+ "257 \"/\" is current directory.\r\n" = Msg =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_compl, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_331(doc) ->
+ ["Especially check that respons can be devided after the third status "
+ " status code character."];
+ftp_331(suite) ->
+ [];
+ftp_331(Config) when is_list(Config) ->
+ %% Brake before white space after code
+ FtpResponse =
+ ["331"," Guest login ok, send ient as password.\r\n"],
+
+ "331 Guest login ok, send ient as password.\r\n" = Msg =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_interm, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_425(doc) ->
+ ["Especially check a message that was received in only one part."];
+ftp_425(suite) ->
+ [];
+ftp_425(Config) when is_list(Config) ->
+ FtpResponse =
+ ["425 Can't build data connection: Connection refused.\r\n"],
+
+ "425 Can't build data connection: Connection refused.\r\n"
+ = Msg = parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {trans_neg_compl, _} = ftp_response:interpret(Msg),
+ ok.
+
+ftp_multiple_lines(doc) ->
+ ["Especially check multiple lines devided in significant places"];
+ftp_multiple_lines(suite) ->
+ [];
+ftp_multiple_lines(Config) when is_list(Config) ->
+ FtpResponse = ["21", "4","-The",
+ " following commands are recognized:\r\n"
+ " USER EPRT STRU MAIL* ALLO CWD",
+ " STAT* XRMD \r\n"
+ " PASS LPRT MODE MSND* "
+ " REST* XCWD HELP PWD ", [?CRLF],
+ " ACCT* EPSV RETR MSOM* RNFR LIST "
+ " NOOP XPWD \r\n",
+ " REIN* LPSV STOR MSAM* RNTO NLST "
+ " MKD CDUP \r\n"
+ " QUIT PASV APPE MRSQ* ABOR SITE* "
+ " XMKD XCUP \r\n"
+ " PORT TYPE MLFL* MRCP* DELE SYST "
+ " RMD STOU \r\n"
+ "214 (*'s => unimplemented)", [?CR], [?LF]],
+
+
+ FtpResponse1 = ["214-", "The",
+ " following commands are recognized:\r\n"
+ " USER EPRT STRU MAIL* ALLO CWD",
+ " STAT* XRMD \r\n"
+ " PASS LPRT MODE MSND* "
+ " REST* XCWD HELP PWD ", [?CRLF],
+ " ACCT* EPSV RETR MSOM* RNFR LIST "
+ " NOOP XPWD \r\n",
+ " REIN* LPSV STOR MSAM* RNTO NLST "
+ " MKD CDUP \r\n"
+ " QUIT PASV APPE MRSQ* ABOR SITE* "
+ " XMKD XCUP \r\n"
+ " PORT TYPE MLFL* MRCP* DELE SYST "
+ " RMD STOU \r\n"
+ "2", "14 (*'s => unimplemented)", [?CR], [?LF]],
+
+ FtpResponse2 = ["214-", "The",
+ " following commands are recognized:\r\n"
+ " USER EPRT STRU MAIL* ALLO CWD",
+ " STAT* XRMD \r\n"
+ " PASS LPRT MODE MSND* "
+ " REST* XCWD HELP PWD ", [?CRLF],
+ " ACCT* EPSV RETR MSOM* RNFR LIST "
+ " NOOP XPWD \r\n",
+ " REIN* LPSV STOR MSAM* RNTO NLST "
+ " MKD CDUP \r\n"
+ " QUIT PASV APPE MRSQ* ABOR SITE* "
+ " XMKD XCUP \r\n"
+ " PORT TYPE MLFL* MRCP* DELE SYST "
+ " RMD STOU \r\n"
+ "21", "4"," (*'s => unimplemented)", [?CR], [?LF]],
+
+ MultiLineResultStr =
+ "214-The following commands are recognized:\r\n"
+ " USER EPRT STRU MAIL* ALLO CWD STAT* "
+ "XRMD \r\n"
+ " PASS LPRT MODE MSND* REST* XCWD HELP "
+ "PWD \r\n"
+ " ACCT* EPSV RETR MSOM* RNFR LIST NOOP "
+ "XPWD \r\n"
+ " REIN* LPSV STOR MSAM* RNTO NLST MKD "
+ "CDUP \r\n"
+ " QUIT PASV APPE MRSQ* ABOR SITE* XMKD "
+ "XCUP \r\n"
+ " PORT TYPE MLFL* MRCP* DELE SYST RMD "
+ "STOU \r\n"
+ "214 (*'s => unimplemented)\r\n",
+
+ MultiLineResultStr =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_compl, _} = ftp_response:interpret(MultiLineResultStr),
+
+ MultiLineResultStr = parse(ftp_response, parse_lines, [[], start],
+ FtpResponse1),
+
+ MultiLineResultStr = parse(ftp_response, parse_lines, [[], start],
+ FtpResponse2),
+ ok.
+
+ftp_other_status_codes(doc) ->
+ ["Check that other valid status codes, than the ones above, are handled"
+ "by ftp_response:interpret/1. Note there are som ftp status codes"
+ "that will not be received with the current ftp instruction support,"
+ "they are not included here."];
+ftp_other_status_codes(suite) ->
+ [];
+ftp_other_status_codes(Config) when is_list(Config) ->
+
+ %% 1XX
+ {pos_prel, _ } = ftp_response:interpret("120 Foobar\r\n"),
+
+ %% 2XX
+ {pos_compl, _ } = ftp_response:interpret("202 Foobar\r\n"),
+ {pos_compl, _ } = ftp_response:interpret("221 Foobar\r\n"),
+ {pos_compl, _ } = ftp_response:interpret("227 Foobar\r\n"),
+ {pos_compl, _ } = ftp_response:interpret("230 Foobar\r\n"),
+ {pos_compl, _ } = ftp_response:interpret("250 Foobar\r\n"),
+
+ %% 3XX
+ {pos_interm_acct, _ } = ftp_response:interpret("332 Foobar\r\n"),
+ {pos_interm, _ } = ftp_response:interpret("350 Foobar\r\n"),
+
+ %% 4XX
+ {trans_neg_compl, _ } = ftp_response:interpret("421 Foobar\r\n"),
+ {trans_neg_compl, _ } = ftp_response:interpret("426 Foobar\r\n"),
+ {trans_neg_compl, _ } = ftp_response:interpret("450 Foobar\r\n"),
+ {trans_neg_compl, _ } = ftp_response:interpret("451 Foobar\r\n"),
+ {etnospc, _ } = ftp_response:interpret("452 Foobar\r\n"),
+
+ %% 5XX
+ {perm_neg_compl, _ } = ftp_response:interpret("500 Foobar\r\n"),
+ {perm_neg_compl, _ } = ftp_response:interpret("501 Foobar\r\n"),
+ {perm_neg_compl, _ } = ftp_response:interpret("503 Foobar\r\n"),
+ {perm_neg_compl, _ } = ftp_response:interpret("504 Foobar\r\n"),
+ {perm_neg_compl, _ } = ftp_response:interpret("530 Foobar\r\n"),
+ {perm_neg_compl, _ } = ftp_response:interpret("532 Foobar\r\n"),
+ {epath, _ } = ftp_response:interpret("550 Foobar\r\n"),
+ {epnospc, _ } = ftp_response:interpret("552 Foobar\r\n"),
+ {efnamena, _ } = ftp_response:interpret("553 Foobar\r\n"),
+ ok.
+
+ftp_multipel_ctrl_messages(doc) ->
+ ["The ftp server may send more than one control message as a reply,"
+ "check that they are handled one at the time."];
+ftp_multipel_ctrl_messages(suite) ->
+ [];
+ftp_multipel_ctrl_messages(Config) when is_list(Config) ->
+ FtpResponse = ["200 PORT command successful.\r\n200 Foobar\r\n"],
+
+ {"200 PORT command successful.\r\n" = Msg, NextMsg} =
+ parse(ftp_response, parse_lines, [[], start], FtpResponse),
+ {pos_compl, _} = ftp_response:interpret(Msg),
+ NewMsg = parse(ftp_response, parse_lines, [[], start], NextMsg),
+ {pos_compl, _} = ftp_response:interpret(NewMsg),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+format_error(doc) ->
+ [""];
+format_error(suite) ->
+ [];
+format_error(Config) when is_list(Config) ->
+ "Synchronisation error during chunk sending." =
+ ftp:formaterror(echunk),
+ "Session has been closed." = ftp:formaterror(eclosed),
+ "Connection to remote server prematurely closed." =
+ ftp:formaterror(econn),
+ "File or directory already exists." = ftp:formaterror(eexists),
+ "Host not found, FTP server not found, or connection rejected." =
+ ftp:formaterror(ehost),
+ "User not logged in." = ftp:formaterror(elogin),
+ "Term is not a binary." = ftp:formaterror(enotbinary),
+ "No such file or directory, already exists, or permission denied."
+ = ftp:formaterror(epath),
+ "No such type." = ftp:formaterror(etype),
+ "User name or password not valid." = ftp:formaterror(euser),
+ "Insufficient storage space in system." = ftp:formaterror(etnospc),
+ "Exceeded storage allocation (for current directory or dataset)."
+ = ftp:formaterror(epnospc),
+ "File name not allowed." = ftp:formaterror(efnamena),
+ "Unknown error: foobar" = ftp:formaterror({error, foobar}).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+parse(Module, Function, Args, Bin) when is_binary(Bin) ->
+ parse(Module, Function, Args, [binary_to_list(Bin)]);
+
+parse(Module, Function, [AccLines, StatusCode], [Data | Rest]) ->
+ case Module:Function(list_to_binary(Data), AccLines, StatusCode) of
+ {ok, Result, <<>>} ->
+ Result;
+ {ok, Result, Next} ->
+ {Result, Next};
+ {continue, {NewData, NewAccLines, NewStatusCode}} ->
+ case Rest of
+ [] ->
+ test_server:fail({wrong_input, Data, Rest});
+ [_ | _] ->
+ parse(Module, Function, [NewAccLines, NewStatusCode],
+ [binary_to_list(NewData) ++ hd(Rest) | tl(Rest)])
+ end
+ end.
diff --git a/lib/inets/test/ftp_freebsd_x86_test.erl b/lib/inets/test/ftp_freebsd_x86_test.erl
new file mode 100644
index 0000000000..457e18ffbe
--- /dev/null
+++ b/lib/inets/test/ftp_freebsd_x86_test.erl
@@ -0,0 +1,153 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_freebsd_x86_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Freebsd x86 ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(freebsd_x86, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_internal.hrl b/lib/inets/test/ftp_internal.hrl
new file mode 120000
index 0000000000..af57081f14
--- /dev/null
+++ b/lib/inets/test/ftp_internal.hrl
@@ -0,0 +1 @@
+../src/ftp/ftp_internal.hrl \ No newline at end of file
diff --git a/lib/inets/test/ftp_linux_ppc_test.erl b/lib/inets/test/ftp_linux_ppc_test.erl
new file mode 100644
index 0000000000..ad38137678
--- /dev/null
+++ b/lib/inets/test/ftp_linux_ppc_test.erl
@@ -0,0 +1,151 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_linux_ppc_test).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Linux ppc ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(linux_ppc, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
diff --git a/lib/inets/test/ftp_linux_x86_test.erl b/lib/inets/test/ftp_linux_x86_test.erl
new file mode 100644
index 0000000000..b9c88d121a
--- /dev/null
+++ b/lib/inets/test/ftp_linux_x86_test.erl
@@ -0,0 +1,160 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_linux_x86_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Linux x86 ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(linux_x86, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [
+ open,
+ open_port,
+ passive,
+ active,
+ api_missuse,
+ not_owner,
+ progress_report
+ ].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_macosx_ppc_test.erl b/lib/inets/test/ftp_macosx_ppc_test.erl
new file mode 100644
index 0000000000..cf548a73c0
--- /dev/null
+++ b/lib/inets/test/ftp_macosx_ppc_test.erl
@@ -0,0 +1,152 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_macosx_ppc_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Macosx ppc ").
+
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(macosx_ppc, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(_X) -> {skipped,"unknown error"}.%?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(_X) -> {skipped,"unknown error"}.%%?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_macosx_x86_test.erl b/lib/inets/test/ftp_macosx_x86_test.erl
new file mode 100644
index 0000000000..c59a992421
--- /dev/null
+++ b/lib/inets/test/ftp_macosx_x86_test.erl
@@ -0,0 +1,152 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_macosx_x86_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Macosx x86 ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(macosx_x86, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_netbsd_x86_test.erl b/lib/inets/test/ftp_netbsd_x86_test.erl
new file mode 100644
index 0000000000..a5711b7bde
--- /dev/null
+++ b/lib/inets/test/ftp_netbsd_x86_test.erl
@@ -0,0 +1,152 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_netbsd_x86_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Netbsd x86 ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(netbsd_x86, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_openbsd_x86_test.erl b/lib/inets/test/ftp_openbsd_x86_test.erl
new file mode 100644
index 0000000000..4833b6332b
--- /dev/null
+++ b/lib/inets/test/ftp_openbsd_x86_test.erl
@@ -0,0 +1,151 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_openbsd_x86_test).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Openbsd x86 ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(openbsd_x86, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
diff --git a/lib/inets/test/ftp_solaris10_sparc_test.erl b/lib/inets/test/ftp_solaris10_sparc_test.erl
new file mode 100644
index 0000000000..6066195f9b
--- /dev/null
+++ b/lib/inets/test/ftp_solaris10_sparc_test.erl
@@ -0,0 +1,154 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_solaris10_sparc_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Solaris 10 sparc ").
+
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(solaris10_sparc, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_solaris10_x86_test.erl b/lib/inets/test/ftp_solaris10_x86_test.erl
new file mode 100644
index 0000000000..3bd99fc3f2
--- /dev/null
+++ b/lib/inets/test/ftp_solaris10_x86_test.erl
@@ -0,0 +1,155 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_solaris10_x86_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD, ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_), ?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM, "Solaris 10 x86 ").
+
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(solaris10_x86, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_solaris8_sparc_test.erl b/lib/inets/test/ftp_solaris8_sparc_test.erl
new file mode 100644
index 0000000000..9764071cd9
--- /dev/null
+++ b/lib/inets/test/ftp_solaris8_sparc_test.erl
@@ -0,0 +1,152 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_solaris8_sparc_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Solaris 8 sparc ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(solaris8_sparc, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_solaris9_sparc_test.erl b/lib/inets/test/ftp_solaris9_sparc_test.erl
new file mode 100644
index 0000000000..a9f77bbdac
--- /dev/null
+++ b/lib/inets/test/ftp_solaris9_sparc_test.erl
@@ -0,0 +1,151 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_solaris9_sparc_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Solaris 9 sparc ").
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(solaris9_sparc, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_suite_lib.erl b/lib/inets/test/ftp_suite_lib.erl
new file mode 100644
index 0000000000..75e1a5a7f9
--- /dev/null
+++ b/lib/inets/test/ftp_suite_lib.erl
@@ -0,0 +1,1593 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_suite_lib).
+
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("inets_test_lib.hrl").
+
+%% Test server specific exports
+% -export([init_per_testcase/2, end_per_testcase/2]).
+
+-compile(export_all).
+
+
+-record(progress, {
+ current = 0,
+ total
+ }).
+
+
+
+-define(FTP_USER, "anonymous").
+-define(FTP_PASS, passwd()).
+-define(FTP_PORT, 21).
+
+-define(BAD_HOST, "badhostname").
+-define(BAD_USER, "baduser").
+-define(BAD_DIR, "baddirectory").
+
+-ifdef(ftp_debug_client).
+-define(ftp_open(Host, Flags),
+ do_ftp_open(Host, [debug, {timeout, timer:seconds(15)}] ++ Flags)).
+-else.
+-ifdef(ftp_trace_client).
+-define(ftp_open(Host, Flags),
+ do_ftp_open(Host, [trace, {timeout, timer:seconds(15)}] ++ Flags)).
+-else.
+-define(ftp_open(Host, Flags),
+ do_ftp_open(Host, [verbose, {timeout, timer:seconds(15)}] ++ Flags)).
+-endif.
+-endif.
+
+%% -- Tickets --
+
+tickets(doc) ->
+ "Test cases for reported bugs";
+tickets(suite) ->
+ [ticket_6035].
+
+%% --
+
+ftpd_init(FtpdTag, Config) ->
+ %% Get the host name(s) of FTP server
+ Hosts =
+ case ?config(ftpd_hosts, Config) of
+ undefined ->
+ ftpd_hosts(data_dir(Config));
+ H ->
+ H
+ end,
+ p("ftpd_init -> "
+ "~n Hosts: ~p"
+ "~n Config: ~p"
+ "~n FtpdTag: ~p", [Hosts, Config, FtpdTag]),
+ %% Get the first host that actually have a running FTP server
+ case lists:keysearch(FtpdTag, 1, Hosts) of
+ {value, {_, TagHosts}} when is_list(TagHosts) ->
+ inets:start(),
+ case (catch get_ftpd_host(TagHosts)) of
+ {ok, Host} ->
+ inets:stop(),
+ [{ftp_remote_host, Host}|Config];
+ _ ->
+ inets:stop(),
+ Reason = lists:flatten(
+ io_lib:format("Could not find a valid "
+ "FTP server for ~p (~p)",
+ [FtpdTag, TagHosts])),
+ {skip, Reason}
+ end;
+ _ ->
+ Reason = lists:flatten(
+ io_lib:format("No host(s) running FTPD server "
+ "for ~p", [FtpdTag])),
+ {skip, Reason}
+ end.
+
+ftpd_fin(Config) ->
+ lists:keydelete(ftp_remote_host, 1, Config).
+
+get_ftpd_host([]) ->
+ {error, no_host};
+get_ftpd_host([Host|Hosts]) ->
+ p("get_ftpd_host -> entry with"
+ "~n Host: ~p"
+ "~n", [Host]),
+ case (catch ftp:open({option_list,
+ [{host, Host}, {port, ?FTP_PORT},
+ {timeout, 20000}]})) of
+ {ok, Pid} ->
+ (catch ftp:close(Pid)),
+ {ok, Host};
+ _ ->
+ get_ftpd_host(Hosts)
+ end.
+
+
+%%--------------------------------------------------------------------
+
+dirty_select_ftpd_host(Config) ->
+ Hosts =
+ case ?config(ftpd_hosts, Config) of
+ undefined ->
+ ftpd_hosts(data_dir(Config));
+ H ->
+ H
+ end,
+ dirty_select_ftpd_host2(Hosts).
+
+dirty_select_ftpd_host2([]) ->
+ throw({error, not_found});
+dirty_select_ftpd_host2([{PlatformTag, Hosts} | PlatformHosts]) ->
+ case dirty_select_ftpd_host3(Hosts) of
+ none ->
+ dirty_select_ftpd_host2(PlatformHosts);
+ {ok, Host} ->
+ {PlatformTag, Host}
+ end.
+
+dirty_select_ftpd_host3([]) ->
+ none;
+dirty_select_ftpd_host3([Host|Hosts]) when is_list(Host) ->
+ case dirty_select_ftpd_host4(Host) of
+ true ->
+ {ok, Host};
+ false ->
+ dirty_select_ftpd_host3(Hosts)
+ end;
+dirty_select_ftpd_host3([_|Hosts]) ->
+ dirty_select_ftpd_host3(Hosts).
+
+%% This is a very simple and dirty test that there is a
+%% (FTP) deamon on the other end.
+dirty_select_ftpd_host4(Host) ->
+ Port = 21,
+ IpFam = inet,
+ Opts = [IpFam, binary, {packet, 0}, {active, false}],
+ Timeout = ?SECS(5),
+ case gen_tcp:connect(Host, Port, Opts, Timeout) of
+ {ok, Sock} ->
+ gen_tcp:close(Sock),
+ true;
+ _Error ->
+ false
+ end.
+
+
+%%--------------------------------------------------------------------
+
+test_filenames() ->
+ {ok, Host} = inet:gethostname(),
+ File = Host ++ "_ftp_test.txt",
+ NewFile = "new_" ++ File,
+ {File, NewFile}.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config)
+ when (Case =:= open) orelse (Case =:= open_port) ->
+ io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]),
+ inets:start(),
+ NewConfig = data_dir(Config),
+ watch_dog(NewConfig);
+
+init_per_testcase(Case, Config) ->
+ put(ftp_testcase, Case),
+ inets:enable_trace(max, io, ftpc),
+ do_init_per_testcase(Case, Config).
+
+do_init_per_testcase(Case, Config)
+ when (Case =:= passive_user) ->
+ io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE,Case]),
+ inets:start(),
+ NewConfig = close_connection(watch_dog(Config)),
+ Host = ftp_host(Config),
+ case (catch ?ftp_open(Host, [])) of
+ {ok, Pid} ->
+ [{ftp, Pid} | data_dir(NewConfig)];
+ {skip, _} = SKIP ->
+ SKIP
+ end;
+
+do_init_per_testcase(Case, Config)
+ when (Case =:= active_user) ->
+ io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]),
+ inets:start(),
+ NewConfig = close_connection(watch_dog(Config)),
+ Host = ftp_host(Config),
+ case (catch ?ftp_open(Host, [])) of
+ {ok, Pid} ->
+ ok = ftp:force_active(Pid),
+ [{ftp, Pid} | data_dir(NewConfig)];
+ {skip, _} = SKIP ->
+ SKIP
+ end;
+
+do_init_per_testcase(Case, Config)
+ when (Case =:= progress_report_send) orelse
+ (Case =:= progress_report_recv) ->
+ inets:start(),
+ io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]),
+ NewConfig = close_connection(watch_dog(Config)),
+ Host = ftp_host(Config),
+ Opts = [{host, Host},
+ {port, ?FTP_PORT},
+ {flags, [verbose]},
+ {progress, {?MODULE, progress, #progress{}}}],
+ case ftp:open({option_list, Opts}) of
+ {ok, Pid} ->
+ ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS),
+ [{ftp, Pid} | data_dir(NewConfig)];
+ {skip, _} = SKIP ->
+ SKIP
+ end;
+
+do_init_per_testcase(Case, Config) ->
+ io:format(user,"~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]),
+ inets:start(),
+ NewConfig = close_connection(watch_dog(Config)),
+ Host = ftp_host(Config),
+ Flags =
+ if
+ ((Case =:= passive_ip_v6_disabled) orelse
+ (Case =:= active_ip_v6_disabled)) ->
+ [ip_v6_disabled];
+ true ->
+ []
+ end,
+ case (catch ?ftp_open(Host, Flags)) of
+ {ok, Pid} ->
+ case string:tokens(atom_to_list(Case), [$_]) of
+ [_, "active"|_] ->
+ ok = ftp:force_active(Pid);
+ _ ->
+ ok
+ end,
+ ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS),
+ [{ftp, Pid} | data_dir(NewConfig)];
+ {skip, _} = SKIP ->
+ SKIP
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_, Config) ->
+ NewConfig = close_connection(Config),
+ Dog = ?config(watchdog, NewConfig),
+ inets:stop(),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+%% Suites similar for all hosts.
+%%-------------------------------------------------------------------------
+
+passive(suite) ->
+ [
+ passive_user,
+ passive_pwd,
+ passive_cd,
+ passive_lcd,
+ passive_ls,
+ passive_nlist,
+ passive_rename,
+ passive_delete,
+ passive_mkdir,
+ passive_send,
+ passive_send_bin,
+ passive_send_chunk,
+ passive_append,
+ passive_append_bin,
+ passive_append_chunk,
+ passive_recv,
+ passive_recv_bin,
+ passive_recv_chunk,
+ passive_type,
+ passive_quote,
+ passive_ip_v6_disabled
+ ].
+
+active(suite) ->
+ [
+ active_user,
+ active_pwd,
+ active_cd,
+ active_lcd,
+ active_ls,
+ active_nlist,
+ active_rename,
+ active_delete,
+ active_mkdir,
+ active_send,
+ active_send_bin,
+ active_send_chunk,
+ active_append,
+ active_append_bin,
+ active_append_chunk,
+ active_recv,
+ active_recv_bin,
+ active_recv_chunk,
+ active_type,
+ active_quote,
+ active_ip_v6_disabled
+ ].
+
+
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+
+open(doc) ->
+ ["Open an ftp connection to a host and close the connection."
+ "Also check that !-messages does not disturbe the connection"];
+open(suite) ->
+ [];
+open(Config) when is_list(Config) ->
+ Host = ftp_host(Config),
+ (catch tc_open(Host)).
+
+tc_open(Host) ->
+ {ok, Pid} = ?ftp_open(Host, []),
+ ok = ftp:close(Pid),
+ {ok, Pid1} =
+ ftp:open({option_list, [{host,Host},
+ {port, ?FTP_PORT},
+ {flags, [verbose]},
+ {timeout, 30000}]}),
+ ok = ftp:close(Pid1),
+ {error, ehost} = ftp:open({option_list, [{port, ?FTP_PORT},
+ {flags, [verbose]}]}),
+ {ok, Pid2} = ftp:open(Host),
+ ok = ftp:close(Pid2),
+
+ {ok, NewHost} = inet:getaddr(Host, inet),
+ {ok, Pid3} = ftp:open(NewHost),
+ ftp:user(Pid3, ?FTP_USER, ?FTP_PASS),
+ Pid3 ! foobar,
+ test_server:sleep(5000),
+ {message_queue_len, 0} = process_info(self(), message_queue_len),
+ ["200" ++ _] = ftp:quote(Pid3, "NOOP"),
+ ok = ftp:close(Pid3),
+
+ %% Bad input that has default values are ignored and the defult
+ %% is used.
+ {ok, Pid4} =
+ ftp:open({option_list, [{host, Host}, {port, badarg},
+ {flags, [verbose]},
+ {timeout, 30000}]}),
+ test_server:sleep(100),
+ ok = ftp:close(Pid4),
+ {ok, Pid5} =
+ ftp:open({option_list, [{host, Host}, {port, ?FTP_PORT},
+ {flags, [verbose]},
+ {timeout, -42}]}),
+ test_server:sleep(100),
+ ok = ftp:close(Pid5),
+ {ok, Pid6} =
+ ftp:open({option_list, [{host, Host}, {port, ?FTP_PORT},
+ {flags, [verbose]},
+ {mode, cool}]}),
+ test_server:sleep(100),
+ ok = ftp:close(Pid6),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+open_port(doc) ->
+ ["Open an ftp connection to a host with given port number "
+ "and close the connection."]; % See also OTP-3892
+open_port(suite) ->
+ [];
+open_port(Config) when is_list(Config) ->
+ Host = ftp_host(Config),
+ {ok, Pid} = ftp:open(Host, ?FTP_PORT),
+ ok = ftp:close(Pid),
+ {error, ehost} = ftp:open(?BAD_HOST, []),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+passive_user(doc) ->
+ ["Open an ftp connection to a host, and logon as anonymous ftp."];
+passive_user(suite) ->
+ [];
+passive_user(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ io:format("Pid: ~p~n",[Pid]),
+ do_user(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_pwd(doc) ->
+ ["Test ftp:pwd/1 & ftp:lpwd/1"];
+passive_pwd(suite) ->
+ [];
+passive_pwd(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_pwd(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_cd(doc) ->
+ ["Open an ftp connection, log on as anonymous ftp, and cd to the"
+ "directory \"/pub\" and the to the non-existent directory."];
+passive_cd(suite) ->
+ [];
+passive_cd(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_cd(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_lcd(doc) ->
+ ["Test api function ftp:lcd/2"];
+passive_lcd(suite) ->
+ [];
+passive_lcd(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ do_lcd(Pid, PrivDir).
+
+
+%%-------------------------------------------------------------------------
+
+passive_ls(doc) ->
+ ["Open an ftp connection; ls the current directory, and the "
+ "\"incoming\" directory. We assume that ls never fails, since "
+ "it's output is meant to be read by humans. "];
+passive_ls(suite) ->
+ [];
+passive_ls(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_ls(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_nlist(doc) ->
+ ["Open an ftp connection; nlist the current directory, and the "
+ "\"incoming\" directory. Nlist does not behave consistenly over "
+ "operating systems. On some it is an error to have an empty "
+ "directory."];
+passive_nlist(suite) ->
+ [];
+passive_nlist(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_nlist(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_rename(doc) ->
+ ["Transfer a file to the server, and rename it; then remove it."];
+passive_rename(suite) ->
+ [];
+passive_rename(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_rename(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_delete(doc) ->
+ ["Transfer a file to the server, and then delete it"];
+passive_delete(suite) ->
+ [];
+passive_delete(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_delete(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_mkdir(doc) ->
+ ["Make a remote directory, cd to it, go to parent directory, and "
+ "remove the directory."];
+passive_mkdir(suite) ->
+ [];
+passive_mkdir(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_mkdir(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_send(doc) ->
+ ["Create a local file in priv_dir; open an ftp connection to a host; "
+ "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to "
+ "priv_dir; send the file; get a directory listing and check that "
+ "the file is on the list;, delete the remote file; get another listing "
+ "and check that the file is not on the list; close the session; "
+ "delete the local file."];
+passive_send(suite) ->
+ [];
+passive_send(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_append(doc) ->
+ ["Create a local file in priv_dir; open an ftp connection to a host; "
+ "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to "
+ "priv_dir; append the file to a file at the remote side that not exits"
+ "this will create the file at the remote side. Then it append the file "
+ "again. When this is done it recive the remote file and control that"
+ "the content is doubled in it.After that it will remove the files"];
+passive_append(suite) ->
+ [];
+passive_append(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_append(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_send_bin(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "send a binary; remove file; close the connection."];
+passive_send_bin(suite) ->
+ [];
+passive_send_bin(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send_bin(Pid, Config).
+
+%%-------------------------------------------------------------------------
+
+passive_append_bin(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "append a binary twice; get the file and compare the content"
+ "remove file; close the connection."];
+passive_append_bin(suite) ->
+ [];
+passive_append_bin(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_append_bin(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_send_chunk(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "send chunks; remove file; close the connection."];
+passive_send_chunk(suite) ->
+ [];
+passive_send_chunk(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send_chunk(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_append_chunk(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "append chunks;control content remove file; close the connection."];
+passive_append_chunk(suite) ->
+ [];
+passive_append_chunk(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_append_chunk(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_recv(doc) ->
+ ["Create a local file and transfer it to the remote host into the "
+ "the \"incoming\" directory, remove "
+ "the local file. Then open a new connection; cd to \"incoming\", "
+ "lcd to the private directory; receive the file; delete the "
+ "remote file; close connection; check that received file is in "
+ "the correct directory; cleanup." ];
+passive_recv(suite) ->
+ [];
+passive_recv(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_recv(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_recv_bin(doc) ->
+ ["Send a binary to the remote host; and retreive "
+ "the file; then remove the file."];
+passive_recv_bin(suite) ->
+ [];
+passive_recv_bin(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_recv_bin(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_recv_chunk(doc) ->
+ ["Send a binary to the remote host; Connect again, and retreive "
+ "the file; then remove the file."];
+passive_recv_chunk(suite) ->
+ [];
+passive_recv_chunk(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_recv_chunk(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+passive_type(doc) ->
+ ["Test that we can change btween ASCCI and binary transfer mode"];
+passive_type(suite) ->
+ [];
+passive_type(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_type(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_quote(doc) ->
+ [""];
+passive_quote(suite) ->
+ [];
+passive_quote(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_quote(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+passive_ip_v6_disabled(doc) ->
+ ["Test ipv4 command PASV"];
+passive_ip_v6_disabled(suite) ->
+ [];
+passive_ip_v6_disabled(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_user(doc) ->
+ ["Open an ftp connection to a host, and logon as anonymous ftp."];
+active_user(suite) ->
+ [];
+active_user(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_user(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_pwd(doc) ->
+ ["Test ftp:pwd/1 & ftp:lpwd/1"];
+active_pwd(suite) ->
+ [];
+active_pwd(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_pwd(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_cd(doc) ->
+ ["Open an ftp connection, log on as anonymous ftp, and cd to the"
+ "directory \"/pub\" and to a non-existent directory."];
+active_cd(suite) ->
+ [];
+active_cd(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_cd(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_lcd(doc) ->
+ ["Test api function ftp:lcd/2"];
+active_lcd(suite) ->
+ [];
+active_lcd(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ do_lcd(Pid, PrivDir).
+
+
+%%-------------------------------------------------------------------------
+
+active_ls(doc) ->
+ ["Open an ftp connection; ls the current directory, and the "
+ "\"incoming\" directory. We assume that ls never fails, since "
+ "it's output is meant to be read by humans. "];
+active_ls(suite) ->
+ [];
+active_ls(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_ls(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_nlist(doc) ->
+ ["Open an ftp connection; nlist the current directory, and the "
+ "\"incoming\" directory. Nlist does not behave consistenly over "
+ "operating systems. On some it is an error to have an empty "
+ "directory."];
+active_nlist(suite) ->
+ [];
+active_nlist(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_nlist(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_rename(doc) ->
+ ["Transfer a file to the server, and rename it; then remove it."];
+active_rename(suite) ->
+ [];
+active_rename(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_rename(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_delete(doc) ->
+ ["Transfer a file to the server, and then delete it"];
+active_delete(suite) ->
+ [];
+active_delete(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_delete(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_mkdir(doc) ->
+ ["Make a remote directory, cd to it, go to parent directory, and "
+ "remove the directory."];
+active_mkdir(suite) ->
+ [];
+active_mkdir(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_mkdir(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_send(doc) ->
+ ["Create a local file in priv_dir; open an ftp connection to a host; "
+ "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to "
+ "priv_dir; send the file; get a directory listing and check that "
+ "the file is on the list;, delete the remote file; get another listing "
+ "and check that the file is not on the list; close the session; "
+ "delete the local file."];
+active_send(suite) ->
+ [];
+active_send(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_append(doc) ->
+ ["Create a local file in priv_dir; open an ftp connection to a host; "
+ "logon as anonymous ftp; cd to the directory \"incoming\"; lcd to "
+ "priv_dir; append the file to a file at the remote side that not exits"
+ "this will create the file at the remote side. Then it append the file "
+ "again. When this is done it recive the remote file and control that"
+ "the content is doubled in it.After that it will remove the files"];
+active_append(suite) ->
+ [];
+active_append(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_append(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_send_bin(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "send a binary; remove file; close the connection."];
+active_send_bin(suite) ->
+ [];
+active_send_bin(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send_bin(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_append_bin(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "append a binary twice; get the file and compare the content"
+ "remove file; close the connection."];
+active_append_bin(suite) ->
+ [];
+active_append_bin(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_append_bin(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_send_chunk(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "send chunks; remove file; close the connection."];
+active_send_chunk(suite) ->
+ [];
+active_send_chunk(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send_chunk(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_append_chunk(doc) ->
+ ["Open a connection to a host; cd to the directory \"incoming\"; "
+ "append chunks;control content remove file; close the connection."];
+active_append_chunk(suite) ->
+ [];
+active_append_chunk(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_append_chunk(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_recv(doc) ->
+ ["Create a local file and transfer it to the remote host into the "
+ "the \"incoming\" directory, remove "
+ "the local file. Then open a new connection; cd to \"incoming\", "
+ "lcd to the private directory; receive the file; delete the "
+ "remote file; close connection; check that received file is in "
+ "the correct directory; cleanup." ];
+active_recv(suite) ->
+ [];
+active_recv(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_recv(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_recv_bin(doc) ->
+ ["Send a binary to the remote host; and retreive "
+ "the file; then remove the file."];
+active_recv_bin(suite) ->
+ [];
+active_recv_bin(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_recv_bin(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_recv_chunk(doc) ->
+ ["Send a binary to the remote host; Connect again, and retreive "
+ "the file; then remove the file."];
+active_recv_chunk(suite) ->
+ [];
+active_recv_chunk(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_recv_chunk(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+active_type(doc) ->
+ ["Test that we can change btween ASCCI and binary transfer mode"];
+active_type(suite) ->
+ [];
+active_type(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_type(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_quote(doc) ->
+ [""];
+active_quote(suite) ->
+ [];
+active_quote(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_quote(Pid).
+
+
+%%-------------------------------------------------------------------------
+
+active_ip_v6_disabled(doc) ->
+ ["Test ipv4 command PORT"];
+active_ip_v6_disabled(suite) ->
+ [];
+active_ip_v6_disabled(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ do_send(Pid, Config).
+
+
+%%-------------------------------------------------------------------------
+
+api_missuse(doc)->
+ ["Test that behaviour of the ftp process if the api is abused"];
+api_missuse(suite) -> [];
+api_missuse(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ Host = ftp_host(Config),
+
+ %% Serious programming fault, connetion will be shut down
+ {error, {connection_terminated, 'API_violation'}} =
+ gen_server:call(Pid, {self(), foobar, 10}, infinity),
+ test_server:sleep(500),
+ undefined = process_info(Pid, status),
+
+ {ok, Pid2} = ?ftp_open(Host, []),
+ %% Serious programming fault, connetion will be shut down
+ gen_server:cast(Pid2, {self(), foobar, 10}),
+ test_server:sleep(500),
+ undefined = process_info(Pid2, status),
+
+ {ok, Pid3} = ?ftp_open(Host, []),
+ %% Could be an innocent misstake the connection lives.
+ Pid3 ! foobar,
+ test_server:sleep(500),
+ {status, _} = process_info(Pid3, status),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+not_owner(doc) ->
+ ["Test what happens if a process that not owns the connection tries "
+ "to use it"];
+not_owner(suite) ->
+ [];
+not_owner(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ OtherPid = spawn_link(?MODULE, not_owner, [Pid, self()]),
+
+ receive
+ {OtherPid, ok} ->
+ {ok, _} = ftp:pwd(Pid)
+ end,
+ ok.
+
+not_owner(FtpPid, Pid) ->
+ {error, not_connection_owner} = ftp:pwd(FtpPid),
+ ftp:close(FtpPid),
+ test_server:sleep(100),
+ Pid ! {self(), ok}.
+
+
+%%-------------------------------------------------------------------------
+
+
+progress_report(doc) ->
+ ["Solaris 8 sparc test the option progress."];
+progress_report(suite) ->
+ [progress_report_send, progress_report_recv].
+
+
+%% --
+
+progress_report_send(doc) ->
+ ["Test the option progress for ftp:send/[2,3]"];
+progress_report_send(suite) ->
+ [];
+progress_report_send(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ ReportPid =
+ spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]),
+ do_send(Pid, Config),
+ receive
+ {ReportPid, ok} ->
+ ok
+ end.
+
+
+%% --
+
+progress_report_recv(doc) ->
+ ["Test the option progress for ftp:recv/[2,3]"];
+progress_report_recv(suite) ->
+ [];
+progress_report_recv(Config) when is_list(Config) ->
+ Pid = ?config(ftp, Config),
+ ReportPid =
+ spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]),
+ do_recv(Pid, Config),
+ receive
+ {ReportPid, ok} ->
+ ok
+ end,
+ ok.
+
+progress(#progress{} = Progress , _File, {file_size, Total}) ->
+ progress_report_receiver ! start,
+ Progress#progress{total = Total};
+progress(#progress{total = Total, current = Current}
+ = Progress, _File, {transfer_size, 0}) ->
+ progress_report_receiver ! finish,
+ case Total of
+ unknown ->
+ ok;
+ Current ->
+ ok;
+ _ ->
+ test_server:fail({error, {progress, {total, Total},
+ {current, Current}}})
+ end,
+ Progress;
+progress(#progress{current = Current} = Progress, _File,
+ {transfer_size, Size}) ->
+ progress_report_receiver ! update,
+ Progress#progress{current = Current + Size}.
+
+progress_report_receiver_init(Pid, N) ->
+ register(progress_report_receiver, self()),
+ receive
+ start ->
+ ok
+ end,
+ progress_report_receiver_loop(Pid, N-1).
+
+progress_report_receiver_loop(Pid, N) ->
+ receive
+ update ->
+ progress_report_receiver_loop(Pid, N);
+ finish when N =:= 0 ->
+ Pid ! {self(), ok};
+ finish ->
+ Pid ! {self(), ok},
+ receive
+ start ->
+ ok
+ end,
+ progress_report_receiver_loop(Pid, N-1)
+ end.
+
+
+%%-------------------------------------------------------------------------
+%% Ticket test cases
+%%-------------------------------------------------------------------------
+
+ticket_6035(doc) -> ["Test that owning process that exits with reason "
+ "'shutdown' does not cause an error message."];
+ticket_6035(suite) -> [];
+ticket_6035(Config) ->
+ p("ticket_6035 -> entry with"
+ "~n Config: ~p", [Config]),
+ PrivDir = ?config(priv_dir, Config),
+ LogFile = filename:join([PrivDir,"ticket_6035.log"]),
+ try
+ begin
+ Host = dirty_select_ftpd_host(Config),
+ Pid = spawn(?MODULE, open_wait_6035, [Host, self()]),
+ error_logger:logfile({open, LogFile}),
+ ok = kill_ftp_proc_6035(Pid,LogFile),
+ error_logger:logfile(close),
+ p("ticket_6035 -> done", []),
+ ok
+ end
+ catch
+ throw:{error, not_found} ->
+ {skip, "No available FTP servers"}
+ end.
+
+kill_ftp_proc_6035(Pid, LogFile) ->
+ p("kill_ftp_proc_6035 -> entry"),
+ receive
+ open ->
+ p("kill_ftp_proc_6035 -> received open: send shutdown"),
+ exit(Pid, shutdown),
+ kill_ftp_proc_6035(Pid, LogFile);
+ {open_failed, Reason} ->
+ p("kill_ftp_proc_6035 -> received open_failed"
+ "~n Reason: ~p", [Reason]),
+ exit({skip, {failed_openening_server_connection, Reason}})
+ after
+ 5000 ->
+ p("kill_ftp_proc_6035 -> timeout"),
+ is_error_report_6035(LogFile)
+ end.
+
+open_wait_6035(FtpServer, From) ->
+ p("open_wait_6035 -> try connect to ~s", [FtpServer]),
+ case ftp:open(FtpServer, [{timeout, timer:seconds(15)}]) of
+ {ok, Pid} ->
+ p("open_wait_6035 -> connected, now login"),
+ LoginResult = ftp:user(Pid,"anonymous","kldjf"),
+ p("open_wait_6035 -> login result: ~p", [LoginResult]),
+ From ! open,
+ receive
+ dummy ->
+ p("open_wait_6035 -> received dummy"),
+ ok
+ after
+ 10000 ->
+ p("open_wait_6035 -> timeout"),
+ ok
+ end,
+ p("open_wait_6035 -> done(ok)"),
+ ok;
+ {error, Reason} ->
+ p("open_wait_6035 -> open failed"
+ "~n Reason: ~p", [Reason]),
+ From ! {open_failed, {Reason, FtpServer}},
+ p("open_wait_6035 -> done(error)"),
+ ok
+ end.
+
+is_error_report_6035(LogFile) ->
+ p("is_error_report_6035 -> entry"),
+ Res =
+ case file:read_file(LogFile) of
+ {ok, Bin} ->
+ p("is_error_report_6035 -> logfile read"),
+ read_log_6035(binary_to_list(Bin));
+ _ ->
+ ok
+ end,
+ p("is_error_report_6035 -> logfile read result: "
+ "~n ~p", [Res]),
+ file:delete(LogFile),
+ Res.
+
+read_log_6035("=ERROR REPORT===="++_Rest) ->
+ error_report;
+read_log_6035([_H|T]) ->
+ read_log_6035(T);
+read_log_6035([]) ->
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+do_user(Pid) ->
+ {error, euser} = ftp:user(Pid, ?BAD_USER, ?FTP_PASS),
+ ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS),
+ ok.
+
+do_pwd(Pid) ->
+ {ok, "/"} = ftp:pwd(Pid),
+ {ok, Path} = ftp:lpwd(Pid),
+ {ok, Path} = file:get_cwd(),
+ ok.
+
+do_cd(Pid) ->
+ ok = ftp:cd(Pid, "/pub"),
+ {error, epath} = ftp:cd(Pid, ?BAD_DIR),
+ ok.
+
+do_lcd(Pid, Dir) ->
+ ok = ftp:lcd(Pid, Dir),
+ {error, epath} = ftp:lcd(Pid, ?BAD_DIR),
+ ok.
+
+
+do_ls(Pid) ->
+ {ok, _} = ftp:ls(Pid),
+ {ok, _} = ftp:ls(Pid, "incoming"),
+ %% neither nlist nor ls operates on a directory
+ %% they operate on a pathname, which *can* be a
+ %% directory, but can also be a filename or a group
+ %% of files (including wildcards).
+ {ok, _} = ftp:ls(Pid, "incom*"),
+ ok.
+
+do_nlist(Pid) ->
+ {ok, _} = ftp:nlist(Pid),
+ {ok, _} = ftp:nlist(Pid, "incoming"),
+ %% neither nlist nor ls operates on a directory
+ %% they operate on a pathname, which *can* be a
+ %% directory, but can also be a filename or a group
+ %% of files (including wildcards).
+ {ok, _} = ftp:nlist(Pid, "incom*"),
+%% {error, epath} = ftp:nlist(Pid, ?BAD_DIR),
+ ok.
+
+do_rename(Pid, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ LFile = ?config(file, Config),
+ NewLFile = ?config(new_file, Config),
+ AbsLFile = filename:absname(LFile, PrivDir),
+ Contents = "ftp_SUITE test ...",
+ ok = file:write_file(AbsLFile, list_to_binary(Contents)),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:lcd(Pid, PrivDir),
+ ftp:delete(Pid, LFile), % reset
+ ftp:delete(Pid, NewLFile), % reset
+ ok = ftp:send(Pid, LFile),
+ {error, epath} = ftp:rename(Pid, NewLFile, LFile),
+ ok = ftp:rename(Pid, LFile, NewLFile),
+ ftp:delete(Pid, LFile), % cleanup
+ ftp:delete(Pid, NewLFile), % cleanup
+ ok.
+
+do_delete(Pid, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ LFile = ?config(file, Config),
+ AbsLFile = filename:absname(LFile, PrivDir),
+ Contents = "ftp_SUITE test ...",
+ ok = file:write_file(AbsLFile, list_to_binary(Contents)),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:lcd(Pid, PrivDir),
+ ftp:delete(Pid,LFile), % reset
+ ok = ftp:send(Pid, LFile),
+ ok = ftp:delete(Pid,LFile),
+ ok.
+
+do_mkdir(Pid) ->
+ {A, B, C} = erlang:now(),
+ NewDir = "nisse_" ++ integer_to_list(A) ++ "_" ++
+ integer_to_list(B) ++ "_" ++ integer_to_list(C),
+ ok = ftp:cd(Pid, "incoming"),
+ {ok, CurrDir} = ftp:pwd(Pid),
+ ok = ftp:mkdir(Pid, NewDir),
+ ok = ftp:cd(Pid, NewDir),
+ ok = ftp:cd(Pid, CurrDir),
+ ok = ftp:rmdir(Pid, NewDir),
+ ok.
+
+do_send(Pid, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ LFile = ?config(file, Config),
+ RFile = LFile ++ ".remote",
+ AbsLFile = filename:absname(LFile, PrivDir),
+ Contents = "ftp_SUITE test ...",
+ ok = file:write_file(AbsLFile, list_to_binary(Contents)),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:lcd(Pid, PrivDir),
+ ok = ftp:send(Pid, LFile, RFile),
+ {ok, RFilesString} = ftp:nlist(Pid),
+ RFiles = split(RFilesString),
+ true = lists:member(RFile, RFiles),
+ ok = ftp:delete(Pid, RFile),
+ case ftp:nlist(Pid) of
+ {error, epath} ->
+ ok; % No files
+ {ok, RFilesString1} ->
+ RFiles1 = split(RFilesString1),
+ false = lists:member(RFile, RFiles1)
+ end,
+ ok = file:delete(AbsLFile).
+
+do_append(Pid, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ LFile = ?config(file, Config),
+ RFile = ?config(new_file, Config),
+ AbsLFile = filename:absname(LFile, PrivDir),
+ Contents = "ftp_SUITE test:appending\r\n",
+
+ ok = file:write_file(AbsLFile, list_to_binary(Contents)),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:lcd(Pid, PrivDir),
+
+ %% remove files from earlier failed test case
+ ftp:delete(Pid, RFile),
+ ftp:delete(Pid, LFile),
+
+ ok = ftp:append(Pid, LFile, RFile),
+ ok = ftp:append(Pid, LFile, RFile),
+ ok = ftp:append(Pid, LFile),
+
+ %% Control the contents of the file
+ {ok, Bin1} = ftp:recv_bin(Pid, RFile),
+ ok = ftp:delete(Pid, RFile),
+ ok = file:delete(AbsLFile),
+ ok = check_content(binary_to_list(Bin1), Contents, double),
+
+ {ok, Bin2} = ftp:recv_bin(Pid, LFile),
+ ok = ftp:delete(Pid, LFile),
+ ok = check_content(binary_to_list(Bin2), Contents, singel),
+ ok.
+
+do_send_bin(Pid, Config) ->
+ File = ?config(file, Config),
+ Contents = "ftp_SUITE test ...",
+ Bin = list_to_binary(Contents),
+ ok = ftp:cd(Pid, "incoming"),
+ {error, enotbinary} = ftp:send_bin(Pid, Contents, File),
+ ok = ftp:send_bin(Pid, Bin, File),
+ {ok, RFilesString} = ftp:nlist(Pid),
+ RFiles = split(RFilesString),
+ true = lists:member(File, RFiles),
+ ok = ftp:delete(Pid, File),
+ ok.
+
+do_append_bin(Pid, Config) ->
+ File = ?config(file, Config),
+ Contents = "ftp_SUITE test ...",
+ Bin = list_to_binary(Contents),
+ ok = ftp:cd(Pid, "incoming"),
+ {error, enotbinary} = ftp:append_bin(Pid, Contents, File),
+ ok = ftp:append_bin(Pid, Bin, File),
+ ok = ftp:append_bin(Pid, Bin, File),
+ %% Control the contents of the file
+ {ok, Bin2} = ftp:recv_bin(Pid, File),
+ ok = ftp:delete(Pid,File),
+ ok = check_content(binary_to_list(Bin2),binary_to_list(Bin), double).
+
+do_send_chunk(Pid, Config) ->
+ File = ?config(file, Config),
+ Contents = "ftp_SUITE test ...",
+ Bin = list_to_binary(Contents),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:send_chunk_start(Pid, File),
+ {error, echunk} = ftp:cd(Pid, "incoming"),
+ {error, enotbinary} = ftp:send_chunk(Pid, Contents),
+ ok = ftp:send_chunk(Pid, Bin),
+ ok = ftp:send_chunk(Pid, Bin),
+ ok = ftp:send_chunk_end(Pid),
+ {ok, RFilesString} = ftp:nlist(Pid),
+ RFiles = split(RFilesString),
+ true = lists:member(File, RFiles),
+ ok = ftp:delete(Pid, File),
+ ok.
+
+do_append_chunk(Pid, Config) ->
+ File = ?config(file, Config),
+ Contents = ["ER","LE","RL"],
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:append_chunk_start(Pid, File),
+ {error, enotbinary} = ftp:append_chunk(Pid, lists:nth(1,Contents)),
+ ok = ftp:append_chunk(Pid,list_to_binary(lists:nth(1,Contents))),
+ ok = ftp:append_chunk(Pid,list_to_binary(lists:nth(2,Contents))),
+ ok = ftp:append_chunk(Pid,list_to_binary(lists:nth(3,Contents))),
+ ok = ftp:append_chunk_end(Pid),
+ %%Control the contents of the file
+ {ok, Bin2} = ftp:recv_bin(Pid, File),
+ ok = check_content(binary_to_list(Bin2),"ERL", double),
+ ok = ftp:delete(Pid, File),
+ ok.
+
+do_recv(Pid, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ File = ?config(file, Config),
+ Newfile = ?config(new_file, Config),
+ AbsFile = filename:absname(File, PrivDir),
+ Contents = "ftp_SUITE:recv test ...",
+ ok = file:write_file(AbsFile, list_to_binary(Contents)),
+ ok = ftp:cd(Pid, "incoming"),
+ ftp:delete(Pid, File), % reset
+ ftp:lcd(Pid, PrivDir),
+ ok = ftp:send(Pid, File),
+ ok = file:delete(AbsFile), % cleanup
+ test_server:sleep(100),
+ ok = ftp:lcd(Pid, PrivDir),
+ ok = ftp:recv(Pid, File),
+ {ok, Files} = file:list_dir(PrivDir),
+ true = lists:member(File, Files),
+ ok = file:delete(AbsFile), % cleanup
+ ok = ftp:recv(Pid, File, Newfile),
+ ok = ftp:delete(Pid, File), % cleanup
+ ok.
+
+do_recv_bin(Pid, Config) ->
+ File = ?config(file, Config),
+ Contents1 = "ftp_SUITE test ...",
+ Bin1 = list_to_binary(Contents1),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:send_bin(Pid, Bin1, File),
+ test_server:sleep(100),
+ {ok, Bin2} = ftp:recv_bin(Pid, File),
+ ok = ftp:delete(Pid, File), % cleanup
+ Contents2 = binary_to_list(Bin2),
+ Contents1 = Contents2,
+ ok.
+
+do_recv_chunk(Pid, Config) ->
+ File = ?config(file, Config),
+ Data = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
+ "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
+ "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
+ "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"
+ "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
+ "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII",
+
+ Contents1 = lists:flatten(lists:duplicate(10, Data)),
+ Bin1 = list_to_binary(Contents1),
+ ok = ftp:cd(Pid, "incoming"),
+ ok = ftp:type(Pid, binary),
+ ok = ftp:send_bin(Pid, Bin1, File),
+ test_server:sleep(100),
+ {error, "ftp:recv_chunk_start/2 not called"} = recv_chunk(Pid, <<>>),
+ ok = ftp:recv_chunk_start(Pid, File),
+ {ok, Contents2} = recv_chunk(Pid, <<>>),
+ ok = ftp:delete(Pid, File), % cleanup
+ ok = find_diff(Contents2, Contents1, 1),
+ ok.
+
+do_type(Pid) ->
+ ok = ftp:type(Pid, ascii),
+ ok = ftp:type(Pid, binary),
+ ok = ftp:type(Pid, ascii),
+ {error, etype} = ftp:type(Pid, foobar),
+ ok.
+
+do_quote(Pid) ->
+ ["257 \"/\""++_Rest] = ftp:quote(Pid, "pwd"), %% 257
+ [_| _] = ftp:quote(Pid, "help"),
+ %% This negativ test causes some ftp servers to hang. This test
+ %% is not important for the client, so we skip it for now.
+ %%["425 Can't build data connection: Connection refused."]
+ %% = ftp:quote(Pid, "list"),
+ ok.
+
+ watch_dog(Config) ->
+ Dog = test_server:timetrap(inets_test_lib:minutes(1)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ [{watchdog, Dog} | NewConfig].
+
+ close_connection(Config) ->
+ case ?config(ftp, Config) of
+ Pid when is_pid(Pid) ->
+ ok = ftp:close(Pid),
+ lists:delete({ftp, Pid}, Config);
+ _ ->
+ Config
+ end.
+
+ftp_host(Config) ->
+ case ?config(ftp_remote_host, Config) of
+ undefined ->
+ exit({skip, "No host specified"});
+ Host ->
+ Host
+ end.
+
+check_content(RContent, LContent, Amount) ->
+ LContent2 = case Amount of
+ double ->
+ LContent ++ LContent;
+ singel ->
+ LContent
+ end,
+ case string:equal(RContent, LContent2) of
+ true ->
+ ok;
+ false ->
+ %% Find where the diff is
+ Where = find_diff(RContent, LContent2, 1),
+ Where
+ end.
+
+find_diff(A, A, _) ->
+ ok;
+find_diff([H|T1], [H|T2], Pos) ->
+ find_diff(T1, T2, Pos+1);
+find_diff(RC, LC, Pos) ->
+ {error, {diff, Pos, RC, LC}}.
+
+recv_chunk(Pid, Acc) ->
+ case ftp:recv_chunk(Pid) of
+ ok ->
+ {ok, binary_to_list(Acc)};
+ {ok, Bin} ->
+ recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
+ Error ->
+ Error
+ end.
+
+split(Cs) ->
+ split(Cs, [], []).
+
+split([$\r, $\n| Cs], I, Is) ->
+ split(Cs, [], [lists:reverse(I)| Is]);
+split([C| Cs], I, Is) ->
+ split(Cs, [C| I], Is);
+split([], I, Is) ->
+ lists:reverse([lists:reverse(I)| Is]).
+
+do_ftp_open(Host, Flags) ->
+ io:format("do_ftp_open -> entry with"
+ "~n Host: ~p"
+ "~n Flags: ~p", [Host, Flags]),
+ case ftp:open(Host, Flags) of
+ {ok, _} = OK ->
+ OK;
+ {error, Reason} ->
+ Str =
+ lists:flatten(
+ io_lib:format("Unable to reach test FTP server ~p (~p)",
+ [Host, Reason])),
+ throw({skip, Str})
+ end.
+
+
+passwd() ->
+ Host =
+ case inet:gethostname() of
+ {ok, H} ->
+ H;
+ _ ->
+ "localhost"
+ end,
+ "ftp_SUITE@" ++ Host.
+
+ftpd_hosts(Config) ->
+ DataDir = ?config(data_dir, Config),
+ FileName = filename:join([DataDir, "../ftp_SUITE_data/", ftpd_hosts]),
+ io:format("FileName: ~p~n", [FileName]),
+ case file:consult(FileName) of
+ {ok, [Hosts]} when is_list(Hosts) ->
+ Hosts;
+ _ ->
+ []
+ end.
+
+wrapper(Prefix,doc,Func) ->
+ Prefix++Func(doc);
+wrapper(_,X,Func) ->
+ Func(X).
+
+data_dir(Config) ->
+ case ?config(data_dir, Config) of
+ List when (length(List) > 0) ->
+ PathList = filename:split(List),
+ {NewPathList,_} = lists:split((length(PathList)-1), PathList),
+ DataDir = filename:join(NewPathList ++ [ftp_SUITE_data]),
+ NewConfig =
+ lists:keyreplace(data_dir,1,Config, {data_dir,DataDir}),
+ NewConfig;
+ _ -> Config
+ end.
+
+
+
+p(F) ->
+ p(F, []).
+
+p(F, A) ->
+ case get(ftp_testcase) of
+ undefined ->
+ io:format("~w [~w] " ++ F ++ "~n", [?MODULE, self() | A]);
+ TC when is_atom(TC) ->
+ io:format("~w [~w] ~w:" ++ F ++ "~n", [?MODULE, self(), TC | A])
+ end.
diff --git a/lib/inets/test/ftp_ticket_test.erl b/lib/inets/test/ftp_ticket_test.erl
new file mode 100644
index 0000000000..6748df03bb
--- /dev/null
+++ b/lib/inets/test/ftp_ticket_test.erl
@@ -0,0 +1,51 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_ticket_test).
+
+-compile(export_all).
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Solaris 8 sparc ").
+
+
+%% Test server callbacks
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+
+all(suite) ->
+ {conf,init,tickets(),fin}.
+
+init(Config) ->
+ ?LIB_MOD:ftpd_init(ticket_test, Config).
+
+tickets() ->
+ [ticket_6035].
+
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+ticket_6035(X) -> ?LIB_MOD:ticket_6035(X).
diff --git a/lib/inets/test/ftp_windows_2003_server_test.erl b/lib/inets/test/ftp_windows_2003_server_test.erl
new file mode 100644
index 0000000000..d24318d04f
--- /dev/null
+++ b/lib/inets/test/ftp_windows_2003_server_test.erl
@@ -0,0 +1,152 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_windows_2003_server_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Windows 2003 server ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(windows_2003_server, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+%% Test cases starts here.
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/ftp_windows_xp_test.erl b/lib/inets/test/ftp_windows_xp_test.erl
new file mode 100644
index 0000000000..bc161e4f6a
--- /dev/null
+++ b/lib/inets/test/ftp_windows_xp_test.erl
@@ -0,0 +1,150 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ftp_windows_xp_test).
+
+-compile(export_all).
+
+-include("test_server.hrl").
+
+-define(LIB_MOD,ftp_suite_lib).
+-define(CASE_WRAPPER(_A_,_B_,_C_),?LIB_MOD:wrapper(_A_,_B_,_C_)).
+-define(PLATFORM,"Windows xp ").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ {File, NewFile} = ?LIB_MOD:test_filenames(),
+ NewConfig = [{file, File}, {new_file, NewFile} | Config],
+ ?LIB_MOD:ftpd_init(windows_xp, NewConfig).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ ftp_suite_lib:init_per_testcase(Case, Config).
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ ftp_suite_lib:end_per_testcase(Case, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test ftp client"];
+
+all(suite) ->
+ [open, open_port, passive, active, api_missuse,
+ not_owner, progress_report].
+
+open(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open/1).
+open_port(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:open_port/1).
+passive(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:passive/1).
+active(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:active/1).
+api_missuse(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:api_missuse/1).
+not_owner(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:not_owner/1).
+progress_report(X) -> ?CASE_WRAPPER(?PLATFORM,X,fun ?LIB_MOD:progress_report/1).
+
+passive_user(X) -> ?LIB_MOD:passive_user(X).
+passive_pwd(X) -> ?LIB_MOD:passive_pwd(X).
+passive_cd(X) -> ?LIB_MOD:passive_cd(X).
+passive_lcd(X) -> ?LIB_MOD:passive_lcd(X).
+passive_ls(X) -> ?LIB_MOD:passive_ls(X).
+passive_nlist(X) -> ?LIB_MOD:passive_nlist(X).
+passive_rename(X) -> ?LIB_MOD:passive_rename(X).
+passive_delete(X) -> ?LIB_MOD:passive_delete(X).
+passive_mkdir(X) -> ?LIB_MOD:passive_mkdir(X).
+passive_send(X) -> ?LIB_MOD:passive_send(X).
+passive_send_bin(X) -> ?LIB_MOD:passive_send_bin(X).
+passive_send_chunk(X) -> ?LIB_MOD:passive_send_chunk(X).
+passive_append(X) -> ?LIB_MOD:passive_append(X).
+passive_append_bin(X) -> ?LIB_MOD:passive_append_bin(X).
+passive_append_chunk(X) -> ?LIB_MOD:passive_append_chunk(X).
+passive_recv(X) -> ?LIB_MOD:passive_recv(X).
+passive_recv_bin(X) -> ?LIB_MOD:passive_recv_bin(X).
+passive_recv_chunk(X) -> ?LIB_MOD:passive_recv_chunk(X).
+passive_type(X) -> ?LIB_MOD:passive_type(X).
+passive_quote(X) -> ?LIB_MOD:passive_quote(X).
+passive_ip_v6_disabled(X) -> ?LIB_MOD:passive_ip_v6_disabled(X).
+active_user(X) -> ?LIB_MOD:active_user(X).
+active_pwd(X) -> ?LIB_MOD:active_pwd(X).
+active_cd(X) -> ?LIB_MOD:active_cd(X).
+active_lcd(X) -> ?LIB_MOD:active_lcd(X).
+active_ls(X) -> ?LIB_MOD:active_ls(X).
+active_nlist(X) -> ?LIB_MOD:active_nlist(X).
+active_rename(X) -> ?LIB_MOD:active_rename(X).
+active_delete(X) -> ?LIB_MOD:active_delete(X).
+active_mkdir(X) -> ?LIB_MOD:active_mkdir(X).
+active_send(X) -> ?LIB_MOD:active_send(X).
+active_send_bin(X) -> ?LIB_MOD:active_send_bin(X).
+active_send_chunk(X) -> ?LIB_MOD:active_send_chunk(X).
+active_append(X) -> ?LIB_MOD:active_append(X).
+active_append_bin(X) -> ?LIB_MOD:active_append_bin(X).
+active_append_chunk(X) -> ?LIB_MOD:active_append_chunk(X).
+active_recv(X) -> ?LIB_MOD:active_recv(X).
+active_recv_bin(X) -> ?LIB_MOD:active_recv_bin(X).
+active_recv_chunk(X) -> ?LIB_MOD:active_recv_chunk(X).
+active_type(X) -> ?LIB_MOD:active_type(X).
+active_quote(X) -> ?LIB_MOD:active_quote(X).
+active_ip_v6_disabled(X) -> ?LIB_MOD:active_ip_v6_disabled(X).
+progress_report_send(X) -> ?LIB_MOD:progress_report_send(X).
+progress_report_recv(X) -> ?LIB_MOD:progress_report_recv(X).
+
+fin(Config) ->
+ ?LIB_MOD:ftpd_fin(Config).
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
new file mode 100644
index 0000000000..9559317640
--- /dev/null
+++ b/lib/inets/test/http_format_SUITE.erl
@@ -0,0 +1,585 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(http_format_SUITE).
+-author('[email protected]').
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("http_internal.hrl").
+
+%% Test server specific exports
+-export([all/1, init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases must be exported.
+-export([chunk/1, chunk_decode/1, chunk_encode/1,
+ chunk_extensions_otp_6005/1, chunk_decode_otp_6264/1,
+ chunk_decode_empty_chunk_otp_6511/1,
+ chunk_decode_trailer/1,
+ http_response/1, http_request/1, validate_request_line/1, script/1,
+ esi_parse_headers/1, cgi_parse_headers/1,
+ is_absolut_uri/1, convert_netscapecookie_date/1]).
+
+all(doc) ->
+ ["Test library functions to the http client and server."];
+all(suite) ->
+ [chunk,
+ http_response, http_request, validate_request_line,
+ script, is_absolut_uri, convert_netscapecookie_date].
+
+init_per_testcase(_, Config) ->
+ Dog = test_server:timetrap(?t:minutes(1)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ [{watchdog, Dog} | NewConfig].
+
+end_per_testcase(_, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+script(doc) ->
+ ["Test header parsing in esi/cgi functionality."];
+script(suite) ->
+ [esi_parse_headers, cgi_parse_headers].
+
+chunk(doc) ->
+ ["Test chunk encoding"];
+chunk(suite) ->
+ [chunk_decode, chunk_encode, chunk_extensions_otp_6005,
+ chunk_decode_otp_6264, chunk_decode_empty_chunk_otp_6511,
+ chunk_decode_trailer].
+
+%%-------------------------------------------------------------------------
+chunk_decode(doc) ->
+ ["Test http_chunk:decode/3"];
+chunk_decode(suite) ->
+ [];
+chunk_decode(Config) when is_list(Config) ->
+ ReqHeaders = #http_request_h{'transfer-encoding' = "chunked"},
+ ChunkedBody = "A" ++ ?CRLF ++ "1234567890" ++ ?CRLF ++ "4" ++
+ ?CRLF ++ "HEJ!" ++ ?CRLF ++ "0" ++ ?CRLF ++ ?CRLF,
+ {ok, {Headers, Body}} =
+ http_chunk:decode(list_to_binary(ChunkedBody),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+ "1234567890HEJ!" = binary_to_list(Body),
+ %% When the "chunked" is removed by the decoding the header
+ %% will become empty in this case i.e. undefined!
+ NewReqHeaders = http_chunk:handle_headers(ReqHeaders, Headers),
+ undefined = NewReqHeaders#http_request_h.'transfer-encoding',
+
+ NewChunkedBody = ["A" ++ [?CR], [?LF] ++ "12345", "67890" ++ ?CRLF ++ "4"
+ ++ ?CRLF ++ "HEJ!" ++ ?CRLF ++ "0" ++ [?CR],
+ [?LF, ?CR, ?LF]],
+
+ {Module, Function, Args} =
+ http_chunk:decode(list_to_binary(hd(NewChunkedBody)),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ {_, Body} = parse(Module, Function, Args, tl(NewChunkedBody)),
+ "1234567890HEJ!" = binary_to_list(Body),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+chunk_extensions_otp_6005(doc) ->
+ ["Make sure so called extensions are ignored"];
+chunk_extensions_otp_6005(suite) ->
+ [];
+chunk_extensions_otp_6005(Config) when is_list(Config)->
+ ChunkedBody = "A;ignore this" ++ ?CRLF ++ "1234567890" ++
+ ?CRLF ++ "4" ++ ?CRLF ++ "HEJ!"++ ?CRLF ++ "0" ++
+ ";extensionname=extensionvalue;foo=bar" ++ ?CRLF ++ ?CRLF,
+ {ok, {["content-length:14"], Body}} =
+ http_chunk:decode(list_to_binary(ChunkedBody),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+ "1234567890HEJ!" = binary_to_list(Body),
+
+ ChunkedBody1 = ["A;", "ignore this" ++ [?CR], [?LF] ++ "1234567890" ++
+ ?CRLF ++ "4" ++ ?CRLF ++ "HEJ!"++ ?CRLF ++ "0" ++
+ ";extensionname=extensionvalue;foo=bar" ++ ?CRLF ++ ?CRLF],
+
+ {Module1, Function1, Args1} =
+ http_chunk:decode(list_to_binary(hd(ChunkedBody1)),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ {_, NewBody} = parse(Module1, Function1, Args1, tl(ChunkedBody1)),
+ "1234567890HEJ!" = binary_to_list(NewBody),
+ ok.
+
+%%-------------------------------------------------------------------------
+chunk_decode_otp_6264(doc) ->
+ ["Check that 0 in the body does not count as the last chunk"];
+chunk_decode_otp_6264(suite) ->
+ [];
+chunk_decode_otp_6264(Config) when is_list(Config)->
+ ChunkedBody = "A;ignore this" ++ ?CRLF ++ "1234567890" ++
+ ?CRLF ++ "4" ++ ?CRLF ++ "0123"++ ?CRLF ++ "0" ++
+ ";extensionname=extensionvalue;foo=bar" ++ ?CRLF ++ ?CRLF,
+ {ok, {["content-length:14"], Body}} =
+ http_chunk:decode(list_to_binary(ChunkedBody),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+ "12345678900123" = binary_to_list(Body),
+
+ NewChunkedBody = ["A" ++ [?CR], [?LF] ++ "12345", "67890" ++ ?CRLF ++ "1"
+ ++ ?CRLF ++ "0" ++ ?CRLF ++ "0" ++ [?CR],
+ [?LF, ?CR, ?LF]],
+
+ {Module, Function, Args} =
+ http_chunk:decode(list_to_binary(hd(NewChunkedBody)),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ {_, NewBody} = parse(Module, Function, Args, tl(NewChunkedBody)),
+ "12345678900" = binary_to_list(NewBody),
+
+ NewChunkedBody1 = ["A" ++ [?CR], [?LF] ++ "12345", "67890" ++ ?CRLF ++ "1"
+ ++ ?CRLF ++ "0", ?CRLF ++ "0", [?CR], [?LF],
+ [?CR], [?LF]],
+
+ {Module1, Function1, Args1} =
+ http_chunk:decode(list_to_binary(hd(NewChunkedBody1)),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ {_, NewBody} = parse(Module1, Function1, Args1, tl(NewChunkedBody1)),
+ "12345678900" = binary_to_list(NewBody),
+
+ ok.
+%%-------------------------------------------------------------------------
+chunk_decode_empty_chunk_otp_6511(doc) ->
+ [""];
+chunk_decode_empty_chunk_otp_6511(suite) ->
+ [];
+chunk_decode_empty_chunk_otp_6511(Config) when is_list(Config) ->
+ ChunkedBody = "0" ++ ?CRLF ++ ?CRLF,
+ {ok,{["content-length:0"],<<>>}} =
+ http_chunk:decode(list_to_binary(ChunkedBody),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+ ok.
+
+%%-------------------------------------------------------------------------
+chunk_decode_trailer(doc) ->
+ ["Make sure trailers are handled correctly. Trailers should"
+ "become new headers"];
+chunk_decode_trailer(suite) ->
+ [];
+chunk_decode_trailer(Config) when is_list(Config)->
+ ChunkedBody = "1a; ignore-stuff-here" ++ ?CRLF ++
+ "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF
+ ++ "1234567890abcdef" ++ ?CRLF ++ "0" ++ ?CRLF
+ ++ "some-footer:some-value" ++ ?CRLF
+ ++ "another-footer:another-value" ++ ?CRLF ++ ?CRLF,
+
+ {ok, {Headers, Body}} =
+ http_chunk:decode(list_to_binary(ChunkedBody),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ %% There is no guaranteed order of headers.
+ true = lists:member("content-length:42", Headers),
+ true = lists:member("some-footer:some-value", Headers),
+ true = lists:member("another-footer:another-value", Headers),
+ "abcdefghijklmnopqrstuvwxyz1234567890abcdef" = binary_to_list(Body),
+
+ ChunkedBody1 = "1a" ++ ?CRLF ++
+ "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF
+ ++ "1234567890abcdef" ++ ?CRLF ++ "0" ++ ?CRLF
+ ++ "some-footer:some-value" ++ ?CRLF ++ ?CRLF,
+
+ {ok, {Headers1, Body1}} =
+ http_chunk:decode(list_to_binary(ChunkedBody1),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ true = lists:member("content-length:42", Headers1),
+ true = lists:member("some-footer:some-value", Headers1),
+ false = lists:member("another-footer:another-value", Headers1),
+ "abcdefghijklmnopqrstuvwxyz1234567890abcdef" = binary_to_list(Body1),
+
+
+ ChunkedBody2 = ["1a", ?CRLF ++
+ "abcdefghijklmnopqrstuvwxyz" ++ ?CRLF ++ "10" ++ ?CRLF
+ ++ "1234567890abcdef" ++ ?CRLF ++ "0", ";",
+ "ignore stuff here=foobar", ?CRLF ++
+ "some-footer:some-value", ?CRLF, ?CRLF],
+
+ {Module, Function, Args} =
+ http_chunk:decode(list_to_binary(hd(ChunkedBody2)),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ {_, NewBody} = parse(Module, Function, Args, tl(ChunkedBody2)),
+ "abcdefghijklmnopqrstuvwxyz1234567890abcdef" = binary_to_list(NewBody),
+
+ ChunkedBody3 = ["1a", ?CRLF ++
+ "abcdefghijklmnopqrstuvwxyz", ?CRLF ++ "10" ++ ?CRLF
+ ++ "1234567890abcdef" ++ ?CRLF ++ "0" ++ ?CRLF
+ ++ "some-footer:some-value", [?CR], [?LF] , ?CRLF],
+
+ {Module1, Function1, Args1} =
+ http_chunk:decode(list_to_binary(hd(ChunkedBody3)),
+ ?HTTP_MAX_BODY_SIZE, ?HTTP_MAX_HEADER_SIZE),
+
+ {_, NewBody} = parse(Module1, Function1, Args1, tl(ChunkedBody3)),
+ "abcdefghijklmnopqrstuvwxyz1234567890abcdef" = binary_to_list(NewBody),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+chunk_encode(doc) ->
+ ["Test http_chunk:encode/1 & http_chunk:encode_last/0"];
+chunk_encode(suite) ->
+ [];
+chunk_encode(Config) when is_list(Config) ->
+ <<54, ?CR, ?LF, 102,111,111,98,97,114, ?CR, ?LF>> =
+ http_chunk:encode(list_to_binary("foobar")),
+ ["6", ?CR, ?LF,"foobar", ?CR, ?LF] = http_chunk:encode("foobar"),
+ <<$0, ?CR, ?LF, ?CR, ?LF >> = http_chunk:encode_last(),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+http_response(doc) ->
+ ["Test httpc_response:parse*. This test case will simulate that the "
+ "message will be recived a little at the time on a socket and the "
+ "package may be broken up into smaller parts at arbitrary point."];
+http_response(suite) ->
+ [];
+http_response(Config) when is_list(Config) ->
+
+ HttpHead1 = ["HTTP", "/1.1 ", "20", "0 ", "ok", [?CR, ?LF],
+ "content-length:83" ++ ?CRLF ++ "content", "-type:",
+ "text/html" ++ ?CRLF ++
+ "date:Thu, 28 Oct 2004 07:57:43 GMT" ++
+ [?CR], [?LF, ?CR, ?LF]],
+ {"HTTP/1.1",
+ 200,
+ "ok",
+ #http_response_h{'content-length' = "83",
+ 'content-type' = "text/html",
+ date = "Thu, 28 Oct 2004 07:57:43 GMT"},
+ <<>>} =
+ parse(httpc_response, parse, [?HTTP_MAX_HEADER_SIZE, false],
+ HttpHead1),
+
+ HttpHead2 = ["HTTP/1.1 200", " ok", [?CR], [?LF] ++
+ "content-length:83" ++ ?CRLF ++ "content-type:",
+ "text/html" ++ ?CRLF ++
+ "date:" ++ "Thu, 28 Oct 2004 07:57:43 GMT" ++
+ ?CRLF, ?CRLF],
+ {"HTTP/1.1",
+ 200,
+ "ok",
+ #http_response_h{'content-length' = "83",
+ 'content-type' = "text/html",
+ date = "Thu, 28 Oct 2004 07:57:43 GMT"},
+ <<>>} =
+ parse(httpc_response, parse, [?HTTP_MAX_HEADER_SIZE, false],
+ HttpHead2),
+
+ HttpHead3 = ["HTTP/1.1 200 ", "ok", ?CRLF ++
+ "content-length:83" ++ ?CRLF ++ "content-type:",
+ "text/html" ++ ?CRLF ++
+ "date:" ++ "Thu, 28 Oct 2004 07:57:43 GMT" ++
+ [?CR, ?LF,?CR], [?LF]],
+ {"HTTP/1.1",
+ 200,
+ "ok",
+ #http_response_h{'content-length' = "83",
+ 'content-type' = "text/html",
+ date = "Thu, 28 Oct 2004 07:57:43 GMT"},
+ <<>>} =
+ parse(httpc_response, parse, [?HTTP_MAX_HEADER_SIZE, false],
+ HttpHead3),
+
+ HttpBody = ["<HTML>\n<HEAD>\n<TITLE> dummy </TITLE>\n</HEAD>\n<BODY>\n",
+ "<H1>dummy</H1>\n</BODY>\n</HTML>\n"],
+
+ NewBody = lists:flatten(HttpBody),
+ Length = length(NewBody),
+ NewBody =
+ binary_to_list(parse
+ (httpc_response, whole_body, [<<>>,Length],
+ HttpBody)),
+
+ HttpBody1 = ["<HTML", ">\n<HEAD>", "\n<TITLE> dummy </TITLE>\n</HEAD>\n",
+ "<BODY>\n", "<H1>du", "mmy</H1>\n</BODY>\n</HTML>\n"],
+
+ NewBody1 = lists:flatten(HttpBody1),
+ Length1 = length(NewBody1),
+ NewBody1 = binary_to_list(parse
+ (httpc_response, whole_body,
+ [<<>>,Length1], HttpBody1)),
+ ok.
+%%-------------------------------------------------------------------------
+http_request(doc) ->
+ ["Test httpd_request:parse* This test case will simulate that the "
+ "message will be recived a little at the time on a socket and the "
+ "package may be broken up into smaller parts at arbitrary point."];
+http_request(suite) ->
+ [];
+http_request(Config) when is_list(Config) ->
+
+ HttpHead = ["GE", "T ", "http://www.erlang", ".org ", "HTTP",
+ "/1.1" ++ ?CRLF ++ "host:",
+ "www.erlang.org" ++ [?CR],
+ [?LF] ++ "te: " ++ ?CRLF, ?CRLF],
+ {"GET",
+ "http://www.erlang.org",
+ "HTTP/1.1",
+ {#http_request_h{host = "www.erlang.org", te = []},
+ ["te: ","host:www.erlang.org"]}, <<>>} =
+ parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead),
+
+ HttpHead1 = ["GET http://www.erlang.org HTTP/1.1" ++
+ [?CR], [?LF, ?CR, ?LF]],
+ {"GET",
+ "http://www.erlang.org",
+ "HTTP/1.1",
+ {#http_request_h{}, []}, <<>>} =
+ parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead1),
+
+
+ HttpHead2 = ["GET http://www.erlang.org HTTP/1.1" ++
+ [?CR, ?LF, ?CR], [?LF]],
+ {"GET",
+ "http://www.erlang.org",
+ "HTTP/1.1",
+ {#http_request_h{}, []}, <<>>} =
+ parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead2),
+
+ %% Note the following body is not related to the headers above
+ HttpBody = ["<HTML>\n<HEAD>\n<TITLE> dummy </TITLE>\n</HEAD>\n<BODY>\n",
+ "<H1>dummy</H1>\n</BODY>\n</HTML>\n"],
+
+ NewBody = lists:flatten(HttpBody),
+ Length = length(NewBody),
+ NewBody =
+ binary_to_list(parse
+ (httpd_request, whole_body, [<<>>,Length], HttpBody)),
+
+ HttpBody1 = ["<HTML", ">\n<HEAD>", "\n<TITLE> dummy </TITLE>\n</HEAD>\n",
+ "<BODY>\n", "<H1>du", "mmy</H1>\n</BODY>\n</HTML>\n"],
+
+ NewBody1 = lists:flatten(HttpBody1),
+ Length1 = length(NewBody1),
+ NewBody1 =
+ binary_to_list(parse
+ (httpd_request, whole_body,
+ [<<>>, Length1], HttpBody1)),
+ ok.
+%%-------------------------------------------------------------------------
+validate_request_line(doc) ->
+ ["Test httpd_request:validate/3. Makes sure you can not get past"
+ " the server_root and that the request is recognized by the server"
+ " and protcol version." ];
+validate_request_line(suite) ->
+ [];
+validate_request_line(Config) when is_list(Config) ->
+
+ %% HTTP/0.9 only has GET requests
+ ok =
+ httpd_request:validate("GET", "http://www.erlang/org", "HTTP/0.9"),
+ {error, {not_supported,
+ {"HEAD", "http://www.erlang/org", "HTTP/0.9"}}} =
+ httpd_request:validate("HEAD", "http://www.erlang/org", "HTTP/0.9"),
+ {error, {not_supported,
+ {"TRACE", "http://www.erlang/org", "HTTP/0.9"}}} =
+ httpd_request:validate("TRACE", "http://www.erlang/org", "HTTP/0.9"),
+ {error, {not_supported,
+ {"POST", "http://www.erlang/org", "HTTP/0.9"}}} =
+ httpd_request:validate("POST", "http://www.erlang/org", "HTTP/0.9"),
+
+ %% HTTP/1.*
+ ok = httpd_request:validate("HEAD", "http://www.erlang/org",
+ "HTTP/1.1"),
+ ok = httpd_request:validate("GET", "http://www.erlang/org",
+ "HTTP/1.1"),
+ ok = httpd_request:validate("POST","http://www.erlang/org",
+ "HTTP/1.1"),
+ ok = httpd_request:validate("TRACE","http://www.erlang/org",
+ "HTTP/1.1"),
+ {error, {not_supported,
+ {"FOOBAR", "http://www.erlang/org", "HTTP/1.1"}}} =
+ httpd_request:validate("FOOBAR", "http://www.erlang/org",
+ "HTTP/1.1"),
+
+ %% Attempts to get outside of server_root directory by relative links
+ ForbiddenUri = "http://127.0.0.1:8888/../../../../../etc/passwd",
+ {error, {bad_request, {forbidden, ForbiddenUri}}} =
+ httpd_request:validate("GET", ForbiddenUri, "HTTP/1.1"),
+
+ ForbiddenUri2 =
+ "http://127.0.0.1:8888/././././././../../../../../etc/passwd",
+ {error, {bad_request, {forbidden, ForbiddenUri2}}} =
+ httpd_request:validate("GET", ForbiddenUri2, "HTTP/1.1"),
+
+ HexForbiddenUri = "http://127.0.0.1:8888/%2e%2e/%2e%2e/%2e%2e/"
+ "home/ingela/test.html",
+ {error, {bad_request, {forbidden, HexForbiddenUri}}} =
+ httpd_request:validate("GET", HexForbiddenUri, "HTTP/1.1"),
+
+ NewForbiddenUri =
+ "http://127.0.0.1:8888/foobar/../../../home/ingela/test.html",
+ {error, {bad_request, {forbidden, NewForbiddenUri}}} =
+ httpd_request:validate("GET", NewForbiddenUri, "HTTP/1.1"),
+
+ NewForbiddenUri1 =
+ "http://127.0.0.1:8888/../home/ingela/test.html",
+ {error, {bad_request, {forbidden, NewForbiddenUri1}}} =
+ httpd_request:validate("GET", NewForbiddenUri1, "HTTP/1.1"),
+
+ ok.
+%%-------------------------------------------------------------------------
+esi_parse_headers(doc) ->
+ ["Test httpd_esi:*. All header values are received in the same"
+ " erlang message."];
+esi_parse_headers(suite) ->
+ [];
+esi_parse_headers(Config) when is_list(Config) ->
+
+ ESIResult = "content-type:text/html\r\ndate:Thu, 28 Oct 2004 07:57:43 "
+ "GMT\r\nstatus:200 OK\r\n\r\nFoobar",
+
+ {"content-type:text/html\r\ndate:Thu, 28 Oct 2004 07:57:43 GMT\r\nst"
+ "atus:200 OK\r\n" = Headers,
+ "Foobar"} = httpd_esi:parse_headers(ESIResult),
+
+ {ok,[{"date","Thu, 28 Oct 2004 07:57:43 GMT"},
+ {"content-type","text/html"}], 200} =
+ httpd_esi:handle_headers(Headers),
+
+ ESIResult2 =
+ "location:http://foo.bar.se\r\ndate:Thu, 28 Oct 2004 07:57:43 "
+ "GMT\r\n\r\n",
+
+ {"location:http://foo.bar.se\r\ndate:Thu, 28 Oct 2004 07:57:43 GMT\r\n" =
+ Headers2,[]}
+ = httpd_esi:parse_headers(ESIResult2),
+
+ {ok,[{"date","Thu, 28 Oct 2004 07:57:43 GMT"},
+ {"location","http://foo.bar.se"}], 302} =
+ httpd_esi:handle_headers(Headers2),
+
+ {proceed,"/foo/bar.html"} =
+ httpd_esi:handle_headers("location:/foo/bar.html\r\n"),
+ ok.
+
+%%--------------------------------------------------------------------
+cgi_parse_headers(doc) ->
+ ["Test httpd_cgi:*. This test case will simulate that the "
+ "message will be recived a little at the time on a socket and the "
+ "package may be broken up into smaller parts at arbitrary point."];
+cgi_parse_headers(suite) ->
+ [];
+cgi_parse_headers(Config) when is_list(Config) ->
+
+ CGIResult = ["content-type:text", "/html\ndate:Thu, 28 Oct 2004 07:57:43 "
+ "GMT\nst", "atus:200 OK\n", "\nFoobar"],
+
+ {Headers, Body} =
+ parse(httpd_cgi, parse_headers, [<<>>, [], []], CGIResult),
+
+ "Foobar" = binary_to_list(Body),
+
+ {ok,[{"content-type","text/html"},
+ {"date","Thu, 28 Oct 2004 07:57:43 GMT"}], {200,"OK"}} =
+ httpd_cgi:handle_headers(Headers),
+
+ CGIResult2 = ["location:http://foo.bar.se\ndate:Thu, 28 Oct 2004"
+ " 07:57:43 GMT\n\n"],
+ {Headers2, _} = parse(httpd_cgi, parse_headers,
+ [<<>>, [], []], CGIResult2),
+
+ {ok,[{"location","http://foo.bar.se"},
+ {"date","Thu, 28 Oct 2004 07:57:43 GMT"}], {302,"Redirect"}} =
+ httpd_cgi:handle_headers(Headers2),
+
+ {proceed,"/foo/bar.html"} =
+ httpd_cgi:handle_headers(["location:/foo/bar.html\n"]),
+
+ CGIHTTPResult = ["Content-Type:text", "/html\n", "Connection:close\r\n",
+ "Content-Language:en\r\nAge:", "4711\r\n\r\n\nfoobar"],
+
+ {Headers3, _} = parse(httpd_cgi, parse_headers,
+ [<<>>, [], []], CGIHTTPResult),
+
+ {ok,[{"content-type","text/html"},
+ {"connection","close"},
+ {"content-language","en"},
+ {"age","4711"}], {200,"ok"}} = httpd_cgi:handle_headers(Headers3),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+is_absolut_uri(doc) ->
+ ["Test http_request:is_absolut_uri/1."];
+is_absolut_uri(suite) ->
+ [];
+is_absolut_uri(Config) when is_list(Config) ->
+ true = http_request:is_absolut_uri("http://www.erlang.org"),
+ true = http_request:is_absolut_uri("https://www.erlang.org"),
+ false = http_request:is_absolut_uri("index.html").
+
+
+%%-------------------------------------------------------------------------
+convert_netscapecookie_date(doc) ->
+ ["Test http_util:convert_netscapecookie_date/1."];
+convert_netscapecookie_date(suite) ->
+ [];
+convert_netscapecookie_date(Config) when is_list(Config) ->
+ {{2006,1,6},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Mon, 06-Jan-2006 08:59:38 GMT"),
+ {{2006,2, 7},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Tue, 07-Feb-2006 08:59:38 GMT"),
+ {{2006,3,8},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Wdy, 08-Mar-2006 08:59:38 GMT"),
+ {{2006,4,9},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Thu, 09-Apr-2006 08:59:38 GMT"),
+ {{2006,5,10},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Fri, 10-May-2006 08:59:38 GMT"),
+ {{2006,6,11},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sat, 11-Jun-2006 08:59:38 GMT"),
+ {{2006,7,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Jul-2006 08:59:38 GMT"),
+ {{2006,8,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Aug-2006 08:59:38 GMT"),
+ {{2006,9,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Sep-2006 08:59:38 GMT"),
+ {{2006,10,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Oct-2006 08:59:38 GMT"),
+ {{2006,11,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Nov-2006 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Dec-2006 08:59:38 GMT"),
+ ok.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+parse(Module, Function, Args, [Data | Rest]) ->
+ case Module:Function([list_to_binary(Data) | Args]) of
+ {ok, Result} ->
+ Result;
+ {NewModule, NewFunction, NewArgs} ->
+ parse(NewModule, NewFunction, NewArgs, Rest)
+ end.
+
+
+
diff --git a/lib/inets/test/http_internal.hrl b/lib/inets/test/http_internal.hrl
new file mode 120000
index 0000000000..a4600a09f7
--- /dev/null
+++ b/lib/inets/test/http_internal.hrl
@@ -0,0 +1 @@
+../src/http_lib/http_internal.hrl \ No newline at end of file
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
new file mode 100644
index 0000000000..4914a16264
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -0,0 +1,2846 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+%%
+%% ts:run(inets, httpc_SUITE, [batch]).
+%%
+
+-module(httpc_SUITE).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%% Test server specific exports
+-define(PROXY_URL, "http://www.erlang.org").
+-define(PROXY, "www-proxy.ericsson.se").
+-define(PROXY_PORT, 8080).
+-define(IP_PORT, 8998).
+-define(SSL_PORT, 8999).
+-define(NOT_IN_USE_PORT, 8997).
+-define(LOCAL_HOST, {127,0,0,1}).
+-define(IPV6_LOCAL_HOST, "0:0:0:0:0:0:0:1").
+-define(URL_START, "http://localhost:").
+-define(SSL_URL_START, "https://localhost:").
+-define(CR, $\r).
+-define(LF, $\n).
+-define(HTTP_MAX_HEADER_SIZE, 10240).
+
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+
+all(doc) ->
+ ["Test the http client in the intes application."];
+all(suite) ->
+ [
+ proxy_options,
+ proxy_head,
+ proxy_get,
+ proxy_trace,
+ proxy_post,
+ proxy_put,
+ proxy_delete,
+ proxy_auth,
+ proxy_headers,
+ proxy_emulate_lower_versions,
+ http_options,
+ http_head,
+ http_get,
+ http_post,
+ http_dummy_pipe,
+ http_inets_pipe,
+ http_trace,
+ http_async,
+ http_save_to_file,
+ http_save_to_file_async,
+ http_headers,
+ http_headers_dummy,
+ http_bad_response,
+ ssl_head,
+ ssl_get,
+ ssl_trace,
+ http_redirect,
+ http_redirect_loop,
+ http_internal_server_error,
+ http_userinfo,
+ http_cookie,
+ http_server_does_not_exist,
+ http_invalid_http,
+ http_emulate_lower_versions,
+ http_relaxed,
+ page_does_not_exist,
+ proxy_page_does_not_exist,
+ proxy_https_not_supported,
+ http_stream,
+ http_stream_once,
+ proxy_stream,
+ parse_url,
+ options,
+ ipv6,
+ headers_as_is,
+ tickets
+ ].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ ServerRoot = filename:join(PrivDir, "server_root"),
+ DocRoot = filename:join(ServerRoot, "htdocs"),
+ IpConfFile = integer_to_list(?IP_PORT) ++ ".conf",
+ SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf",
+
+ setup_server_dirs(ServerRoot, DocRoot, DataDir),
+ create_config(IpConfFile, ip_comm, ?IP_PORT, PrivDir, ServerRoot,
+ DocRoot, DataDir),
+ create_config(SslConfFile, ssl, ?SSL_PORT, PrivDir, ServerRoot,
+ DocRoot, DataDir),
+
+ Cgi = case test_server:os_type() of
+ {win32, _} ->
+ filename:join([ServerRoot, "cgi-bin", "cgi_echo.exe"]);
+ _ ->
+ filename:join([ServerRoot, "cgi-bin", "cgi_echo"])
+ end,
+
+ {ok, FileInfo} = file:read_file_info(Cgi),
+ ok = file:write_file_info(Cgi, FileInfo#file_info{mode = 8#00755}),
+
+ [{server_root, ServerRoot},
+ {doc_root, DocRoot},
+ {local_port, ?IP_PORT},
+ {local_ssl_port, ?SSL_PORT} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ inets_test_lib:del_dirs(PrivDir),
+ application:stop(inets),
+ application:stop(ssl),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(otp_8154_1 = Case, Config) ->
+ init_per_testcase(Case, 5, Config);
+init_per_testcase(Case, Config) ->
+ init_per_testcase(Case, 2, Config).
+
+init_per_testcase(Case, Timeout, Config) ->
+ io:format(user, "~n~n*** INIT ~w:~w[~w] ***~n~n",
+ [?MODULE, Timeout, Case]),
+ PrivDir = ?config(priv_dir, Config),
+ application:stop(inets),
+ Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)),
+ TmpConfig = lists:keydelete(watchdog, 1, Config),
+ IpConfFile = integer_to_list(?IP_PORT) ++ ".conf",
+ SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf",
+
+ NewConfig =
+ case atom_to_list(Case) of
+ "ssl" ++ _ ->
+ application:stop(ssl),
+ TmpConfig2 =
+ lists:keydelete(local_ssl_server, 1, TmpConfig),
+ %% Will start inets
+ Server =
+ inets_test_lib:start_http_server(
+ filename:join(PrivDir, SslConfFile)),
+ [{watchdog, Dog}, {local_ssl_server, Server} | TmpConfig2];
+ "proxy" ++ Rest ->
+ case Rest of
+ "_https_not_supported" ->
+ inets:start(),
+ case (catch application:start(ssl)) of
+ ok ->
+ [{watchdog, Dog} | TmpConfig];
+ _ ->
+ [{skip,
+ "SSL does not seem to be supported"}
+ | TmpConfig]
+ end;
+ _ ->
+ case is_proxy_available(?PROXY, ?PROXY_PORT) of
+ true ->
+ inets:start(),
+ [{watchdog, Dog} | TmpConfig];
+ false ->
+ [{skip, "Failed to contact proxy"} |
+ TmpConfig]
+ end
+ end;
+ _ ->
+ TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig),
+ Server =
+ %% Will start inets
+ inets_test_lib:start_http_server(
+ filename:join(PrivDir, IpConfFile)),
+ [{watchdog, Dog}, {local_server, Server} | TmpConfig2]
+ end,
+
+ http:set_options([{proxy, {{?PROXY, ?PROXY_PORT},
+ ["localhost", ?IPV6_LOCAL_HOST]}}]),
+ inets:enable_trace(max, io, httpc),
+ %% snmp:set_trace([gen_tcp, inet_tcp, prim_inet]),
+ NewConfig.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(http_save_to_file, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FullPath = filename:join(PrivDir, "dummy.html"),
+ file:delete(FullPath),
+ finish(Config);
+
+end_per_testcase(_, Config) ->
+ finish(Config).
+
+finish(Config) ->
+ Dog = ?config(watchdog, Config),
+ case Dog of
+ undefined ->
+ ok;
+ _ ->
+ test_server:timetrap_cancel(Dog)
+ end.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+
+tickets(doc) ->
+ ["."];
+tickets(suite) ->
+ [
+ hexed_query_otp_6191,
+ empty_body_otp_6243,
+ empty_response_header_otp_6830,
+ transfer_encoding_otp_6807,
+ proxy_not_modified_otp_6821,
+ no_content_204_otp_6982,
+ missing_CR_otp_7304,
+ otp_7883,
+ otp_8154,
+ otp_8106,
+ otp_8056,
+ otp_8371
+ ].
+
+
+%%-------------------------------------------------------------------------
+
+http_options(doc) ->
+ ["Test http options request against local server."];
+http_options(suite) ->
+ [];
+http_options(Config) when is_list(Config) ->
+ {skip, "Not supported by httpd"}.
+
+http_head(doc) ->
+ ["Test http head request against local server."];
+http_head(suite) ->
+ [];
+http_head(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ case http:request(head, {URL, []}, [], []) of
+ {ok, {{_,200,_}, [_ | _], []}} ->
+ ok;
+ {ok, WrongReply} ->
+ tsf({wrong_reply, WrongReply});
+ Error ->
+ tsf({failed, Error})
+ end;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+%%-------------------------------------------------------------------------
+http_get(doc) ->
+ ["Test http get request against local server"];
+http_get(suite) ->
+ [];
+http_get(Config) when is_list(Config) ->
+ tsp("http_get -> entry with"
+ "~n Config: ~p", [Config]),
+ case ?config(local_server, Config) of
+ ok ->
+ tsp("local-server running"),
+ Method = get,
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ Request = {URL, []},
+ Timeout = timer:seconds(1),
+ ConnTimeout = Timeout + timer:seconds(1),
+ HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}],
+ Options1 = [],
+ Body =
+ case http:request(Method, Request, HttpOptions1, Options1) of
+ {ok, {{_,200,_}, [_ | _], ReplyBody = [_ | _]}} ->
+ ReplyBody;
+ {ok, UnexpectedReply1} ->
+ tsf({unexpected_reply, UnexpectedReply1});
+ {error, _} = Error1 ->
+ tsf({bad_reply, Error1})
+ end,
+
+ %% eqvivivalent to http:request(get, {URL, []}, [], []),
+ inets_test_lib:check_body(Body),
+
+ HttpOptions2 = [],
+ Options2 = [{body_format, binary}],
+ case http:request(Method, Request, HttpOptions2, Options2) of
+ {ok, {{_,200,_}, [_ | _], Bin}} when is_binary(Bin) ->
+ ok;
+ {ok, {{_,200,_}, [_ | _], BadBin}} ->
+ tsf({body_format_not_binary, BadBin});
+ {ok, UnexpectedReply2} ->
+ tsf({unexpected_reply, UnexpectedReply2});
+ {error, _} = Error2 ->
+ tsf({bad_reply, Error2})
+ end;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+%%-------------------------------------------------------------------------
+http_post(doc) ->
+ ["Test http post request against local server. We do in this case"
+ " only care about the client side of the the post. The server"
+ " script will not actually use the post data."];
+http_post(suite) ->
+ [];
+http_post(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+
+ URL = case test_server:os_type() of
+ {win32, _} ->
+ ?URL_START ++ integer_to_list(Port) ++
+ "/cgi-bin/cgi_echo.exe";
+ _ ->
+ ?URL_START ++ integer_to_list(Port) ++
+ "/cgi-bin/cgi_echo"
+
+ end,
+ %% Cgi-script expects the body length to be 100
+ Body = lists:duplicate(100, "1"),
+
+ {ok, {{_,200,_}, [_ | _], [_ | _]}} =
+ http:request(post, {URL, [{"expect","100-continue"}],
+ "text/plain", Body}, [], []),
+
+ {ok, {{_,504,_}, [_ | _], []}} =
+ http:request(post, {URL, [{"expect","100-continue"}],
+ "text/plain", "foobar"}, [], []);
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+%%-------------------------------------------------------------------------
+http_emulate_lower_versions(doc) ->
+ ["Perform request as 0.9 and 1.0 clients."];
+http_emulate_lower_versions(suite) ->
+ [];
+http_emulate_lower_versions(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, Body0} =
+ http:request(get, {URL, []}, [{version, "HTTP/0.9"}], []),
+ inets_test_lib:check_body(Body0),
+ {ok, {{"HTTP/1.0", 200, _}, [_ | _], Body1 = [_ | _]}} =
+ http:request(get, {URL, []}, [{version, "HTTP/1.0"}], []),
+ inets_test_lib:check_body(Body1),
+ {ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} =
+ http:request(get, {URL, []}, [{version, "HTTP/1.1"}], []),
+ inets_test_lib:check_body(Body2);
+ _->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+%%-------------------------------------------------------------------------
+
+http_relaxed(doc) ->
+ ["Test relaxed mode"];
+http_relaxed(suite) ->
+ [];
+http_relaxed(Config) when is_list(Config) ->
+ ok = http:set_options([{ipv6, disabled}]), % also test the old option
+ %% ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++
+ "/missing_reason_phrase.html",
+
+ {error, Reason} =
+ http:request(get, {URL, []}, [{relaxed, false}], []),
+
+ test_server:format("Not relaxed: ~p~n", [Reason]),
+
+ {ok, {{_, 200, _}, [_ | _], [_ | _]}} =
+ http:request(get, {URL, []}, [{relaxed, true}], []),
+
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipv6, enabled}]),
+ %% ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+http_dummy_pipe(doc) ->
+ ["Test pipelining code."];
+http_dummy_pipe(suite) ->
+ [];
+http_dummy_pipe(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html",
+
+ test_pipeline(URL),
+
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+http_inets_pipe(doc) ->
+ ["Test pipelining code."];
+http_inets_pipe(suite) ->
+ [];
+http_inets_pipe(Config) when is_list(Config) ->
+
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ test_pipeline(URL);
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+test_pipeline(URL) ->
+ p("test_pipeline -> entry with"
+ "~n URL: ~p", [URL]),
+
+ http:set_options([{pipeline_timeout, 50000}]),
+
+ p("test_pipeline -> issue (async) request 1"),
+ {ok, RequestId1} =
+ http:request(get, {URL, []}, [], [{sync, false}]),
+ test_server:format("RequestId1: ~p~n", [RequestId1]),
+ p("test_pipeline -> RequestId1: ~p", [RequestId1]),
+
+ %% Make sure pipeline is initiated
+ p("test_pipeline -> sleep some", []),
+ test_server:sleep(4000),
+
+ p("test_pipeline -> issue (async) request 2"),
+ {ok, RequestId2} =
+ http:request(get, {URL, []}, [], [{sync, false}]),
+ tsp("RequestId2: ~p", [RequestId2]),
+ p("test_pipeline -> RequestId2: ~p", [RequestId2]),
+
+ p("test_pipeline -> issue (sync) request 3"),
+ {ok, {{_,200,_}, [_ | _], [_ | _]}} =
+ http:request(get, {URL, []}, [], []),
+
+ p("test_pipeline -> expect reply for (async) request 1 or 2"),
+ receive
+ {http, {RequestId1, {{_, 200, _}, _, _}}} ->
+ p("test_pipeline -> received reply for (async) request 1 - now wait for 2"),
+ receive
+ {http, {RequestId2, {{_, 200, _}, _, _}}} ->
+ p("test_pipeline -> received reply for (async) request 2"),
+ ok;
+ {http, Msg1} ->
+ test_server:fail(Msg1)
+ end;
+ {http, {RequestId2, {{_, 200, _}, _, _}}} ->
+ io:format("test_pipeline -> received reply for (async) request 2 - now wait for 1"),
+ receive
+ {http, {RequestId1, {{_, 200, _}, _, _}}} ->
+ io:format("test_pipeline -> received reply for (async) request 1"),
+ ok;
+ {http, Msg2} ->
+ test_server:fail(Msg2)
+ end;
+ {http, Msg3} ->
+ test_server:fail(Msg3)
+ after 60000 ->
+ receive Any1 ->
+ tsp("received crap after timeout: ~n ~p", [Any1]),
+ test_server:fail({error, {timeout, Any1}})
+ end
+ end,
+
+ p("test_pipeline -> sleep some"),
+ test_server:sleep(4000),
+
+ p("test_pipeline -> issue (async) request 4"),
+ {ok, RequestId3} =
+ http:request(get, {URL, []}, [], [{sync, false}]),
+ tsp("RequestId3: ~p", [RequestId3]),
+ p("test_pipeline -> RequestId3: ~p", [RequestId3]),
+
+ p("test_pipeline -> issue (async) request 5"),
+ {ok, RequestId4} =
+ http:request(get, {URL, []}, [], [{sync, false}]),
+ tsp("RequestId4: ~p~n", [RequestId4]),
+ p("test_pipeline -> RequestId4: ~p", [RequestId4]),
+
+ p("test_pipeline -> cancel (async) request 4"),
+ ok = http:cancel_request(RequestId3),
+
+ p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"),
+ receive
+ {http, {RequestId3, _}} ->
+ test_server:fail(http_cancel_request_failed)
+ after 3000 ->
+ ok
+ end,
+
+ p("test_pipeline -> expect reply for (async) request 4"),
+ Body =
+ receive
+ {http, {RequestId4, {{_, 200, _}, _, BinBody4}}} = Res ->
+ p("test_pipeline -> received reply for (async) request 5"),
+ tsp("Receive : ~p", [Res]),
+ BinBody4;
+ {http, Msg4} ->
+ test_server:fail(Msg4)
+ after 60000 ->
+ receive Any2 ->
+ tsp("received crap after timeout: ~n ~p", [Any2]),
+ test_server:fail({error, {timeout, Any2}})
+ end
+ end,
+
+ p("test_pipeline -> check reply for (async) request 5"),
+ inets_test_lib:check_body(binary_to_list(Body)),
+
+ p("test_pipeline -> ensure no unexpected incomming"),
+ receive
+ {http, Any} ->
+ test_server:fail({unexpected_message, Any})
+ after 500 ->
+ ok
+ end,
+
+ p("test_pipeline -> done"),
+ ok.
+
+
+
+%%-------------------------------------------------------------------------
+http_trace(doc) ->
+ ["Perform a TRACE request that goes through a proxy."];
+http_trace(suite) ->
+ [];
+http_trace(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ case http:request(trace, {URL, []}, [], []) of
+ {ok, {{_,200,_}, [_ | _], "TRACE /dummy.html" ++ _}} ->
+ ok;
+ {ok, {{_,200,_}, [_ | _], WrongBody}} ->
+ test_server:fail({wrong_body, WrongBody});
+ {ok, WrongReply} ->
+ test_server:fail({wrong_reply, WrongReply});
+ Error ->
+ test_server:fail({failed, Error})
+ end;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+%%-------------------------------------------------------------------------
+http_async(doc) ->
+ ["Test an asynchrony http request."];
+http_async(suite) ->
+ [];
+http_async(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, RequestId} =
+ http:request(get, {URL, []}, [], [{sync, false}]),
+
+ Body =
+ receive
+ {http, {RequestId, {{_, 200, _}, _, BinBody}}} ->
+ BinBody;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end,
+
+ inets_test_lib:check_body(binary_to_list(Body)),
+
+ {ok, NewRequestId} =
+ http:request(get, {URL, []}, [], [{sync, false}]),
+ ok = http:cancel_request(NewRequestId),
+ receive
+ {http, {NewRequestId, _NewResult}} ->
+ test_server:fail(http_cancel_request_failed)
+ after 3000 ->
+ ok
+ end;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+%%-------------------------------------------------------------------------
+http_save_to_file(doc) ->
+ ["Test to save the http body to a file"];
+http_save_to_file(suite) ->
+ [];
+http_save_to_file(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ PrivDir = ?config(priv_dir, Config),
+ FilePath = filename:join(PrivDir, "dummy.html"),
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, saved_to_file}
+ = http:request(get, {URL, []}, [], [{stream, FilePath}]),
+ {ok, Bin} = file:read_file(FilePath),
+ {ok, {{_,200,_}, [_ | _], Body}} = http:request(URL),
+ Bin == Body;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+%%-------------------------------------------------------------------------
+http_save_to_file_async(doc) ->
+ ["Test to save the http body to a file"];
+http_save_to_file_async(suite) ->
+ [];
+http_save_to_file_async(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ PrivDir = ?config(priv_dir, Config),
+ FilePath = filename:join(PrivDir, "dummy.html"),
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, RequestId} = http:request(get, {URL, []}, [],
+ [{stream, FilePath},
+ {sync, false}]),
+ receive
+ {http, {RequestId, saved_to_file}} ->
+ ok;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end,
+
+ {ok, Bin} = file:read_file(FilePath),
+ {ok, {{_,200,_}, [_ | _], Body}} = http:request(URL),
+ Bin == Body;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+%%-------------------------------------------------------------------------
+http_headers(doc) ->
+ ["Use as many request headers as possible not used in proxy_headers"];
+http_headers(suite) ->
+ [];
+http_headers(Config) when is_list(Config) ->
+
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ DocRoot = ?config(doc_root, Config),
+ {ok, FileInfo} =
+ file:read_file_info(filename:join([DocRoot,"dummy.html"])),
+ CreatedSec =
+ calendar:datetime_to_gregorian_seconds(
+ FileInfo#file_info.mtime),
+
+ Mod = httpd_util:rfc1123_date(
+ calendar:gregorian_seconds_to_datetime(
+ CreatedSec-1)),
+
+ Date = httpd_util:rfc1123_date({date(), time()}),
+
+ {ok, {{_,200,_}, [_ | _], [_ | _]}} =
+ http:request(get, {URL, [{"If-Modified-Since",
+ Mod},
+ {"From","[email protected]"},
+ {"Date", Date}
+ ]}, [], []),
+
+ Mod1 = httpd_util:rfc1123_date(
+ calendar:gregorian_seconds_to_datetime(
+ CreatedSec+1)),
+
+ {ok, {{_,200,_}, [_ | _], [_ | _]}} =
+ http:request(get, {URL, [{"If-UnModified-Since",
+ Mod1}
+ ]}, [], []),
+
+ Tag = httpd_util:create_etag(FileInfo),
+
+
+ {ok, {{_,200,_}, [_ | _], [_ | _]}} =
+ http:request(get, {URL, [{"If-Match",
+ Tag}
+ ]}, [], []),
+
+ {ok, {{_,200,_}, [_ | _], _}} =
+ http:request(get, {URL, [{"If-None-Match",
+ "NotEtag,NeihterEtag"},
+ {"Connection", "Close"}
+ ]}, [], []),
+ ok;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+%%-------------------------------------------------------------------------
+http_headers_dummy(doc) ->
+ ["Test the code for handling headers we do not want/can send "
+ "to a real server. Note it is not logical to send"
+ "all of these headers together, we only want to test that"
+ "the code for handling headers will not crash."];
+http_headers_dummy(suite) ->
+ [];
+http_headers_dummy(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html",
+
+ Foo = http_chunk:encode("foobar") ++
+ binary_to_list(http_chunk:encode_last()),
+ FooBar = Foo ++ "\r\n\r\nOther:inets_test\r\n\r\n",
+
+ UserPasswd = base64:encode_to_string("Alladin:Sesame"),
+ Auth = "Basic " ++ UserPasswd,
+
+ %% The dummy server will ignore the headers, we only want to test
+ %% that the client header-handling code. This would not
+ %% be a vaild http-request!
+ {ok, {{_,200,_}, [_ | _], [_|_]}} =
+ http:request(post,
+ {URL,
+ [{"Via",
+ "1.0 fred, 1.1 nowhere.com (Apache/1.1)"},
+ {"Warning","1#pseudonym foobar"},
+ {"Vary","*"},
+ {"Upgrade","HTTP/2.0"},
+ {"Pragma", "1#no-cache"},
+ {"Cache-Control", "no-cache"},
+ {"Connection", "close"},
+ {"Date", "Sat, 29 Oct 1994 19:43:31 GMT"},
+ {"Accept", " text/plain; q=0.5, text/html"},
+ {"Accept-Language", "en"},
+ {"Accept-Encoding","chunked"},
+ {"Accept-Charset", "ISO8859-1"},
+ {"Authorization", Auth},
+ {"Expect", "1#100-continue"},
+ {"User-Agent","inets"},
+ {"Transfer-Encoding","chunked"},
+ {"Range", " bytes=0-499"},
+ {"If-Range", "Sat, 29 Oct 1994 19:43:31 GMT"},
+ {"If-Match", "*"},
+ {"Content-Type", "text/plain"},
+ {"Content-Encoding", "chunked"},
+ {"Content-Length", "6"},
+ {"Content-Language", "en"},
+ {"Content-Location", "http://www.foobar.se"},
+ {"Content-MD5",
+ "104528739076276072743283077410617235478"},
+ {"Content-Range", "bytes 0-499/1234"},
+ {"Allow", "GET"},
+ {"Proxy-Authorization", Auth},
+ {"Expires", "Sat, 29 Oct 1994 19:43:31 GMT"},
+ {"Upgrade", "HTTP/2.0"},
+ {"Last-Modified", "Sat, 29 Oct 1994 19:43:31 GMT"},
+ {"Trailer","1#User-Agent"}
+ ], "text/plain", FooBar},
+ [], []),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+http_bad_response(doc) ->
+ ["Test what happens when the server does not follow the protocol"];
+http_bad_response(suite) ->
+ [];
+http_bad_response(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html",
+
+ URL1 = ?URL_START ++ integer_to_list(Port) ++ "/wrong_statusline.html",
+
+ {error, timeout} = http:request(get, {URL, []}, [{timeout, 400}], []),
+
+ {error, Reason} = http:request(URL1),
+
+ test_server:format("Wrong Statusline: ~p~n", [Reason]),
+
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+ssl_head(doc) ->
+ ["Same as http_head/1 but over ssl sockets."];
+ssl_head(suite) ->
+ [];
+ssl_head(Config) when is_list(Config) ->
+ case ?config(local_ssl_server, Config) of
+ ok ->
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(local_ssl_port, Config),
+ URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
+ SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
+ {ok, {{_,200, _}, [_ | _], []}} =
+ http:request(head, {URL, []}, [{ssl, SSLOptions}], []);
+ {ok, _} ->
+ {skip, "Failed to start local http-server"};
+ _ ->
+ {skip, "Failed to start SSL"}
+ end.
+%%-------------------------------------------------------------------------
+ssl_get(doc) ->
+ ["Same as http_get/1 but over ssl sockets."];
+ssl_get(suite) ->
+ [];
+ssl_get(Config) when is_list(Config) ->
+ case ?config(local_ssl_server, Config) of
+ ok ->
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(local_ssl_port, Config),
+ URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
+ SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
+ {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} =
+ http:request(get, {URL, []}, [{ssl, SSLOptions}], []),
+ inets_test_lib:check_body(Body);
+ {ok, _} ->
+ {skip, "Failed to start local http-server"};
+ _ ->
+ {skip, "Failed to start SSL"}
+ end.
+%%-------------------------------------------------------------------------
+ssl_trace(doc) ->
+ ["Same as http_trace/1 but over ssl sockets."];
+ssl_trace(suite) ->
+ [];
+ssl_trace(Config) when is_list(Config) ->
+ case ?config(local_ssl_server, Config) of
+ ok ->
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(local_ssl_port, Config),
+ URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
+ SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
+ case http:request(trace, {URL, []}, [{ssl, SSLOptions}], []) of
+ {ok, {{_,200, _}, [_ | _], "TRACE /dummy.html" ++ _}} ->
+ ok;
+ {ok, {{_,200,_}, [_ | _], WrongBody}} ->
+ test_server:fail({wrong_body, WrongBody});
+ {ok, WrongReply} ->
+ test_server:fail({wrong_reply, WrongReply});
+ Error ->
+ test_server:fail({failed, Error})
+ end;
+ {ok, _} ->
+ {skip, "Failed to start local http-server"};
+ _ ->
+ {skip, "Failed to start SSL"}
+ end.
+%%-------------------------------------------------------------------------
+http_redirect(doc) ->
+ ["Test redirect with dummy server as httpd does not implement"
+ " server redirect"];
+http_redirect(suite) ->
+ [];
+http_redirect(Config) when is_list(Config) ->
+ tsp("http_redirect -> entry with"
+ "~n Config: ~p", [Config]),
+ case ?config(local_server, Config) of
+ ok ->
+ tsp("http_redirect -> set ipfamily option to inet"),
+ ok = http:set_options([{ipfamily, inet}]),
+
+ tsp("http_redirect -> start dummy server inet"),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+ tsp("http_redirect -> server port = ~p", [Port]),
+
+ URL300 = ?URL_START ++ integer_to_list(Port) ++ "/300.html",
+
+ tsp("http_redirect -> issue request 1: "
+ "~n ~p", [URL300]),
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = http:request(get, {URL300, []}, [], []),
+
+ tsp("http_redirect -> issue request 2: "
+ "~n ~p", [URL300]),
+ {ok, {{_,300,_}, [_ | _], _}} =
+ http:request(get, {URL300, []}, [{autoredirect, false}], []),
+
+ URL301 = ?URL_START ++ integer_to_list(Port) ++ "/301.html",
+
+ tsp("http_redirect -> issue request 3: "
+ "~n ~p", [URL301]),
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = http:request(get, {URL301, []}, [], []),
+
+ tsp("http_redirect -> issue request 4: "
+ "~n ~p", [URL301]),
+ {ok, {{_,200,_}, [_ | _], []}}
+ = http:request(head, {URL301, []}, [], []),
+
+ tsp("http_redirect -> issue request 5: "
+ "~n ~p", [URL301]),
+ {ok, {{_,301,_}, [_ | _], [_|_]}}
+ = http:request(post, {URL301, [],"text/plain", "foobar"},
+ [], []),
+
+ URL302 = ?URL_START ++ integer_to_list(Port) ++ "/302.html",
+
+ tsp("http_redirect -> issue request 6: "
+ "~n ~p", [URL302]),
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = http:request(get, {URL302, []}, [], []),
+
+ tsp("http_redirect -> issue request 7: "
+ "~n ~p", [URL302]),
+ {ok, {{_,200,_}, [_ | _], []}}
+ = http:request(head, {URL302, []}, [], []),
+
+ tsp("http_redirect -> issue request 8: "
+ "~n ~p", [URL302]),
+ {ok, {{_,302,_}, [_ | _], [_|_]}}
+ = http:request(post, {URL302, [],"text/plain", "foobar"},
+ [], []),
+
+ URL307 = ?URL_START ++ integer_to_list(Port) ++ "/307.html",
+
+ tsp("http_redirect -> issue request 9: "
+ "~n ~p", [URL307]),
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = http:request(get, {URL307, []}, [], []),
+
+ tsp("http_redirect -> issue request 10: "
+ "~n ~p", [URL307]),
+ {ok, {{_,200,_}, [_ | _], []}}
+ = http:request(head, {URL307, []}, [], []),
+
+ tsp("http_redirect -> issue request 11: "
+ "~n ~p", [URL307]),
+ {ok, {{_,307,_}, [_ | _], [_|_]}}
+ = http:request(post, {URL307, [],"text/plain", "foobar"},
+ [], []),
+
+ tsp("http_redirect -> stop dummy server"),
+ DummyServerPid ! stop,
+ tsp("http_redirect -> reset ipfamily option (to inet6fb4)"),
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ tsp("http_redirect -> done"),
+ ok;
+
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+%%-------------------------------------------------------------------------
+http_redirect_loop(doc) ->
+ ["Test redirect loop detection"];
+http_redirect_loop(suite) ->
+ [];
+http_redirect_loop(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html",
+
+ {ok, {{_,300,_}, [_ | _], _}}
+ = http:request(get, {URL, []}, [], []),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+%%-------------------------------------------------------------------------
+http_internal_server_error(doc) ->
+ ["Test 50X codes"];
+http_internal_server_error(suite) ->
+ [];
+http_internal_server_error(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html",
+
+ {ok, {{_,500,_}, [_ | _], _}}
+ = http:request(get, {URL500, []}, [], []),
+
+
+ URL503 = ?URL_START ++ integer_to_list(Port) ++ "/503.html",
+
+ %% Used to be able to make the service available after retry.
+ ets:new(unavailable, [named_table, public, set]),
+ ets:insert(unavailable, {503, unavailable}),
+
+ {ok, {{_,200, _}, [_ | _], [_|_]}} =
+ http:request(get, {URL503, []}, [], []),
+
+ ets:insert(unavailable, {503, long_unavailable}),
+
+ {ok, {{_,503, _}, [_ | _], [_|_]}} =
+ http:request(get, {URL503, []}, [], []),
+
+ ets:delete(unavailable),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+http_userinfo(doc) ->
+ ["Test user info e.i. http://user:passwd@host:port/"];
+http_userinfo(suite) ->
+ [];
+http_userinfo(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URLAuth = "http://alladin:sesame@localhost:"
+ ++ integer_to_list(Port) ++ "/userinfo.html",
+
+ {ok, {{_,200,_}, [_ | _], _}}
+ = http:request(get, {URLAuth, []}, [], []),
+
+ URLUnAuth = "http://alladin:foobar@localhost:"
+ ++ integer_to_list(Port) ++ "/userinfo.html",
+
+ {ok, {{_,401, _}, [_ | _], _}} =
+ http:request(get, {URLUnAuth, []}, [], []),
+
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+http_cookie(doc) ->
+ ["Test cookies."];
+http_cookie(suite) ->
+ [];
+http_cookie(Config) when is_list(Config) ->
+ ok = http:set_options([{cookies, enabled}, {ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URLStart = ?URL_START
+ ++ integer_to_list(Port),
+
+ URLCookie = URLStart ++ "/cookie.html",
+
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = http:request(get, {URLCookie, []}, [], []),
+
+ ets:new(cookie, [named_table, public, set]),
+ ets:insert(cookie, {cookies, true}),
+
+ {ok, {{_,200,_}, [_ | _], [_|_]}}
+ = http:request(get, {URLStart ++ "/", []}, [], []),
+
+ ets:delete(cookie),
+
+ ok = http:set_options([{cookies, disabled}, {ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6************
+ ok.
+
+%%-------------------------------------------------------------------------
+proxy_options(doc) ->
+ ["Perform a OPTIONS request that goes through a proxy."];
+proxy_options(suite) ->
+ [];
+proxy_options(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ case http:request(options, {?PROXY_URL, []}, [], []) of
+ {ok, {{_,200,_}, Headers, _}} ->
+ case lists:keysearch("allow", 1, Headers) of
+ {value, {"allow", _}} ->
+ ok;
+ _ ->
+ test_server:fail(http_options_request_failed)
+ end;
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+proxy_head(doc) ->
+ ["Perform a HEAD request that goes through a proxy."];
+proxy_head(suite) ->
+ [];
+proxy_head(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ case http:request(head, {?PROXY_URL, []}, [], []) of
+ {ok, {{_,200, _}, [_ | _], []}} ->
+ ok;
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+proxy_get(doc) ->
+ ["Perform a GET request that goes through a proxy."];
+proxy_get(suite) ->
+ [];
+proxy_get(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ case http:request(get, {?PROXY_URL, []}, [], []) of
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} ->
+ inets_test_lib:check_body(Body);
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+%%-------------------------------------------------------------------------
+proxy_emulate_lower_versions(doc) ->
+ ["Perform requests as 0.9 and 1.0 clients."];
+proxy_emulate_lower_versions(suite) ->
+ [];
+proxy_emulate_lower_versions(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ {ok, Body0 = [_| _]} = http:request(get, {?PROXY_URL, []},
+ [{version, "HTTP/0.9"}], []),
+ inets_test_lib:check_body(Body0),
+
+ %% We do not check the version here as many servers
+ %% do not behave according to the rfc and send
+ %% 1.1 in its response.
+ {ok,{{_, 200, _}, [_ | _], Body1 = [_ | _]}} =
+ http:request(get, {?PROXY_URL, []},
+ [{version, "HTTP/1.0"}], []),
+ inets_test_lib:check_body(Body1),
+
+ {ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} =
+ http:request(get, {?PROXY_URL, []},
+ [{version, "HTTP/1.1"}], []),
+ inets_test_lib:check_body(Body2);
+ Reason ->
+ {skip, Reason}
+ end.
+
+%%-------------------------------------------------------------------------
+proxy_trace(doc) ->
+ ["Perform a TRACE request that goes through a proxy."];
+proxy_trace(suite) ->
+ [];
+proxy_trace(Config) when is_list(Config) ->
+ %%{ok, {{_,200,_}, [_ | _], "TRACE " ++ _}} =
+ %% http:request(trace, {?PROXY_URL, []}, [], []),
+ {skip, "HTTP TRACE is no longer allowed on the ?PROXY_URL server due "
+ "to security reasons"}.
+
+
+%%-------------------------------------------------------------------------
+proxy_post(doc) ->
+ ["Perform a POST request that goes through a proxy. Note the server"
+ " will reject the request this is a test of the sending of the"
+ " request."];
+proxy_post(suite) ->
+ [];
+proxy_post(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ case http:request(post, {?PROXY_URL, [],
+ "text/plain", "foobar"}, [],[]) of
+ {ok, {{_,405,_}, [_ | _], [_ | _]}} ->
+ ok;
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+proxy_put(doc) ->
+ ["Perform a PUT request that goes through a proxy. Note the server"
+ " will reject the request this is a test of the sending of the"
+ " request."];
+proxy_put(suite) ->
+ [];
+proxy_put(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ case http:request(put, {"http://www.erlang.org/foobar.html", [],
+ "html", "<html> <body><h1> foo </h1>"
+ "<p>bar</p> </body></html>"}, [], []) of
+ {ok, {{_,405,_}, [_ | _], [_ | _]}} ->
+ ok;
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+proxy_delete(doc) ->
+ ["Perform a DELETE request that goes through a proxy. Note the server"
+ " will reject the request this is a test of the sending of the"
+ " request. But as the file does not exist the return code will"
+ " be 404 not found."];
+proxy_delete(suite) ->
+ [];
+proxy_delete(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ URL = ?PROXY_URL ++ "/foobar.html",
+ case http:request(delete, {URL, []}, [], []) of
+ {ok, {{_,404,_}, [_ | _], [_ | _]}} ->
+ ok;
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+proxy_headers(doc) ->
+ ["Use as many request headers as possible"];
+proxy_headers(suite) ->
+ [];
+proxy_headers(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ {ok, {{_,200,_}, [_ | _], [_ | _]}}
+ = http:request(get, {?PROXY_URL,
+ [
+ {"Accept",
+ "text/*, text/html,"
+ " text/html;level=1,"
+ " */*"},
+ {"Accept-Charset",
+ "iso-8859-5, unicode-1-1;"
+ "q=0.8"},
+ {"Accept-Encoding", "*"},
+ {"Accept-Language",
+ "sv, en-gb;q=0.8,"
+ " en;q=0.7"},
+ {"User-Agent", "inets"},
+ {"Max-Forwards","5"},
+ {"Referer",
+ "http://otp.ericsson.se:8000"
+ "/product/internal"}
+ ]}, [], []),
+ ok;
+ Reason ->
+ {skip, Reason}
+ end.
+
+%%-------------------------------------------------------------------------
+proxy_auth(doc) ->
+ ["Test the code for sending of proxy authorization."];
+proxy_auth(suite) ->
+ [];
+proxy_auth(Config) when is_list(Config) ->
+ %% Our proxy seems to ignore the header, however our proxy
+ %% does not requirer an auth header, but we want to know
+ %% atleast the code for sending the header does not crash!
+ case ?config(skip, Config) of
+ undefined ->
+ case http:request(get, {?PROXY_URL, []},
+ [{proxy_auth, {"foo", "bar"}}], []) of
+ {ok, {{_,200, _}, [_ | _], [_|_]}} ->
+ ok;
+ Unexpected ->
+ test_server:fail({unexpected_result, Unexpected})
+ end;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+http_server_does_not_exist(doc) ->
+ ["Test that we get an error message back when the server "
+ "does note exist."];
+http_server_does_not_exist(suite) ->
+ [];
+http_server_does_not_exist(Config) when is_list(Config) ->
+ {error, _} =
+ http:request(get, {"http://localhost:" ++
+ integer_to_list(?NOT_IN_USE_PORT)
+ ++ "/", []},[], []),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+page_does_not_exist(doc) ->
+ ["Test that we get a 404 when the page is not found."];
+page_does_not_exist(suite) ->
+ [];
+page_does_not_exist(Config) when is_list(Config) ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/doesnotexist.html",
+ {ok, {{_,404,_}, [_ | _], [_ | _]}}
+ = http:request(get, {URL, []}, [], []),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+proxy_page_does_not_exist(doc) ->
+ ["Test that we get a 404 when the page is not found."];
+proxy_page_does_not_exist(suite) ->
+ [];
+proxy_page_does_not_exist(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ URL = ?PROXY_URL ++ "/doesnotexist.html",
+ {ok, {{_,404,_}, [_ | _], [_ | _]}} =
+ http:request(get, {URL, []}, [], []),
+ ok;
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+proxy_https_not_supported(doc) ->
+ [];
+proxy_https_not_supported(suite) ->
+ [];
+proxy_https_not_supported(Config) when is_list(Config) ->
+ {error, {failed_connecting, https_through_proxy_is_not_currently_supported}} =
+ http:request(get, {"https://login.yahoo.com", []}, [], []),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+http_stream(doc) ->
+ ["Test the option stream for asynchrony requests"];
+http_stream(suite) ->
+ [];
+http_stream(Config) when is_list(Config) ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, {{_,200,_}, [_ | _], Body}} =
+ http:request(get, {URL, []}, [], []),
+
+ {ok, RequestId} =
+ http:request(get, {URL, []}, [], [{sync, false},
+ {stream, self}]),
+
+ receive
+ {http, {RequestId, stream_start, _Headers}} ->
+ ok;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end,
+
+ StreamedBody = receive_streamed_body(RequestId, <<>>),
+
+ Body == binary_to_list(StreamedBody).
+
+
+%%-------------------------------------------------------------------------
+http_stream_once(doc) ->
+ ["Test the option stream for asynchrony requests"];
+http_stream_once(suite) ->
+ [];
+http_stream_once(Config) when is_list(Config) ->
+ p("http_stream_once -> entry with"
+ "~n Config: ~p", [Config]),
+
+ p("http_stream_once -> set ipfamily to inet", []),
+ ok = http:set_options([{ipfamily, inet}]),
+ p("http_stream_once -> start dummy server", []),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ PortStr = integer_to_list(Port),
+ p("http_stream_once -> once", []),
+ once(?URL_START ++ PortStr ++ "/once.html"),
+ p("http_stream_once -> once_chunked", []),
+ once(?URL_START ++ PortStr ++ "/once_chunked.html"),
+ p("http_stream_once -> dummy", []),
+ once(?URL_START ++ PortStr ++ "/dummy.html"),
+
+ p("http_stream_once -> stop dummy server", []),
+ DummyServerPid ! stop,
+ p("http_stream_once -> set ipfamily to inet6fb4", []),
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ p("http_stream_once -> done", []),
+ ok.
+
+once(URL) ->
+ p("once -> issue sync request for ~p", [URL]),
+ {ok, {{_,200,_}, [_ | _], Body}} =
+ http:request(get, {URL, []}, [], []),
+
+ p("once -> issue async (self stream) request for ~p", [URL]),
+ {ok, RequestId} =
+ http:request(get, {URL, []}, [], [{sync, false},
+ {stream, {self, once}}]),
+
+ p("once -> await stream_start reply for (async) request ~p", [RequestId]),
+ NewPid =
+ receive
+ {http, {RequestId, stream_start, _Headers, Pid}} ->
+ p("once -> received stream_start reply for (async) request ~p: ~p",
+ [RequestId, Pid]),
+ Pid;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end,
+
+ tsp("once -> request handler: ~p", [NewPid]),
+
+ p("once -> await stream reply for (async) request ~p", [RequestId]),
+ BodyPart =
+ receive
+ {http, {RequestId, stream, BinBodyPart}} ->
+ p("once -> received stream reply for (async) request ~p: "
+ "~n~p", [RequestId, binary_to_list(BinBodyPart)]),
+ BinBodyPart
+ end,
+
+ tsp("once -> first body part '~p' received", [binary_to_list(BodyPart)]),
+
+ StreamedBody = receive_streamed_body(RequestId, BinBodyPart, NewPid),
+
+ Body = binary_to_list(StreamedBody),
+
+ p("once -> done when Bode: ~p", [Body]),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+proxy_stream(doc) ->
+ ["Test the option stream for asynchrony requests"];
+proxy_stream(suite) ->
+ [];
+proxy_stream(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ {ok, {{_,200,_}, [_ | _], Body}} =
+ http:request(get, {?PROXY_URL, []}, [], []),
+
+ {ok, RequestId} =
+ http:request(get, {?PROXY_URL, []}, [],
+ [{sync, false}, {stream, self}]),
+
+ receive
+ {http, {RequestId, stream_start, _Headers}} ->
+ ok;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end,
+
+ StreamedBody = receive_streamed_body(RequestId, <<>>),
+
+ Body == binary_to_list(StreamedBody);
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+parse_url(doc) ->
+ ["Test that an url is parsed correctly"];
+parse_url(suite) ->
+ [];
+parse_url(Config) when is_list(Config) ->
+ %% ipv6
+ {http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}
+ = http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html"),
+ {error,
+ {malformed_url,"http://2010:836B:4179::836B:4179/foobar.html"}} =
+ http_uri:parse("http://2010:836B:4179::836B:4179/foobar.html"),
+
+ %% ipv4
+ {http,[],"127.0.0.1",80,"/foobar.html",[]} =
+ http_uri:parse("http://127.0.0.1/foobar.html"),
+
+ %% host
+ {http,[],"localhost",8888,"/foobar.html",[]} =
+ http_uri:parse("http://localhost:8888/foobar.html"),
+
+ %% Userinfo
+ {http,"nisse:foobar","localhost",8888,"/foobar.html",[]} =
+ http_uri:parse("http://nisse:foobar@localhost:8888/foobar.html"),
+
+ %% Scheme error
+ {error,no_scheme} = http_uri:parse("localhost/foobar.html"),
+ {error,{not_supported_scheme,localhost}} =
+ http_uri:parse("localhost:8888/foobar.html"),
+
+ %% Query
+ {http,[],"localhost",8888,"/foobar.html","?foo=bar&foobar=42"} =
+ http_uri:parse("http://localhost:8888/foobar.html?foo=bar&foobar=42"),
+
+ %% Esc chars
+ {http,[],"www.somedomain.com",80,"/%2Eabc",[]} =
+ http_uri:parse("http://www.somedomain.com/%2Eabc"),
+ {http,[],"www.somedomain.com",80,"/%252Eabc",[]} =
+ http_uri:parse("http://www.somedomain.com/%252Eabc"),
+ {http,[],"www.somedomain.com",80,"/%25abc",[]} =
+ http_uri:parse("http://www.somedomain.com/%25abc"),
+ {http,[],"www.somedomain.com",80,"/%25abc", "?foo=bar"} =
+ http_uri:parse("http://www.somedomain.com/%25abc?foo=bar"),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+ipv6(doc) ->
+ ["Test ipv6."];
+ipv6(suite) ->
+ [];
+ipv6(Config) when is_list(Config) ->
+ {ok, Hostname} = inet:gethostname(),
+
+ case lists:member(list_to_atom(Hostname),
+ ?config(ipv6_hosts, Config)) of
+ true ->
+ {DummyServerPid, Port} = dummy_server(self(), ipv6),
+
+ URL = "http://[" ++ ?IPV6_LOCAL_HOST ++ "]:" ++
+ integer_to_list(Port) ++ "/foobar.html",
+ {ok, {{_,200,_}, [_ | _], [_|_]}} =
+ http:request(get, {URL, []}, [], []),
+
+ DummyServerPid ! stop,
+ ok;
+ false ->
+ {skip, "Host does not support IPv6"}
+ end.
+
+
+%%-------------------------------------------------------------------------
+headers_as_is(doc) ->
+ ["Test the option headers_as_is"];
+headers_as_is(suite) ->
+ [];
+headers_as_is(Config) when is_list(Config) ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, {{_,200,_}, [_|_], [_|_]}} =
+ http:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]},
+ [], [{headers_as_is, true}]),
+
+ {ok, {{_,400,_}, [_|_], [_|_]}} =
+ http:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+options(doc) ->
+ ["Test the option parameters."];
+options(suite) ->
+ [];
+options(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ {ok, {{_,200,_}, [_ | _], Bin}}
+ = http:request(get, {URL, []}, [{foo, bar}],
+ %% Ignore unknown options
+ [{body_format, binary}, {foo, bar}]),
+
+ true = is_binary(Bin),
+ {ok, {200, [_|_]}}
+ = http:request(get, {URL, []}, [{timeout, infinity}],
+ [{full_result, false}]);
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+%%-------------------------------------------------------------------------
+http_invalid_http(doc) ->
+ ["Test parse error"];
+http_invalid_http(suite) ->
+ [];
+http_invalid_http(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html",
+
+ {error, {could_not_parse_as_http, _} = Reason} =
+ http:request(get, {URL, []}, [], []),
+
+ test_server:format("Parse error: ~p ~n", [Reason]),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+hexed_query_otp_6191(doc) ->
+ [];
+hexed_query_otp_6191(suite) ->
+ [];
+hexed_query_otp_6191(Config) when is_list(Config) ->
+ Google = "www.google.com",
+ GoogleSearch = "http://" ++ Google ++ "/search",
+ Search1 = "?hl=en&q=a%D1%85%D1%83%D0%B9&btnG=Google+Search",
+ URI1 = GoogleSearch ++ Search1,
+ Search2 = "?hl=en&q=%25%25",
+ URI2 = GoogleSearch ++ Search2,
+ Search3 = "?hl=en&q=%foo",
+ URI3 = GoogleSearch ++ Search3,
+
+ {http, [], Google, 80, "/search", _} = http_uri:parse(URI1),
+ {http, [], Google, 80, "/search", _} = http_uri:parse(URI2),
+ {http, [], Google, 80, "/search", _} = http_uri:parse(URI3),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+empty_body_otp_6243(doc) ->
+ ["An empty body was not returned directly. There was a delay for several"
+ "seconds."];
+empty_body_otp_6243(suite) ->
+ [];
+empty_body_otp_6243(Config) when is_list(Config) ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/empty.html",
+ {ok, {{_,200,_}, [_ | _], []}} =
+ http:request(get, {URL, []}, [{timeout, 500}], []).
+
+
+%%-------------------------------------------------------------------------
+
+transfer_encoding_otp_6807(doc) ->
+ ["Transfer encoding is case insensitive"];
+transfer_encoding_otp_6807(suite) ->
+ [];
+transfer_encoding_otp_6807(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++
+ "/capital_transfer_encoding.html",
+ {ok, {{_,200,_}, [_|_], [_ | _]}} = http:request(URL),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+proxy_not_modified_otp_6821(doc) ->
+ ["If unmodified no body should be returned"];
+proxy_not_modified_otp_6821(suite) ->
+ [];
+proxy_not_modified_otp_6821(Config) when is_list(Config) ->
+ case ?config(skip, Config) of
+ undefined ->
+ provocate_not_modified_bug(?PROXY_URL);
+ Reason ->
+ {skip, Reason}
+ end.
+
+
+%%-------------------------------------------------------------------------
+
+empty_response_header_otp_6830(doc) ->
+ ["Test the case that the HTTP server does not send any headers"];
+empty_response_header_otp_6830(suite) ->
+ [];
+empty_response_header_otp_6830(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html",
+ {ok, {{_,200,_}, [], [_ | _]}} = http:request(URL),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+no_content_204_otp_6982(doc) ->
+ ["Test the case that the HTTP 204 no content header"];
+no_content_204_otp_6982(suite) ->
+ [];
+no_content_204_otp_6982(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html",
+ {ok, {{_,204,_}, [], []}} = http:request(URL),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+missing_CR_otp_7304(doc) ->
+ ["Test the case that the HTTP server uses only LF instead of CRLF"
+ "as delimitor"];
+missing_CR_otp_7304(suite) ->
+ [];
+missing_CR_otp_7304(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html",
+ {ok, {{_,200,_}, _, [_ | _]}} = http:request(URL),
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+otp_7883(suite) ->
+ [otp_7883_1, otp_7883_2].
+
+otp_7883_1(doc) ->
+ ["OTP-7883-sync"];
+otp_7883_1(suite) ->
+ [];
+otp_7883_1(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html",
+ {error, socket_closed_remotely} = http:request(URL),
+ DummyServerPid ! stop,
+
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+otp_7883_2(doc) ->
+ ["OTP-7883-async"];
+otp_7883_2(suite) ->
+ [];
+otp_7883_2(Config) when is_list(Config) ->
+ ok = http:set_options([{ipfamily, inet}]),
+
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html",
+ Method = get,
+ Request = {URL, []},
+ HttpOptions = [],
+ Options = [{sync, false}],
+ Profile = http:default_profile(),
+ {ok, RequestId} =
+ http:request(Method, Request, HttpOptions, Options, Profile),
+ ok =
+ receive
+ {http, {RequestId, {error, socket_closed_remotely}}} ->
+ ok
+ end,
+ DummyServerPid ! stop,
+
+ ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+otp_8154(suite) ->
+ [otp_8154_1].
+
+otp_8154_1(doc) ->
+ ["OTP-8154"];
+otp_8154_1(suite) ->
+ [];
+otp_8154_1(Config) when is_list(Config) ->
+ start_inets(),
+ ReqSeqNumServer = start_sequence_number_server(),
+ RespSeqNumServer = start_sequence_number_server(),
+ {ok, Server, Port} = start_slow_server(RespSeqNumServer),
+ Clients = run_clients(105, Port, ReqSeqNumServer),
+ %% ok = wait_for_clients(Clients),
+ ok = wait4clients(Clients, timer:minutes(3)),
+ Server ! shutdown,
+ RespSeqNumServer ! shutdown,
+ ReqSeqNumServer ! shutdown,
+ ok.
+
+start_inets() ->
+ inets:start(),
+ ok.
+
+
+%% -----------------------------------------------------
+%% A sequence number handler
+%% The purpose is to be able to pair requests with responses.
+
+start_sequence_number_server() ->
+ proc_lib:spawn(fun() -> loop_sequence_number(1) end).
+
+loop_sequence_number(N) ->
+ receive
+ shutdown ->
+ ok;
+ {From, get_next} ->
+ From ! {next_is, N},
+ loop_sequence_number(N + 1)
+ end.
+
+get_next_sequence_number(SeqNumServer) ->
+ SeqNumServer ! {self(), get_next},
+ receive {next_is, N} -> N end.
+
+%% -----------------------------------------------------
+%% Client part
+%% Sends requests randomly parallel
+
+run_clients(NumClients, ServerPort, SeqNumServer) ->
+ io:format("start clients when"
+ "~n NumClients: ~w"
+ "~n ServerPort: ~w"
+ "~n SeqNumServer: ~w"
+ "~n", [NumClients, ServerPort, SeqNumServer]),
+ set_random_seed(),
+ lists:map(
+ fun(Id) ->
+ io:format("starting client ~w~n", [Id]),
+ Req = f("req~3..0w", [get_next_sequence_number(SeqNumServer)]),
+ Url = f(?URL_START ++ "~w/~s", [ServerPort, Req]),
+ Pid = proc_lib:spawn(
+ fun() ->
+ io:format("[~w] client started - "
+ "issue request~n", [Id]),
+ case http:request(Url) of
+ {ok, {{_,200,_}, _, Resp}} ->
+ io:format("[~w] 200 response: "
+ "~p~n", [Id, Resp]),
+ case lists:prefix(Req++"->", Resp) of
+ true -> exit(normal);
+ false -> exit({bad_resp,Req,Resp})
+ end;
+ {ok, {{_,EC,Reason},_,Resp}} ->
+ io:format("[~w] ~w response: "
+ "~s~n~s~n",
+ [Id, EC, Reason, Resp]),
+ exit({bad_resp,Req,Resp});
+ Crap ->
+ io:format("[~w] bad response: ~p",
+ [Id, Crap]),
+ exit({bad_resp, Req, Crap})
+ end
+ end),
+ MRef = erlang:monitor(process, Pid),
+ timer:sleep(10 + random:uniform(1334)),
+ {Id, Pid, MRef}
+
+ end,
+ lists:seq(1, NumClients)).
+
+%% wait_for_clients(Clients) ->
+%% lists:foreach(
+%% fun({Id, Pid, MRef}) ->
+%% io:format("waiting for client ~w termination~n", [Id]),
+%% receive
+%% {'DOWN', MRef, process, Pid, normal} ->
+%% io:format("waiting for clients: "
+%% "normal exit from ~w (~p)~n",
+%% [Id, Pid]),
+%% ok;
+%% {'DOWN', MRef, process, Pid, Reason} ->
+%% io:format("waiting for clients: "
+%% "unexpected exit from ~w (~p):"
+%% "~n Reason: ~p"
+%% "~n", [Id, Pid, Reason]),
+%% erlang:error(Reason)
+%% end
+%% end,
+%% Clients).
+
+
+wait4clients([], _Timeout) ->
+ ok;
+wait4clients(Clients, Timeout) when Timeout > 0 ->
+ io:format("wait4clients -> entry with"
+ "~n length(Clients): ~w"
+ "~n Timeout: ~w"
+ "~n", [length(Clients), Timeout]),
+ T = t(),
+ receive
+ {'DOWN', _MRef, process, Pid, normal} ->
+ case lists:keysearch(Pid, 2, Clients) of
+ {value, {Id, _, _}} ->
+ io:format("receive normal exit message "
+ "from client ~p (~p)", [Id, Pid]),
+ NewClients =
+ lists:keydelete(Id, 1, Clients),
+ wait4clients(NewClients,
+ Timeout - (t() - T));
+ false ->
+ io:format("receive normal exit message "
+ "from unknown process: ~p", [Pid]),
+ wait4clients(Clients, Timeout - (t() - T))
+ end;
+
+ {'DOWN', _MRef, process, Pid, Reason} ->
+ case lists:keysearch(Pid, 2, Clients) of
+ {value, {Id, _, _}} ->
+ io:format("receive bad exit message "
+ "from client ~p (~p):"
+ "~n ~p", [Id, Pid, Reason]),
+ erlang:error({bad_client_termination, Id, Reason});
+ false ->
+ io:format("receive normal exit message "
+ "from unknown process: ~p", [Pid]),
+ wait4clients(Clients, Timeout - (t() - T))
+ end
+
+ after Timeout ->
+ erlang:error({client_timeout, Clients})
+ end;
+wait4clients(Clients, _) ->
+ erlang:error({client_timeout, Clients}).
+
+
+%% Time in milli seconds
+t() ->
+ {A,B,C} = erlang:now(),
+ A*1000000000+B*1000+(C div 1000).
+
+
+%% -----------------------------------------------------
+%% Webserver part:
+%% Implements a web server that sends responses one character
+%% at a time, with random delays between the characters.
+
+start_slow_server(SeqNumServer) ->
+ io:format("start slow server when"
+ "~n SeqNumServer: ~w"
+ "~n", [SeqNumServer]),
+ proc_lib:start(
+ erlang, apply, [fun() -> init_slow_server(SeqNumServer) end, []]).
+
+init_slow_server(SeqNumServer) ->
+ io:format("[webserver ~w] init slow server"
+ "~n", [SeqNumServer]),
+ {ok, LSock} = gen_tcp:listen(0, [binary, {packet,0}, {active,true},
+ {backlog, 100}]),
+ io:format("[webserver ~w] LSock: ~p"
+ "~n", [SeqNumServer, LSock]),
+ {ok, {_IP, Port}} = inet:sockname(LSock),
+ io:format("[webserver ~w] Port: ~w"
+ "~n", [SeqNumServer, Port]),
+ proc_lib:init_ack({ok, self(), Port}),
+ loop_slow_server(LSock, SeqNumServer).
+
+loop_slow_server(LSock, SeqNumServer) ->
+ io:format("[webserver ~w] entry with"
+ "~n LSock: ~p"
+ "~n", [SeqNumServer, LSock]),
+ Master = self(),
+ Acceptor = proc_lib:spawn(
+ fun() -> client_handler(Master, LSock, SeqNumServer) end),
+ io:format("[webserver ~w] acceptor started"
+ "~n Acceptor: ~p"
+ "~n", [SeqNumServer, Acceptor]),
+ receive
+ {accepted, Acceptor} ->
+ io:format("[webserver ~w] accepted"
+ "~n", [SeqNumServer]),
+ loop_slow_server(LSock, SeqNumServer);
+ shutdown ->
+ gen_tcp:close(LSock),
+ exit(Acceptor, kill)
+ end.
+
+
+%% Handle one client connection
+client_handler(Master, LSock, SeqNumServer) ->
+ io:format("[acceptor ~w] await accept"
+ "~n", [SeqNumServer]),
+ {ok, CSock} = gen_tcp:accept(LSock),
+ io:format("[acceptor ~w] accepted"
+ "~n CSock: ~p"
+ "~n", [SeqNumServer, CSock]),
+ Master ! {accepted, self()},
+ set_random_seed(),
+ loop_client(1, CSock, SeqNumServer).
+
+loop_client(N, CSock, SeqNumServer) ->
+ %% Await request, don't bother parsing it too much,
+ %% assuming the entire request arrives in one packet.
+ io:format("[acceptor ~w] await request"
+ "~n N: ~p"
+ "~n", [SeqNumServer, N]),
+ receive
+ {tcp, CSock, Req} ->
+ ReqNum = parse_req_num(Req),
+ RespSeqNum = get_next_sequence_number(SeqNumServer),
+ Response = f("~s->resp~3..0w/~2..0w", [ReqNum, RespSeqNum, N]),
+ Txt = f("Slow server (~p) got ~p, answering with ~p",
+ [self(), Req, Response]),
+ io:format("~s...~n", [Txt]),
+ slowly_send_response(CSock, Response),
+ case parse_connection_type(Req) of
+ keep_alive ->
+ io:format("~s...done~n", [Txt]),
+ loop_client(N+1, CSock, SeqNumServer);
+ close ->
+ io:format("~s...done (closing)~n", [Txt]),
+ gen_tcp:close(CSock)
+ end
+ end.
+
+slowly_send_response(CSock, Answer) ->
+ Response = f("HTTP/1.1 200 OK\r\nContent-Length: ~w\r\n\r\n~s",
+ [length(Answer), Answer]),
+ lists:foreach(
+ fun(Char) ->
+ timer:sleep(random:uniform(500)),
+ gen_tcp:send(CSock, <<Char>>)
+ end,
+ Response).
+
+parse_req_num(Request) ->
+ Opts = [caseless,{capture,all_but_first,list}],
+ {match, [ReqNum]} = re:run(Request, "GET /(.*) HTTP", Opts),
+ ReqNum.
+
+parse_connection_type(Request) ->
+ Opts = [caseless,{capture,all_but_first,list}],
+ {match,[CType]} = re:run(Request, "connection: *(keep-alive|close)", Opts),
+ case string:to_lower(CType) of
+ "close" -> close;
+ "keep-alive" -> keep_alive
+ end.
+
+
+set_random_seed() ->
+ {_, _, Micros} = now(),
+ A = erlang:phash2([make_ref(), self(), Micros]),
+ random:seed(A, A, A).
+
+f(F, A) -> lists:flatten(io_lib:format(F,A)).
+
+
+
+
+%%-------------------------------------------------------------------------
+
+otp_8106(suite) ->
+ [
+ otp_8106_pid,
+ otp_8106_fun,
+ otp_8106_mfa
+ ].
+
+
+otp_8106_pid(doc) ->
+ ["OTP-8106 - deliver reply info using \"other\" pid"];
+otp_8106_pid(suite) ->
+ [];
+otp_8106_pid(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ ReceiverPid = create_receiver(pid),
+ Receiver = ReceiverPid,
+
+ otp8106(ReceiverPid, Receiver, Config),
+
+ stop_receiver(ReceiverPid),
+
+ ok;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+otp_8106_fun(doc) ->
+ ["OTP-8106 - deliver reply info using fun"];
+otp_8106_fun(suite) ->
+ [];
+otp_8106_fun(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ ReceiverPid = create_receiver(function),
+ Receiver = otp_8106_deliver_fun(ReceiverPid),
+
+ otp8106(ReceiverPid, Receiver, Config),
+
+ stop_receiver(ReceiverPid),
+
+ ok;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+otp_8106_mfa(doc) ->
+ ["OTP-8106 - deliver reply info using mfa callback"];
+otp_8106_mfa(suite) ->
+ [];
+otp_8106_mfa(Config) when is_list(Config) ->
+ case ?config(local_server, Config) of
+ ok ->
+ ReceiverPid = create_receiver(mfa),
+ Receiver = {?MODULE, otp_8106_deliver, [mfa, ReceiverPid]},
+
+ otp8106(ReceiverPid, Receiver, Config),
+
+ stop_receiver(ReceiverPid),
+
+ ok;
+ _ ->
+ {skip, "Failed to start local http-server"}
+ end.
+
+
+ otp8106(ReceiverPid, Receiver, Config) ->
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ Request = {URL, []},
+ HTTPOptions = [],
+ Options = [{sync, false}, {receiver, Receiver}],
+
+ {ok, RequestId} =
+ httpc:request(get, Request, HTTPOptions, Options),
+
+ Body =
+ receive
+ {reply, ReceiverPid, {RequestId, {{_, 200, _}, _, B}}} ->
+ B;
+ {reply, ReceiverPid, Msg} ->
+ tsf(Msg);
+ {bad_reply, ReceiverPid, Msg} ->
+ tsf(Msg)
+ end,
+
+ inets_test_lib:check_body(binary_to_list(Body)),
+ ok.
+
+
+create_receiver(Type) ->
+ Parent = self(),
+ Receiver = fun() -> receiver(Type, Parent) end,
+ spawn_link(Receiver).
+
+stop_receiver(Pid) ->
+ Pid ! {stop, self()}.
+
+receiver(Type, Parent) ->
+ receive
+ {stop, Parent} ->
+ exit(normal);
+
+ {http, ReplyInfo} when (Type =:= pid) ->
+ Parent ! {reply, self(), ReplyInfo},
+ receiver(Type, Parent);
+
+ {Type, ReplyInfo} ->
+ Parent ! {reply, self(), ReplyInfo},
+ receiver(Type, Parent);
+
+ Crap ->
+ Parent ! {reply, self(), {bad_reply, Crap}},
+ receiver(Type, Parent)
+ end.
+
+
+otp_8106_deliver_fun(ReceiverPid) ->
+ fun(ReplyInfo) -> otp_8106_deliver(ReplyInfo, function, ReceiverPid) end.
+
+otp_8106_deliver(ReplyInfo, Type, ReceiverPid) ->
+ ReceiverPid ! {Type, ReplyInfo},
+ ok.
+
+
+
+%%-------------------------------------------------------------------------
+
+otp_8056(doc) ->
+ "OTP-8056";
+otp_8056(suite) ->
+ [];
+otp_8056(Config) when is_list(Config) ->
+ Method = get,
+ Port = ?config(local_port, Config),
+ URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ Request = {URL, []},
+ HTTPOptions = [],
+ Options1 = [{sync, true}, {stream, {self, once}}],
+ Options2 = [{sync, true}, {stream, self}],
+ {error, streaming_error} = httpc:request(Method, Request,
+ HTTPOptions, Options1),
+ tsp("request 1 failed as expected"),
+ {error, streaming_error} = httpc:request(Method, Request,
+ HTTPOptions, Options2),
+ tsp("request 2 failed as expected"),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+otp_8371(doc) ->
+ ["OTP-8371"];
+otp_8371(suite) ->
+ [];
+otp_8371(Config) when is_list(Config) ->
+ ok = http:set_options([{ipv6, disabled}]), % also test the old option
+ {DummyServerPid, Port} = dummy_server(self(), ipv4),
+
+ URL = ?URL_START ++ integer_to_list(Port) ++
+ "/ensure_host_header_with_port.html",
+
+ case http:request(get, {URL, []}, [], []) of
+ {ok, Result} ->
+ case Result of
+ {{_, 200, _}, _Headers, Body} ->
+ tsp("expected response with"
+ "~n Body: ~p", [Body]),
+ ok;
+ {StatusLine, Headers, Body} ->
+ tsp("expected response with"
+ "~n StatusLine: ~p"
+ "~n Headers: ~p"
+ "~n Body: ~p", [StatusLine, Headers, Body]),
+ tsf({unexpected_result,
+ [{status_line, StatusLine},
+ {headers, Headers},
+ {body, Body}]});
+ _ ->
+ tsf({unexpected_result, Result})
+ end;
+ Error ->
+ tsf({request_failed, Error})
+ end,
+
+ DummyServerPid ! stop,
+ ok = http:set_options([{ipv6, enabled}]),
+ ok.
+
+
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
+ ConfDir = filename:join(ServerRoot, "conf"),
+ CgiDir = filename:join(ServerRoot, "cgi-bin"),
+ ok = file:make_dir(ServerRoot),
+ ok = file:make_dir(DocRoot),
+ ok = file:make_dir(ConfDir),
+ ok = file:make_dir(CgiDir),
+
+ {ok, Files} = file:list_dir(DataDir),
+
+ lists:foreach(fun(File) -> case lists:suffix(".html", File) of
+ true ->
+ inets_test_lib:copy_file(File,
+ DataDir,
+ DocRoot);
+ false ->
+ ok
+ end
+ end, Files),
+
+ Cgi = case test_server:os_type() of
+ {win32, _} ->
+ "cgi_echo.exe";
+ _ ->
+ "cgi_echo"
+ end,
+
+ inets_test_lib:copy_file(Cgi, DataDir, CgiDir),
+ inets_test_lib:copy_file("mime.types", DataDir, ConfDir).
+
+create_config(FileName, ComType, Port, PrivDir, ServerRoot, DocRoot,
+ SSLDir) ->
+ MaxHdrSz = io_lib:format("~p", [256]),
+ MaxHdrAct = io_lib:format("~p", [close]),
+ SSL =
+ case ComType of
+ ssl ->
+ [cline(["SSLCertificateFile ",
+ filename:join(SSLDir, "ssl_server_cert.pem")]),
+ cline(["SSLCertificateKeyFile ",
+ filename:join(SSLDir, "ssl_server_cert.pem")]),
+ cline(["SSLVerifyClient 0"])];
+ _ ->
+ []
+ end,
+
+ Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi"
+ " mod_include mod_dir mod_get mod_head"
+ " mod_log mod_disk_log mod_trace",
+
+ HttpConfig = [
+ cline(["Port ", integer_to_list(Port)]),
+ cline(["ServerName ", "httpc_test"]),
+ cline(["SocketType ", atom_to_list(ComType)]),
+ cline([Mod_order]),
+ cline(["ServerRoot ", ServerRoot]),
+ cline(["DocumentRoot ", DocRoot]),
+ cline(["MaxHeaderSize ",MaxHdrSz]),
+ cline(["MaxHeaderAction ",MaxHdrAct]),
+ cline(["DirectoryIndex ", "index.html "]),
+ cline(["DefaultType ", "text/plain"]),
+ cline(["ScriptAlias /cgi-bin/ ",
+ filename:join(ServerRoot, "cgi-bin"), "/"]),
+ SSL],
+ ConfigFile = filename:join([PrivDir,FileName]),
+ {ok, Fd} = file:open(ConfigFile, [write]),
+ ok = file:write(Fd, lists:flatten(HttpConfig)),
+ ok = file:close(Fd).
+
+cline(List) ->
+ lists:flatten([List, "\r\n"]).
+
+is_proxy_available(Proxy, Port) ->
+ case gen_tcp:connect(Proxy, Port, []) of
+ {ok, Socket} ->
+ gen_tcp:close(Socket),
+ true;
+ _ ->
+ false
+ end.
+
+receive_streamed_body(RequestId, Body) ->
+ receive
+ {http, {RequestId, stream, BinBodyPart}} ->
+ receive_streamed_body(RequestId,
+ <<Body/binary, BinBodyPart/binary>>);
+ {http, {RequestId, stream_end, _Headers}} ->
+ Body;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end.
+
+receive_streamed_body(RequestId, Body, Pid) ->
+ http:stream_next(Pid),
+ test_server:format("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]),
+ receive
+ {http, {RequestId, stream, BinBodyPart}} ->
+ receive_streamed_body(RequestId,
+ <<Body/binary, BinBodyPart/binary>>,
+ Pid);
+ {http, {RequestId, stream_end, _Headers}} ->
+ Body;
+ {http, Msg} ->
+ test_server:fail(Msg)
+ end.
+
+
+
+dummy_server(Caller, IpV) ->
+ Pid = spawn(httpc_SUITE, dummy_server_init, [Caller, IpV]),
+ receive
+ {port, Port} ->
+ {Pid, Port}
+ end.
+
+dummy_server_init(Caller, IpV) ->
+ {ok, ListenSocket} =
+ case IpV of
+ ipv4 ->
+ gen_tcp:listen(0, [binary, inet, {packet, 0},
+ {reuseaddr,true},
+ {active, false}]);
+ ipv6 ->
+ gen_tcp:listen(0, [binary, inet6, {packet, 0},
+ {reuseaddr,true},
+ {active, false}])
+ end,
+ {ok, Port} = inet:port(ListenSocket),
+ tsp("dummy_server_init -> Port: ~p", [Port]),
+ Caller ! {port, Port},
+ dummy_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]},
+ [], ListenSocket).
+
+dummy_server_loop(MFA, Handlers, ListenSocket) ->
+ receive
+ stop ->
+ lists:foreach(fun(Handler) -> Handler ! stop end, Handlers)
+ after 0 ->
+ {ok, Socket} = gen_tcp:accept(ListenSocket),
+ HandlerPid = dummy_request_handler(MFA, Socket),
+ gen_tcp:controlling_process(Socket, HandlerPid),
+ HandlerPid ! controller,
+ dummy_server_loop(MFA, [HandlerPid | Handlers],
+ ListenSocket)
+ end.
+
+dummy_request_handler(MFA, Socket) ->
+ spawn(httpc_SUITE, dummy_request_handler_init, [MFA, Socket]).
+
+dummy_request_handler_init(MFA, Socket) ->
+ receive
+ controller ->
+ inet:setopts(Socket, [{active, true}])
+ end,
+ dummy_request_handler_loop(MFA, Socket).
+
+dummy_request_handler_loop({Module, Function, Args}, Socket) ->
+ tsp("dummy_request_handler_loop -> entry with"
+ "~n Module: ~p"
+ "~n Function: ~p"
+ "~n Args: ~p", [Module, Function, Args]),
+ receive
+ {tcp, _, Data} ->
+ tsp("dummy_request_handler_loop -> Data ~p", [Data]),
+ case handle_request(Module, Function, [Data | Args], Socket) of
+ stop ->
+ gen_tcp:close(Socket);
+ NewMFA ->
+ dummy_request_handler_loop(NewMFA, Socket)
+ end;
+ stop ->
+ gen_tcp:close(Socket)
+ end.
+
+handle_request(Module, Function, Args, Socket) ->
+ tsp("handle_request -> entry with"
+ "~n Module: ~p"
+ "~n Function: ~p"
+ "~n Args: ~p", [Module, Function, Args]),
+ case Module:Function(Args) of
+ {ok, Result} ->
+ tsp("handle_request -> ok"
+ "~n Result: ~p", [Result]),
+ case (catch handle_http_msg(Result, Socket)) of
+ stop ->
+ stop;
+ <<>> ->
+ tsp("handle_request -> empty data"),
+ {httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]};
+ Data ->
+ handle_request(httpd_request, parse,
+ [Data |[?HTTP_MAX_HEADER_SIZE]], Socket)
+ end;
+ NewMFA ->
+ tsp("handle_request -> "
+ "~n NewMFA: ~p", [NewMFA]),
+ NewMFA
+ end.
+
+handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) ->
+ tsp("handle_http_msg -> entry with: "
+ "~n RelUri: ~p"
+ "~n Headers: ~p"
+ "~n Body: ~p", [RelUri, Headers, Body]),
+ NextRequest =
+ case RelUri of
+ "/dummy_headers.html" ->
+ <<>>;
+ "/no_headers.html" ->
+ stop;
+ "/just_close.html" ->
+ stop;
+ _ ->
+ ContentLength = content_length(Headers),
+ case size(Body) - ContentLength of
+ 0 ->
+ <<>>;
+ _ ->
+ <<_BodyThisReq:ContentLength/binary,
+ Next/binary>> = Body,
+ Next
+ end
+ end,
+
+ tsp("handle_http_msg -> NextRequest: ~p", [NextRequest]),
+ case (catch ets:lookup(cookie, cookies)) of
+ [{cookies, true}]->
+ tsp("handle_http_msg -> check cookies ~p", []),
+ check_cookie(Headers);
+ _ ->
+ ok
+ end,
+
+ DefaultResponse = "HTTP/1.1 200 ok\r\n" ++
+ "Content-Length:32\r\n\r\n"
+ "<HTML><BODY>foobar</BODY></HTML>",
+
+ Msg =
+ case RelUri of
+ "/just_close.html" ->
+ close;
+ "/no_content.html" ->
+ "HTTP/1.0 204 No Content\r\n\r\n";
+ "/no_headers.html" ->
+ "HTTP/1.0 200 OK\r\n\r\nTEST";
+ "/ensure_host_header_with_port.html" ->
+ %% tsp("handle_http_msg -> validate host with port"),
+ case ensure_host_header_with_port(Headers) of
+ true ->
+ B =
+ "<HTML><BODY>" ++
+ "host with port" ++
+ "</BODY></HTML>",
+ Len = integer_to_list(length(B)),
+ "HTTP/1.1 200 ok\r\n" ++
+ "Content-Length:" ++ Len ++ "\r\n\r\n" ++ B;
+ false ->
+ B =
+ "<HTML><BODY>" ++
+ "Internal Server Error - host without port" ++
+ "</BODY></HTML>",
+ Len = integer_to_list(length(B)),
+ "HTTP/1.1 500 Internal Server Error\r\n" ++
+ "Content-Length:" ++ Len ++ "\r\n\r\n" ++ B
+ end;
+ "/300.html" ->
+ NewUri = ?URL_START ++
+ integer_to_list(?IP_PORT) ++ "/dummy.html",
+ "HTTP/1.1 300 Multiple Choices\r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:0\r\n\r\n";
+ "/301.html" ->
+ NewUri = ?URL_START ++
+ integer_to_list(?IP_PORT) ++ "/dummy.html",
+ "HTTP/1.1 301 Moved Permanently\r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:80\r\n\r\n" ++
+ "<HTML><BODY><a href=" ++ NewUri ++
+ ">New place</a></BODY></HTML>";
+ "/302.html" ->
+ NewUri = ?URL_START ++
+ integer_to_list(?IP_PORT) ++ "/dummy.html",
+ "HTTP/1.1 302 Found \r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:80\r\n\r\n" ++
+ "<HTML><BODY><a href=" ++ NewUri ++
+ ">New place</a></BODY></HTML>";
+ "/307.html" ->
+ NewUri = ?URL_START ++
+ integer_to_list(?IP_PORT) ++ "/dummy.html",
+ "HTTP/1.1 307 Temporary Rediect \r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:80\r\n\r\n" ++
+ "<HTML><BODY><a href=" ++ NewUri ++
+ ">New place</a></BODY></HTML>";
+ "/500.html" ->
+ "HTTP/1.1 500 Internal Server Error\r\n" ++
+ "Content-Length:47\r\n\r\n" ++
+ "<HTML><BODY>Internal Server Error</BODY></HTML>";
+ "/503.html" ->
+ case ets:lookup(unavailable, 503) of
+ [{503, unavailable}] ->
+ ets:insert(unavailable, {503, available}),
+ "HTTP/1.1 503 Service Unavailable\r\n" ++
+ "Retry-After:5\r\n" ++
+ "Content-Length:47\r\n\r\n" ++
+ "<HTML><BODY>Internal Server Error</BODY></HTML>";
+ [{503, available}] ->
+ DefaultResponse;
+ [{503, long_unavailable}] ->
+ "HTTP/1.1 503 Service Unavailable\r\n" ++
+ "Retry-After:120\r\n" ++
+ "Content-Length:47\r\n\r\n" ++
+ "<HTML><BODY>Internal Server Error</BODY></HTML>"
+ end;
+ "/redirectloop.html" -> %% Create a potential endless loop!
+ {ok, Port} = inet:port(Socket),
+ NewUri = ?URL_START ++
+ integer_to_list(Port) ++ "/redirectloop.html",
+ "HTTP/1.1 300 Multiple Choices\r\n" ++
+ "Location:" ++ NewUri ++ "\r\n" ++
+ "Content-Length:0\r\n\r\n";
+ "/userinfo.html" ->
+ Challange = "HTTP/1.1 401 Unauthorized \r\n" ++
+ "WWW-Authenticate:Basic" ++"\r\n" ++
+ "Content-Length:0\r\n\r\n",
+ case auth_header(Headers) of
+ {ok, Value} ->
+ handle_auth(Value, Challange, DefaultResponse);
+ _ ->
+ Challange
+ end;
+ "/dummy_headers.html" ->
+ %% The client will only care about the Transfer-Encoding
+ %% header the rest of these headers are left to the
+ %% user to evaluate. This is not a valid response
+ %% it only tests that the header handling code works.
+ Head = "HTTP/1.1 200 ok\r\n" ++
+ "Content-Length:32\r\n" ++
+ "Pragma:1#no-cache\r\n" ++
+ "Via:1.0 fred, 1.1 nowhere.com (Apache/1.1)\r\n" ++
+ "Warning:1#pseudonym foobar\r\n" ++
+ "Vary:*\r\n" ++
+ "Trailer:Other:inets_test\r\n" ++
+ "Upgrade:HTTP/2.0\r\n" ++
+ "Age:4711\r\n" ++
+ "Transfer-Encoding:chunked\r\n" ++
+ "Content-Encoding:foo\r\n" ++
+ "Content-Language:en\r\n" ++
+ "Content-Location:http://www.foobar.se\r\n" ++
+ "Content-MD5:104528739076276072743283077410617235478\r\n"
+ ++
+ "Content-Range:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++
+ "Expires:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++
+ "Proxy-Authenticate:#1Basic" ++
+ "\r\n\r\n",
+ gen_tcp:send(Socket, Head),
+ gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")),
+ gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")),
+ http_chunk:encode_last();
+ "/capital_transfer_encoding.html" ->
+ Head = "HTTP/1.1 200 ok\r\n" ++
+ "Transfer-Encoding:Chunked\r\n\r\n",
+ gen_tcp:send(Socket, Head),
+ gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")),
+ gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")),
+ http_chunk:encode_last();
+ "/cookie.html" ->
+ "HTTP/1.1 200 ok\r\n" ++
+ "set-cookie:" ++ "test_cookie=true; path=/;" ++
+ "max-age=60000\r\n" ++
+ "Content-Length:32\r\n\r\n"++
+ "<HTML><BODY>foobar</BODY></HTML>";
+ "/missing_crlf.html" ->
+ "HTTP/1.1 200 ok" ++
+ "Content-Length:32\r\n" ++
+ "<HTML><BODY>foobar</BODY></HTML>";
+ "/wrong_statusline.html" ->
+ "ok 200 HTTP/1.1\r\n\r\n" ++
+ "Content-Length:32\r\n\r\n" ++
+ "<HTML><BODY>foobar</BODY></HTML>";
+ "/once_chunked.html" ->
+ Head = "HTTP/1.1 200 ok\r\n" ++
+ "Transfer-Encoding:Chunked\r\n\r\n",
+ gen_tcp:send(Socket, Head),
+ gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")),
+ gen_tcp:send(Socket,
+ http_chunk:encode("obar</BODY></HTML>")),
+ http_chunk:encode_last();
+ "/once.html" ->
+ Head = "HTTP/1.1 200 ok\r\n" ++
+ "Content-Length:32\r\n\r\n",
+ gen_tcp:send(Socket, Head),
+ gen_tcp:send(Socket, "<HTML><BODY>fo"),
+ test_server:sleep(1000),
+ gen_tcp:send(Socket, "ob"),
+ test_server:sleep(1000),
+ gen_tcp:send(Socket, "ar</BODY></HTML>");
+ "/invalid_http.html" ->
+ "HTTP/1.1 301\r\nDate:Sun, 09 Dec 2007 13:04:18 GMT\r\n" ++
+ "Transfer-Encoding:chunked\r\n\r\n";
+ "/missing_reason_phrase.html" ->
+ "HTTP/1.1 200\r\n" ++
+ "Content-Length: 32\r\n\r\n"
+ "<HTML><BODY>foobar</BODY></HTML>";
+ "/missing_CR.html" ->
+ "HTTP/1.1 200 ok\n" ++
+ "Content-Length:32\r\n\n"
+ "<HTML><BODY>foobar</BODY></HTML>";
+ _ ->
+ DefaultResponse
+ end,
+
+ tsp("handle_http_msg -> Msg: ~p", [Msg]),
+ case Msg of
+ ok ->
+ %% Previously, this resulted in an {error, einval}. Now what?
+ ok;
+ close ->
+ %% Nothing to send, just close
+ gen_tcp:close(Socket);
+ _ when is_list(Msg) orelse is_binary(Msg) ->
+ gen_tcp:send(Socket, Msg)
+ end,
+ tsp("handle_http_msg -> done"),
+ NextRequest.
+
+ensure_host_header_with_port([]) ->
+ false;
+ensure_host_header_with_port(["host: " ++ Host| _]) ->
+ case string:tokens(Host, [$:]) of
+ [ActualHost, Port] ->
+ tsp("ensure_host_header_with_port -> "
+ "~n ActualHost: ~p"
+ "~n Port: ~p", [ActualHost, Port]),
+ true;
+ _ ->
+ false
+ end;
+ensure_host_header_with_port([_|T]) ->
+ ensure_host_header_with_port(T).
+
+auth_header([]) ->
+ auth_header_not_found;
+auth_header(["authorization:" ++ Value | _]) ->
+ {ok, string:strip(Value)};
+auth_header([_ | Tail]) ->
+ auth_header(Tail).
+
+handle_auth("Basic " ++ UserInfo, Challange, DefaultResponse) ->
+ case string:tokens(base64:decode_to_string(UserInfo), ":") of
+ ["alladin", "sesame"] = Auth ->
+ test_server:format("Auth: ~p~n", [Auth]),
+ DefaultResponse;
+ Other ->
+ test_server:format("UnAuth: ~p~n", [Other]),
+ Challange
+ end.
+
+check_cookie([]) ->
+ test_server:fail(no_cookie_header);
+check_cookie(["cookie:" ++ _Value | _]) ->
+ ok;
+check_cookie([_Head | Tail]) ->
+ check_cookie(Tail).
+
+content_length([]) ->
+ 0;
+content_length(["content-length:" ++ Value | _]) ->
+ list_to_integer(string:strip(Value));
+content_length([_Head | Tail]) ->
+ content_length(Tail).
+
+provocate_not_modified_bug(Url) ->
+ Timeout = 15000, %% 15s should be plenty
+
+ {ok, {{_, 200, _}, ReplyHeaders, _Body}} =
+ http:request(get, {Url, []}, [{timeout, Timeout}], []),
+ Etag = pick_header(ReplyHeaders, "ETag"),
+ Last = pick_header(ReplyHeaders, "last-modified"),
+
+ case http:request(get, {Url, [{"If-None-Match", Etag},
+ {"If-Modified-Since", Last}]},
+ [{timeout, 15000}],
+ []) of
+ {ok, {{_, 304, _}, _, _}} -> %% The expected reply
+ page_unchanged;
+ {ok, {{_, 200, _}, _, _}} ->
+ %% If the page has changed since the
+ %% last request we retry to
+ %% trigger the bug
+ provocate_not_modified_bug(Url);
+ {error, timeout} ->
+ %% Not what we expected. Tcpdump can be used to
+ %% verify that we receive the complete http-reply
+ %% but still time out.
+ incorrect_result
+ end.
+
+pick_header(Headers, Name) ->
+ case lists:keysearch(string:to_lower(Name), 1,
+ [{string:to_lower(X), Y} || {X, Y} <- Headers]) of
+ false ->
+ [];
+ {value, {_Key, Val}} ->
+ Val
+ end.
+
+
+not_implemented_yet() ->
+ exit(not_implemented_yet).
+
+
+p(F) ->
+ p(F, []).
+
+p(F, A) ->
+ io:format("~p ~w:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
+tsp(F) ->
+ tsp(F, []).
+tsp(F, A) ->
+ test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
+tsf(Reason) ->
+ test_server:fail(Reason).
diff --git a/lib/inets/test/httpc_SUITE_data/Makefile.src b/lib/inets/test/httpc_SUITE_data/Makefile.src
new file mode 120000
index 0000000000..ad6e7bf9c8
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/Makefile.src
@@ -0,0 +1 @@
+../httpd_SUITE_data/Makefile.src \ No newline at end of file
diff --git a/lib/inets/test/httpc_SUITE_data/cgi_echo.c b/lib/inets/test/httpc_SUITE_data/cgi_echo.c
new file mode 120000
index 0000000000..38e06fe0d2
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/cgi_echo.c
@@ -0,0 +1 @@
+../httpd_SUITE_data/cgi_echo.c \ No newline at end of file
diff --git a/lib/inets/test/httpc_SUITE_data/dummy.html b/lib/inets/test/httpc_SUITE_data/dummy.html
new file mode 100644
index 0000000000..9a960ecc8a
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/dummy.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<title>Dummy</title>
+</head>
+
+<body>
+<h1>Dummy</h1>
+
+<p>This is a dummy html file! </p>
+</body>
+</html>
diff --git a/lib/inets/test/httpc_SUITE_data/empty.html b/lib/inets/test/httpc_SUITE_data/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/empty.html
diff --git a/lib/inets/test/httpc_SUITE_data/mime.types b/lib/inets/test/httpc_SUITE_data/mime.types
new file mode 100644
index 0000000000..d2f81e4e5e
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/mime.types
@@ -0,0 +1,465 @@
+# This is a comment. I love comments.
+
+# MIME type Extension
+application/EDI-Consent
+application/EDI-X12
+application/EDIFACT
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atomicmail
+application/batch-SMTP
+application/beep+xml
+application/cals-1840
+application/commonground
+application/cybercash
+application/dca-rft
+application/dec-dx
+application/dvcs
+application/eshop
+application/http
+application/hyperstudio
+application/iges
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/font-tdpfr
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc
+application/mathematica
+application/mathematica-old
+application/msword doc
+application/news-message-id
+application/news-transmission
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh exe class so dll
+application/oda oda
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted
+application/pgp-keys
+application/pgp-signature
+application/pkcs10
+application/pkcs7-mime
+application/pkcs7-signature
+application/pkix-cert
+application/pkix-crl
+application/pkixcmp
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww
+application/prs.nprend
+application/qsig
+application/remote-printing
+application/riscos
+application/rtf
+application/sdp
+application/set-payment
+application/set-payment-initiation
+application/set-registration
+application/set-registration-initiation
+application/sgml
+application/sgml-open-catalog
+application/sieve
+application/slate
+application/smil smi smil
+application/timestamp-query
+application/timestamp-reply
+application/vemmi
+application/vnd.3M.Post-it-Notes
+application/vnd.FloGraphIt
+application/vnd.accpac.simply.aso
+application/vnd.accpac.simply.imp
+application/vnd.acucobol
+application/vnd.aether.imp
+application/vnd.anser-web-certificate-issue-initiation
+application/vnd.anser-web-funds-transfer-initiation
+application/vnd.audiograph
+application/vnd.businessobjects
+application/vnd.bmi
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.claymore
+application/vnd.commerce-battelle
+application/vnd.commonspace
+application/vnd.comsocaller
+application/vnd.contact.cmsg
+application/vnd.cosmocaller
+application/vnd.cups-postscript
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.ctc-posml
+application/vnd.cybank
+application/vnd.dna
+application/vnd.dpgraph
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven
+application/vnd.epson.esf
+application/vnd.epson.msf
+application/vnd.epson.quickanime
+application/vnd.epson.salt
+application/vnd.epson.ssf
+application/vnd.ericsson.quickcall
+application/vnd.eudora.data
+application/vnd.fdf
+application/vnd.ffsns
+application/vnd.framemaker
+application/vnd.fsc.weblaunch
+application/vnd.fujitsu.oasys
+application/vnd.fujitsu.oasys2
+application/vnd.fujitsu.oasys3
+application/vnd.fujitsu.oasysgp
+application/vnd.fujitsu.oasysprs
+application/vnd.fujixerox.ddd
+application/vnd.fujixerox.docuworks
+application/vnd.fujixerox.docuworks.binder
+application/vnd.fut-misnet
+application/vnd.grafeq
+application/vnd.groove-account
+application/vnd.groove-identity-message
+application/vnd.groove-injector
+application/vnd.groove-tool-message
+application/vnd.groove-tool-template
+application/vnd.groove-vcard
+application/vnd.hhe.lesson-player
+application/vnd.hp-HPGL
+application/vnd.hp-PCL
+application/vnd.hp-PCLXL
+application/vnd.hp-hpid
+application/vnd.hp-hps
+application/vnd.httphone
+application/vnd.hzn-3d-crossword
+application/vnd.ibm.afplinedata
+application/vnd.ibm.MiniPay
+application/vnd.ibm.modcap
+application/vnd.informix-visionary
+application/vnd.intercon.formnet
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo
+application/vnd.intu.qfx
+application/vnd.irepository.package+xml
+application/vnd.is-xpr
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.koan
+application/vnd.lotus-1-2-3
+application/vnd.lotus-approach
+application/vnd.lotus-freelance
+application/vnd.lotus-notes
+application/vnd.lotus-organizer
+application/vnd.lotus-screencam
+application/vnd.lotus-wordpro
+application/vnd.mcd
+application/vnd.mediastation.cdkey
+application/vnd.meridian-slingshot
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf
+application/vnd.mobius.dis
+application/vnd.mobius.msl
+application/vnd.mobius.plc
+application/vnd.mobius.txf
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml
+application/vnd.ms-artgalry
+application/vnd.ms-asf
+application/vnd.ms-excel xls
+application/vnd.ms-lrm
+application/vnd.ms-powerpoint ppt
+application/vnd.ms-project
+application/vnd.ms-tnef
+application/vnd.ms-works
+application/vnd.mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician
+application/vnd.netfpx
+application/vnd.noblenet-directory
+application/vnd.noblenet-sealer
+application/vnd.noblenet-web
+application/vnd.novadigm.EDM
+application/vnd.novadigm.EDX
+application/vnd.novadigm.EXT
+application/vnd.osa.netdeploy
+application/vnd.palm
+application/vnd.pg.format
+application/vnd.pg.osasli
+application/vnd.powerbuilder6
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.previewsystems.box
+application/vnd.publishare-delta-tree
+application/vnd.pvi.ptid1
+application/vnd.pwg-xhtml-print+xml
+application/vnd.rapid
+application/vnd.s3sms
+application/vnd.seemail
+application/vnd.shana.informed.formdata
+application/vnd.shana.informed.formtemplate
+application/vnd.shana.informed.interchange
+application/vnd.shana.informed.package
+application/vnd.sss-cod
+application/vnd.sss-dtf
+application/vnd.sss-ntf
+application/vnd.street-stream
+application/vnd.svd
+application/vnd.swiftview-ics
+application/vnd.triscape.mxs
+application/vnd.trueapp
+application/vnd.truedoc
+application/vnd.tve-trigger
+application/vnd.ufdl
+application/vnd.uplanet.alert
+application/vnd.uplanet.alert-wbxml
+application/vnd.uplanet.bearer-choice-wbxml
+application/vnd.uplanet.bearer-choice
+application/vnd.uplanet.cacheop
+application/vnd.uplanet.cacheop-wbxml
+application/vnd.uplanet.channel
+application/vnd.uplanet.channel-wbxml
+application/vnd.uplanet.list
+application/vnd.uplanet.list-wbxml
+application/vnd.uplanet.listcmd
+application/vnd.uplanet.listcmd-wbxml
+application/vnd.uplanet.signal
+application/vnd.vcx
+application/vnd.vectorworks
+application/vnd.vidsoft.vidconference
+application/vnd.visio
+application/vnd.vividence.scriptfile
+application/vnd.wap.sic
+application/vnd.wap.slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo
+application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf
+application/vnd.xara
+application/vnd.xfdl
+application/vnd.yellowriver-custom-menu
+application/whoispp-query
+application/whoispp-response
+application/wita
+application/wordperfect5.1
+application/x-bcpio bcpio
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-gzip
+application/x-hdf hdf
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/x400-bp
+application/xml
+application/xml-dtd
+application/xml-external-parsed-entity
+application/zip zip
+audio/32kadpcm
+audio/basic au snd
+audio/g.722.1
+audio/l16
+audio/midi mid midi kar
+audio/mp4a-latm
+audio/mpa-robust
+audio/mpeg mpga mp2 mp3
+audio/parityfec
+audio/prs.sid
+audio/telephone-event
+audio/tone
+audio/vnd.cisco.nse
+audio/vnd.cns.anp1
+audio/vnd.cns.inf1
+audio/vnd.digital-winds
+audio/vnd.everad.plj
+audio/vnd.lucent.voice
+audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800
+audio/vnd.nuera.ecelp7470
+audio/vnd.nuera.ecelp9600
+audio/vnd.octel.sbc
+audio/vnd.qcelp
+audio/vnd.rhetorex.32kadpcm
+audio/vnd.vmx.cvsd
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-pn-realaudio ram rm
+audio/x-pn-realaudio-plugin rpm
+audio/x-realaudio ra
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm
+image/g3fax
+image/gif gif
+image/ief ief
+image/jpeg jpeg jpg jpe
+image/naplps
+image/png png
+image/prs.btif
+image/prs.pti
+image/tiff tiff tif
+image/vnd.cns.inf2
+image/vnd.dwg
+image/vnd.dxf
+image/vnd.fastbidsheet
+image/vnd.fpx
+image/vnd.fst
+image/vnd.fujixerox.edmics-mmr
+image/vnd.fujixerox.edmics-rlc
+image/vnd.mix
+image/vnd.net-fpx
+image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff
+image/x-cmu-raster ras
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+message/delivery-status
+message/disposition-notification
+message/external-body
+message/http
+message/news
+message/partial
+message/rfc822
+message/s-http
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.dwf
+model/vnd.flatland.3dml
+model/vnd.gdl
+model/vnd.gs-gdl
+model/vnd.gtw
+model/vnd.mts
+model/vnd.vtu
+model/vrml wrl vrml
+multipart/alternative
+multipart/appledouble
+multipart/byteranges
+multipart/digest
+multipart/encrypted
+multipart/form-data
+multipart/header-set
+multipart/mixed
+multipart/parallel
+multipart/related
+multipart/report
+multipart/signed
+multipart/voice-message
+text/calendar
+text/css css
+text/directory
+text/enriched
+text/html html htm
+text/parityfec
+text/plain asc txt
+text/prs.lines.tag
+text/rfc822-headers
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/t140
+text/uri-list
+text/vnd.DMClientScript
+text/vnd.IPTC.NITF
+text/vnd.IPTC.NewsML
+text/vnd.abc
+text/vnd.curl
+text/vnd.flatland.3dml
+text/vnd.fly
+text/vnd.fmi.flexstor
+text/vnd.in3d.3dml
+text/vnd.in3d.spot
+text/vnd.latex-z
+text/vnd.motorola.reflex
+text/vnd.ms-mediapackage
+text/vnd.wap.si
+text/vnd.wap.sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+text/x-server-parsed-html shtml
+text/xml xml xsl
+text/xml-external-parsed-entity
+video/mp4v-es
+video/mpeg mpeg mpg mpe
+video/parityfec
+video/pointer
+video/quicktime qt mov
+video/vnd.fvt
+video/vnd.motorola.video
+video/vnd.motorola.videop
+video/vnd.mpegurl mxu
+video/vnd.mts
+video/vnd.nokia.interleaved-multimedia
+video/vnd.vivo
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
+
+
+
diff --git a/lib/inets/test/httpc_SUITE_data/redirect.html b/lib/inets/test/httpc_SUITE_data/redirect.html
new file mode 100644
index 0000000000..7034461ed6
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/redirect.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<meta http-eqviv="Refresh", content="10;URL=http://localhost:8888/dummy.html">
+<title>Redirect</title>
+
+</head>
+
+<body>
+<p> You will be redirected</p>
+</body> </html>
diff --git a/lib/inets/test/httpc_SUITE_data/ssl_client_cert.pem b/lib/inets/test/httpc_SUITE_data/ssl_client_cert.pem
new file mode 100644
index 0000000000..f274d2021d
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/ssl_client_cert.pem
@@ -0,0 +1,22 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBANz7eFvORmJDi1XJMM2U3uHC5wmp/DXTLMw08XaEvtZ73wgVg84E
+V0oyX3Kh1thRE3Hch9AyrHjgpizCj9/Ra38CAwEAAQJACzpz2SZYCTIpaEh6xFdm
+I86FcsZCXHHIeu/NvRntoHQ+nfM7Np379+z6XNJWIcWh/QgG/jNJalR1BO+eyc6/
+YQIhAP3m8M0LDxJwSgHFtGAGatQqaqw9l48Kq5xdMFqvdpiHAiEA3s7lld6yCJYu
+6q7fZjTH+eKUwgg0vpgJutP7Fsok60kCIHHesQBEhW3vjkFdOZgXSLH+k/jLZr1w
+O6bU5GrHZpjhAiEAyTvGYcjDtTunXjDY9l+fadK6FlEBCk8ZIpNIiTnDhHkCIQDr
+QxxLLuNHRj8iWNbuVVZ99SJy8zC33pMgPFaFKaZesQ==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIB7jCCAZgCAQAwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlNFMRIwEAYD
+VQQHEwlTdG9ja2hvbG0xETAPBgNVBAoTCEVyaWNzc29uMQwwCgYDVQQLEwNFVFgx
+FjAUBgNVBAMTDUhlbGVuIEFpcml5YW4xJTAjBgkqhkiG9w0BCQEWFmhlbGVuQGVy
+aXguZXJpY3Nzb24uc2UwHhcNOTcwNzI4MDcxNDI1WhcNOTgxMjEwMDcxNDI1WjCB
+gTELMAkGA1UEBhMCU0UxEjAQBgNVBAcTCVN0b2NraG9sbTERMA8GA1UEChMIRXJp
+Y3Nzb24xDDAKBgNVBAsTA0VUWDEWMBQGA1UEAxMNSGVsZW4gQWlyaXlhbjElMCMG
+CSqGSIb3DQEJARYWaGVsZW5AZXJpeC5lcmljc3Nvbi5zZTBcMA0GCSqGSIb3DQEB
+AQUAA0sAMEgCQQDc+3hbzkZiQ4tVyTDNlN7hwucJqfw10yzMNPF2hL7We98IFYPO
+BFdKMl9yodbYURNx3IfQMqx44KYswo/f0Wt/AgMBAAEwDQYJKoZIhvcNAQEEBQAD
+QQC2++hLIaQJ4ChCjFE9UCfXO9cZ3Vq/FT9VjE+G4MRBDo4LQ5mBKNXcPF6EFZmi
+7XrlvopXkVPlRguTi2SLRPkY
+-----END CERTIFICATE-----
diff --git a/lib/inets/test/httpc_SUITE_data/ssl_server_cert.pem b/lib/inets/test/httpc_SUITE_data/ssl_server_cert.pem
new file mode 100644
index 0000000000..f01b6c992b
--- /dev/null
+++ b/lib/inets/test/httpc_SUITE_data/ssl_server_cert.pem
@@ -0,0 +1,22 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOQIBAAJBAMe2WhP6s+JeKOwWPEjI9susfN4Vjn2dd1X4QUlOETcWVLoF916m
+M4JU+ms7+ciMR8GRNCsIeqZGY8/GSqm74ccCAwEAAQJAF08YKlbLYfM9cXiS5qfV
+7iWemUkIzW5wfC8yZ3zeE4Cp6R9ViUfs/dadQ/23Cw0Bpo2t8UdTUdCa4KpmqOem
+cQIhAOnxTWZ5eo6h6PXDp7L5FZUACg8+wT3qf5f2is2mbSZPAiEA2orUY8JZDTSk
+Rm7q9WxLiLNtORsXdTCmnCWhqBOYpwkCIErdowRxScxNekz0IT3AQqzdR1rbnWHg
+IpcSGhd39CQ3AiA1XvQxjLP8wp9fyBS/bPwhXVhOOuyGpSP7PEF3b5m3KQIgGQWc
+/a5wuWx3pc3mLx0ILwNoJr2ubFEuW1PJPsPJPv0=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIB7jCCAZgCAQAwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlNFMRIwEAYD
+VQQHEwlTdG9ja2hvbG0xETAPBgNVBAoTCEVyaWNzc29uMQwwCgYDVQQLEwNFVFgx
+FjAUBgNVBAMTDUhlbGVuIEFpcml5YW4xJTAjBgkqhkiG9w0BCQEWFmhlbGVuQGVy
+aXguZXJpY3Nzb24uc2UwHhcNOTcwNzI4MDcyMTAwWhcNOTgxMjEwMDcyMTAwWjCB
+gTELMAkGA1UEBhMCU0UxEjAQBgNVBAcTCVN0b2NraG9sbTERMA8GA1UEChMIRXJp
+Y3Nzb24xDDAKBgNVBAsTA0VUWDEWMBQGA1UEAxMNSGVsZW4gQWlyaXlhbjElMCMG
+CSqGSIb3DQEJARYWaGVsZW5AZXJpeC5lcmljc3Nvbi5zZTBcMA0GCSqGSIb3DQEB
+AQUAA0sAMEgCQQDHtloT+rPiXijsFjxIyPbLrHzeFY59nXdV+EFJThE3FlS6Bfde
+pjOCVPprO/nIjEfBkTQrCHqmRmPPxkqpu+HHAgMBAAEwDQYJKoZIhvcNAQEEBQAD
+QQCnU1TkxmfbLdUwjdECb5x9QHCevAR7AmTms4Csn2oOEyPX+bgF2d94xhrV1sxO
+Rs0yigk1PtN17Ci0Dey0LYkR
+-----END CERTIFICATE-----
diff --git a/lib/inets/test/httpc_cookie_SUITE.erl b/lib/inets/test/httpc_cookie_SUITE.erl
new file mode 100644
index 0000000000..ad5df656c6
--- /dev/null
+++ b/lib/inets/test/httpc_cookie_SUITE.erl
@@ -0,0 +1,451 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(httpc_cookie_SUITE).
+
+-include("test_server.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+
+%% Test server specific exports
+-export([all/1, init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases must be exported.
+-export([session_cookies_only/1, netscape_cookies/1,
+ cookie_cancel/1, cookie_expires/1, persistent_cookie/1,
+ domain_cookie/1, secure_cookie/1, update_cookie/1,
+ update_cookie_session/1, cookie_attributes/1]).
+
+-define(URL, "http://myhost.cookie.test.org").
+-define(URL_DOMAIN, "http://myhost2.cookie.test.org").
+-define(URL_SECURE, "https://myhost.cookie.test.org").
+
+%% Test server callback functions
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(session_cookies_only = Case, Config0) ->
+ tsp("init_per_testcase(~p) -> entry with"
+ "~n Config0: ~p", [Case, Config0]),
+ Config = init_workdir(Case, Config0),
+ application:start(inets),
+ http:set_options([{cookies, verify}]),
+ watch_dog(Config);
+
+init_per_testcase(Case, Config0) ->
+ tsp("init_per_testcase(~p) -> entry with"
+ "~n Config0: ~p", [Case, Config0]),
+ Config = init_workdir(Case, Config0),
+ CaseDir = ?config(case_top_dir, Config),
+ application:load(inets),
+ application:set_env(inets, services, [{httpc, {default, CaseDir}}]),
+ application:start(inets),
+ http:set_options([{cookies, verify}]),
+ watch_dog(Config).
+
+watch_dog(Config) ->
+ Dog = test_server:timetrap(inets_test_lib:minutes(10)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ [{watchdog, Dog} | NewConfig].
+
+init_workdir(Case, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ SuiteTopDir = filename:join(PrivDir, ?MODULE),
+ case file:make_dir(SuiteTopDir) of
+ ok ->
+ ok;
+ {error, eexist} ->
+ ok;
+ Error ->
+ tsf({failed_creating_subsuite_top_dir, Error})
+ end,
+
+ CaseTopDir = filename:join(SuiteTopDir, Case),
+ ?line ok = file:make_dir(CaseTopDir),
+ [{suite_top_dir, SuiteTopDir},
+ {case_top_dir, CaseTopDir} | Config].
+
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ tsp("end_per_testcase(~p) -> entry with"
+ "~n Config: ~p", [Case, Config]),
+ application:stop(inets),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Describe the main purpose of this suite"];
+
+all(suite) ->
+ [
+ session_cookies_only,
+ netscape_cookies,
+ cookie_cancel,
+ cookie_expires,
+ persistent_cookie,
+ domain_cookie,
+ secure_cookie,
+ update_cookie,
+ update_cookie_session,
+ cookie_attributes
+ ].
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+session_cookies_only(doc) ->
+ ["Test that all cookies are handled as session cookies if there"
+ "does not exist a directory to save presitent cookies in."];
+session_cookies_only(suite) ->
+ [];
+session_cookies_only(Config) when is_list(Config) ->
+ tsp("session_cookies_only -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ ";max-age=60000"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=0; test_cookie=true; $Path=/"}
+ = http:cookie_header(?URL),
+ application:stop(inets),
+ application:start(inets),
+ {"cookie",""} = http:cookie_header(?URL),
+
+ tsp("session_cookies_only -> Cookies 2: ~p", [httpc:which_cookies()]),
+ ok.
+
+netscape_cookies(doc) ->
+ ["Test that the old (original) format of cookies are accepted."];
+netscape_cookies(suite) ->
+ [];
+netscape_cookies(Config) when is_list(Config) ->
+ tsp("netscape_cookies -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ Expires = future_netscape_date(),
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/; "
+ "expires=" ++ Expires}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=0; test_cookie=true; $Path=/"} =
+ http:cookie_header(?URL),
+
+ tsp("netscape_cookies -> Cookies 2: ~p", [httpc:which_cookies()]),
+ ok.
+
+cookie_cancel(doc) ->
+ ["A cookie can be canceld by sending the same cookie with max-age=0 "
+ "this test cheks that cookie is canceled."];
+cookie_cancel(suite) ->
+ [];
+cookie_cancel(Config) when is_list(Config) ->
+ tsp("cookie_cancel -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ "max-age=60000"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=0; test_cookie=true; $Path=/"}
+ = http:cookie_header(?URL),
+ NewSetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ "max-age=0"}],
+ http:verify_cookies(NewSetCookieHeaders, ?URL),
+ {"cookie", ""} = http:cookie_header(?URL),
+
+ tsp("cookie_cancel -> Cookies 2: ~p", [httpc:which_cookies()]),
+ ok.
+
+cookie_expires(doc) ->
+ ["Test that a cookie is not used when it has expired"];
+cookie_expires(suite) ->
+ [];
+cookie_expires(Config) when is_list(Config) ->
+ tsp("cookie_expires -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ "max-age=5"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=0; test_cookie=true; $Path=/"}
+ = http:cookie_header(?URL),
+ test_server:sleep(10000),
+ {"cookie", ""} = http:cookie_header(?URL),
+
+ tsp("cookie_expires -> Cookies 2: ~p", [httpc:which_cookies()]),
+ ok.
+
+persistent_cookie(doc) ->
+ ["Test domian cookie attribute"];
+persistent_cookie(suite) ->
+ [];
+persistent_cookie(Config) when is_list(Config)->
+ tsp("persistent_cookie -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ "max-age=60000"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=0; test_cookie=true; $Path=/"} =
+ http:cookie_header(?URL),
+ CaseDir = ?config(case_top_dir, Config),
+ application:stop(inets),
+ application:load(inets),
+ application:set_env(inets, services, [{httpc, {default, CaseDir}}]),
+ application:start(inets),
+ http:set_options([{cookies, enabled}]),
+ {"cookie","$Version=0; test_cookie=true; $Path=/"} = http:cookie_header(?URL),
+
+ tsp("persistent_cookie -> Cookies 2: ~p", [httpc:which_cookies()]),
+ ok.
+
+
+domain_cookie(doc) ->
+ ["Test the domian cookie attribute"];
+domain_cookie(suite) ->
+ [];
+domain_cookie(Config) when is_list(Config) ->
+ tsp("domain_cookie -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ "domain=.cookie.test.org"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=0; test_cookie=true; $Path=/; "
+ "$Domain=.cookie.test.org"} =
+ http:cookie_header(?URL_DOMAIN),
+
+ tsp("domain_cookie -> Cookies 2: ~p", [httpc:which_cookies()]),
+ ok.
+
+
+secure_cookie(doc) ->
+ ["Test the secure cookie attribute"];
+secure_cookie(suite) ->
+ [];
+secure_cookie(Config) when is_list(Config) ->
+ tsp("secure_cookie -> entry with"
+ "~n Config: ~p", [Config]),
+
+ inets:enable_trace(max, io, httpc),
+
+ %% httpc:reset_cookies(),
+
+ tsp("secure_cookie -> Cookies 1: ~p", [httpc:which_cookies()]),
+
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/; secure"}],
+ tsp("secure_cookie -> verify cookies (1)"),
+ ok = http:verify_cookies(SetCookieHeaders, ?URL),
+
+ tsp("secure_cookie -> Cookies 2: ~p", [httpc:which_cookies()]),
+
+ tsp("secure_cookie -> check cookie (secure)"),
+ check_cookie("$Version=0; test_cookie=true; $Path=/", ?URL_SECURE),
+
+ tsp("secure_cookie -> check cookie (plain)"),
+ check_cookie("", ?URL),
+
+ tsp("secure_cookie -> verify cookies (2)"),
+ SetCookieHeaders1 = [{"set-cookie", "test1_cookie=true; path=/; secure"}],
+ ok = http:verify_cookies(SetCookieHeaders1, ?URL),
+
+ tsp("secure_cookie -> Cookies 3: ~p", [httpc:which_cookies()]),
+
+ tsp("secure_cookie -> cookie header (3)"),
+ check_cookie("$Version=0; test_cookie=true; $Path=/; "
+ "test1_cookie=true; $Path=/",
+ ?URL_SECURE),
+%% {"cookie","$Version=0; test_cookie=true; $Path=/; "
+%% "test1_cookie=true; $Path=/"} = http:cookie_header(?URL_SECURE),
+
+ tsp("secure_cookie -> Cookies 4: ~p", [httpc:which_cookies()]),
+
+ inets:disable_trace(),
+ tsp("secure_cookie -> done"),
+ ok.
+
+update_cookie(doc)->
+ ["Test that a cookie can be updated."];
+update_cookie(suite) ->
+ [];
+update_cookie(Config) when is_list(Config)->
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/;"
+ "max-age=6500"},
+ {"set-cookie", "test_cookie2=true; path=/;"
+ "max-age=6500"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie", "$Version=0; test_cookie2=true; $Path=/; "
+ "test_cookie=true; $Path=/"} = http:cookie_header(?URL),
+ NewSetCookieHeaders = [{"set-cookie", "test_cookie=false; "
+ "path=/;max-age=6500"}],
+ http:verify_cookies(NewSetCookieHeaders, ?URL),
+ {"cookie", "$Version=0; test_cookie2=true; $Path=/; "
+ "test_cookie=false; $Path=/"} = http:cookie_header(?URL).
+
+update_cookie_session(doc)->
+ ["Test that a cookie can be updated."];
+update_cookie_session(suite) ->
+ [];
+update_cookie_session(Config) when is_list(Config)->
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true; path=/"},
+ {"set-cookie", "test_cookie2=true; path=/"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie", "$Version=0; test_cookie2=true; $Path=/; "
+ "test_cookie=true; $Path=/"} = http:cookie_header(?URL),
+ NewSetCookieHeaders = [{"set-cookie", "test_cookie=false; path=/"}],
+ http:verify_cookies(NewSetCookieHeaders, ?URL),
+ {"cookie", "$Version=0; test_cookie2=true; $Path=/; "
+ "test_cookie=false; $Path=/"} = http:cookie_header(?URL).
+
+
+cookie_attributes(doc) ->
+ ["Test attribute not covered by the other test cases"];
+cookie_attributes(suite) ->
+ [];
+cookie_attributes(Config) when is_list(Config) ->
+ SetCookieHeaders = [{"set-cookie", "test_cookie=true;version=1;"
+ "comment=foobar; "%% Comment
+ "foo=bar;" %% Nonsense should be ignored
+ "max-age=60000"}],
+ http:verify_cookies(SetCookieHeaders, ?URL),
+ {"cookie","$Version=1; test_cookie=true"} = http:cookie_header(?URL),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+check_cookie(Expect, URL) ->
+ case http:cookie_header(URL) of
+ {"cookie", Expect} ->
+ ok;
+ {"cookie", Unexpected} ->
+ case lists:prefix(Expect, Unexpected) of
+ true ->
+ Extra = Unexpected -- Expect,
+ tsf({extra_cookie_info, Extra});
+ false ->
+ tsf({unknown_cookie, Expect, Unexpected})
+ end;
+ Bad ->
+ tsf({bad_cookies, Bad})
+ end.
+
+
+future_netscape_date() ->
+ [Day, DD, Mon, YYYY] = netscape_date(date()),
+ lists:flatten(io_lib:format("~s, ~s ~s ~s 12:30:00 GMT",
+ [Day, DD, Mon, YYYY])).
+
+netscape_date({Year, 12, _}) ->
+ NewYear = Year + 1,
+ NewMonth = 1,
+ NewDay = calendar:last_day_of_the_month(NewYear, NewMonth),
+ WeekDay = calendar:day_of_the_week(NewYear, NewMonth, NewDay),
+ WeekDayNrStr = day_nr_str(NewDay),
+ NewDayStr = week_day_str(WeekDay),
+ MonthStr = month_str(NewMonth),
+ [NewDayStr, WeekDayNrStr, MonthStr, integer_to_list(NewYear)];
+
+netscape_date({Year, Month, _}) ->
+ NewMonth = Month + 1,
+ NewDay = calendar:last_day_of_the_month(Year, NewMonth),
+ WeekDay = calendar:day_of_the_week(Year, NewMonth, NewDay),
+ WeekDayNrStr = day_nr_str(NewDay),
+ NewDayStr = week_day_str(WeekDay),
+ MonthStr = month_str(NewMonth),
+ [NewDayStr, WeekDayNrStr, MonthStr, integer_to_list(Year)].
+
+week_day_str(1) ->
+ "Mon";
+week_day_str(2) ->
+ "Tus";
+week_day_str(3) ->
+ "Wdy";
+week_day_str(4) ->
+ "Thu";
+week_day_str(5) ->
+ "Fri";
+week_day_str(6) ->
+ "Sat";
+week_day_str(7) ->
+ "Sun".
+
+day_nr_str(1) ->
+ "01";
+day_nr_str(2) ->
+ "02";
+day_nr_str(3) ->
+ "03";
+day_nr_str(4) ->
+ "04";
+day_nr_str(5) ->
+ "05";
+day_nr_str(6) ->
+ "06";
+day_nr_str(7) ->
+ "07";
+day_nr_str(8) ->
+ "08";
+day_nr_str(0) ->
+ "09";
+day_nr_str(N) ->
+ integer_to_list(N).
+
+month_str(1) ->"Jan";
+month_str(2) ->"Feb";
+month_str(3) ->"Mar";
+month_str(4) ->"Apr";
+month_str(5) ->"May";
+month_str(6) ->"Jun";
+month_str(7) ->"Jul";
+month_str(8) ->"Aug";
+month_str(9) ->"Sep";
+month_str(10) ->"Oct";
+month_str(11) ->"Nov";
+month_str(12) ->"Dec".
+
+
+tsp(F) ->
+ tsp(F, []).
+tsp(F, A) ->
+ test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
+tsf(Reason) ->
+ test_server:fail(Reason).
+
diff --git a/lib/inets/test/httpc_internal.hrl b/lib/inets/test/httpc_internal.hrl
new file mode 120000
index 0000000000..bef2c94879
--- /dev/null
+++ b/lib/inets/test/httpc_internal.hrl
@@ -0,0 +1 @@
+../src/http_client/httpc_internal.hrl \ No newline at end of file
diff --git a/lib/inets/test/httpd_1_1.erl b/lib/inets/test/httpd_1_1.erl
new file mode 100644
index 0000000000..055d034bec
--- /dev/null
+++ b/lib/inets/test/httpd_1_1.erl
@@ -0,0 +1,494 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(httpd_1_1).
+-author('[email protected]').
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-export([host/4, chunked/4, expect/4, range/4, if_test/5, http_trace/4,
+ head/4, mod_cgi_chunked_encoding_test/5]).
+
+%% -define(all_keys_lower_case,true).
+-ifndef(all_keys_lower_case).
+-define(CONTENT_LENGTH, "Content-Length: ").
+-define(CONTENT_RANGE, "Content-Range: ").
+-define(CONTENT_TYPE, "Content-Type: ").
+-else.
+-define(CONTENT_LENGTH, "content-length:").
+-define(CONTENT_RANGE, "content-range:").
+-define(CONTENT_TYPE, "content-type:").
+-endif.
+
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+host(Type, Port, Host, Node) ->
+ %% No host needed for HTTP/1.0
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ %% No host must generate an error
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\n\r\n",
+ [{statuscode, 400}]),
+
+ %% If it is a fully qualified URL no host is needed
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET HTTP://"++ Host ++ ":" ++
+ integer_to_list(Port)++
+ "/ HTTP/1.1\r\n\r\n",
+ [{statuscode, 200}]),
+
+ %% If both look at the url.
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET HTTP://"++ Host ++ ":"++
+ integer_to_list(Port) ++
+ "/ HTTP/1.1\r\nHost:"++ Host ++
+ "\r\n\r\n",[{statuscode, 200}]),
+
+ %% Allow the request if its a Host field
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:"++
+ Host ++ "\r\n\r\n",
+ [{statuscode, 200}]),
+ ok.
+
+chunked(Type, Port, Host, Node)->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\n"
+ "Host:"++ Host ++"\r\n"
+ "Transfer-Encoding:chunked\r\n"
+ "\r\n"
+ "A\r\n"
+ "1234567890\r\n"
+ "4\r\n"
+ "HEJ!\r\n"
+ "0\r\n\r\n",
+ [{statuscode, 200}]),
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\n"
+ "Host:"++ Host ++"\r\n"
+ "Transfer-Encoding:chunked\r\n"
+ "Trailer:Content-Type\r\n"
+ "\r\n"
+ "A\r\n"
+ "1234567890\r\n"
+ "4\r\n"
+ "HEJ!\r\n"
+ "0\r\n"
+ "Content-Type:text/plain\r\n\r\n",
+ [{statuscode, 200}]),
+ ok.
+
+expect(Type, Port, Host, Node)->
+ Request="GET / HTTP/1.1\r\nHost:" ++ Host ++
+ "\r\nContent-Length:22\r\nExpect:100-continue\r\n\r\n",
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ Request,
+ [{statuscode, 100}]).
+range(Type, Port, Host, Node)->
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /range.txt HTTP/1.1\r\nHost:"
+ ++ Host
+ ++ "\r\nRange:bytes=110-120\r\n\r\n",
+ [{statuscode,416}]),
+ %%The simples of all range request a range
+ Request1="GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++
+ "\r\nRange:bytes=0-9\r\n\r\n",
+ {ok, Socket1} = inets_test_lib:connect_byte(Type, Host, Port),
+ inets_test_lib:send(Type, Socket1,Request1),
+ ok = validateRangeRequest(Socket1,[],"1234567890",$2,$0,$6),
+ inets_test_lib:close(Type,Socket1),
+
+ %% Request the end of the file
+ Request2 =
+ "GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++
+ "\r\nRange:bytes=90-\r\n\r\n",
+
+ {ok, Socket2} = inets_test_lib:connect_byte(Type, Host, Port),
+ inets_test_lib:send(Type, Socket2, Request2),
+ ok = validateRangeRequest(Socket2,[],"1234567890",$2,$0,$6),
+ inets_test_lib:close(Type,Socket2),
+
+ %% The last byte in the file
+ Request3 =
+ "GET /range.txt HTTP/1.1\r\nHost:"++
+ Host ++ "\r\nRange:bytes=-1\r\n\r\n",
+ {ok, Socket3} = inets_test_lib:connect_byte(Type, Host, Port),
+ inets_test_lib:send(Type, Socket3,Request3),
+ ok = validateRangeRequest(Socket3,[],"0",$2,$0,$6),
+ inets_test_lib:close(Type, Socket3),
+
+ %%Multi Range
+ Request4 = "GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++
+ "\r\nRange:bytes=0-0,2-2,-1\r\n\r\n",
+ {ok, Socket4} = inets_test_lib:connect_byte(Type, Host, Port),
+ inets_test_lib:send(Type, Socket4, Request4),
+ ok = validateRangeRequest(Socket4,[],"130",$2,$0,$6),
+ inets_test_lib:close(Type, Socket4).
+
+if_test(Type, Port, Host, Node, DocRoot)->
+ {ok, FileInfo} =
+ file:read_file_info(filename:join([DocRoot,"index.html"])),
+ CreatedSec =
+ calendar:datetime_to_gregorian_seconds(FileInfo#file_info.mtime),
+
+ Mod = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
+ CreatedSec-1)),
+
+ %% Test that we get the data when the file is modified
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:" ++ Host ++
+ "\r\nIf-Modified-Since:" ++
+ Mod ++ "\r\n\r\n",
+ [{statuscode, 200}]),
+ Mod1 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
+ CreatedSec+100)),
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET / HTTP/1.1\r\nHost:"
+ ++ Host ++"\r\nIf-Modified-Since:"
+ ++ Mod1 ++"\r\n\r\n",
+ [{statuscode, 304}]),
+
+ Mod2 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
+ CreatedSec+1)),
+ %% Control that the If-Unmodified-Header lmits the response
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET / HTTP/1.1\r\nHost:"
+ ++ Host ++
+ "\r\nIf-Unmodified-Since:" ++ Mod2
+ ++ "\r\n\r\n",
+ [{statuscode, 200}]),
+ Mod3 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
+ CreatedSec-1)),
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:"
+ ++ Host ++
+ "\r\nIf-Unmodified-Since:"++ Mod3
+ ++"\r\n\r\n",
+ [{statuscode, 412}]),
+
+ %% Control that we get the body when the etag match
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:" ++ Host
+ ++"\r\n"++
+ "If-Match:"++
+ httpd_util:create_etag(FileInfo)++
+ "\r\n\r\n",
+ [{statuscode, 200}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:" ++
+ Host ++ "\r\n"++
+ "If-Match:NotEtag\r\n\r\n",
+ [{statuscode, 412}]),
+
+ %% Control the response when the if-none-match header is there
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:"
+ ++ Host ++"\r\n"++
+ "If-None-Match:NoTaag," ++
+ httpd_util:create_etag(FileInfo) ++
+ "\r\n\r\n",
+ [{statuscode, 304}]),
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.1\r\nHost:"
+ ++ Host ++ "\r\n"++
+ "If-None-Match:NotEtag,"
+ "NeihterEtag\r\n\r\n",
+ [{statuscode,200}]).
+
+http_trace(Type, Port, Host, Node)->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "TRACE / HTTP/1.1\r\n" ++
+ "Host:" ++ Host ++ "\r\n" ++
+ "Max-Forwards:2\r\n\r\n",
+ [{statuscode, 200}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "TRACE / HTTP/1.0\r\n\r\n",
+ [{statuscode, 501},
+ {version, "HTTP/1.0"}]).
+head(Type, Port, Host, Node)->
+ %% mod_include
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD /fsize.shtml HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD /fsize.shtml HTTP/1.1\r\nhost:" ++
+ Host ++ "\r\n\r\n", [{statuscode, 200}]),
+ %% mod_esi
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD /cgi-bin/erl/httpd_example/newformat"
+ " HTTP/1.0\r\n\r\n", [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD /cgi-bin/erl/httpd_example/newformat "
+ "HTTP/1.1\r\nhost:" ++ Host ++ "\r\n\r\n",
+ [{statuscode, 200}]),
+ %% mod_cgi
+ Script =
+ case test_server:os_type() of
+ {win32, _} ->
+ "printenv.bat";
+ _ ->
+ "printenv.sh"
+ end,
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,"HEAD /cgi-bin/"
+ ++ Script ++ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node, "HEAD /cgi-bin/"
+ ++ Script ++ " HTTP/1.1\r\nhost:" ++
+ Host ++ "\r\n\r\n",
+ [{statuscode, 200}]).
+
+mod_cgi_chunked_encoding_test(_, _, _, _, [])->
+ ok;
+mod_cgi_chunked_encoding_test(Type, Port, Host, Node, [Request| Rest])->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Request,
+ [{statuscode, 200}]),
+ mod_cgi_chunked_encoding_test(Type, Port, Host, Node, Rest).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+validateRangeRequest(Socket,Response,ValidBody,C,O,DE)->
+ receive
+ {tcp,Socket,Data} ->
+ case string:str(Data,"\r\n") of
+ 0->
+ validateRangeRequest(Socket,
+ Response ++ Data,
+ ValidBody, C, O, DE);
+ _N ->
+ case Response ++ Data of
+ [$H,$T,$T,$P,$/,$1,$.,$1,$ ,C,O,DE | _Rest]->
+ case [C,O,DE] of
+ "206" ->
+ validateRangeRequest1(Socket,
+ Response ++ Data,
+ ValidBody);
+ _ ->
+ bad_code
+ end;
+ _->
+ error
+ end
+ end;
+ _Error ->
+ error
+ end.
+
+validateRangeRequest1(Socket, Response, ValidBody) ->
+ case end_of_header(Response) of
+ false ->
+ receive
+ {tcp,Socket,Data} ->
+ validateRangeRequest1(Socket, Response ++ Data,
+ ValidBody);
+ _->
+ error
+ end;
+ {true, Head1, Body, _Size} ->
+ %% In this case size will be 0 if it is a multipart so
+ %% don't use it.
+ validateRangeRequest2(Socket, Head1, Body, ValidBody,
+ getRangeSize(Head1))
+ end.
+
+validateRangeRequest2(Socket, Head, Body, ValidBody, {multiPart,Boundary})->
+ case endReached(Body,Boundary) of
+ true ->
+ validateMultiPartRangeRequest(Body, ValidBody, Boundary);
+ false->
+ receive
+ {tcp, Socket, Data} ->
+ validateRangeRequest2(Socket, Head, Body ++ Data,
+ ValidBody, {multiPart, Boundary});
+ {tcp_closed, Socket} ->
+ error;
+ _ ->
+ error
+ end
+ end;
+
+validateRangeRequest2(Socket, Head, Body, ValidBody, BodySize)
+ when is_integer(BodySize) ->
+ case length(Body) of
+ Size when Size =:= BodySize ->
+ case Body of
+ ValidBody ->
+ ok;
+ Body ->
+ error
+ end;
+ Size when Size < BodySize ->
+ receive
+ {tcp, Socket, Data} ->
+ validateRangeRequest2(Socket, Head,
+ Body ++ Data, ValidBody, BodySize);
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
+
+validateMultiPartRangeRequest(Body, ValidBody, Boundary)->
+ case inets_regexp:split(Body,"--"++Boundary++"--") of
+ %%Last is the epilogue and must be ignored
+ {ok,[First | _Last]}->
+ %%First is now the actuall http request body.
+ case inets_regexp:split(First, "--" ++ Boundary) of
+ %%Parts is now a list of ranges and the heads for each range
+ %%Gues we try to split out the body
+ {ok,Parts}->
+ case lists:flatten(lists:map(fun splitRange/1,Parts)) of
+ ValidBody->
+ ok;
+ ParsedBody->
+ error = ParsedBody
+ end
+ end;
+ _ ->
+ error
+ end.
+
+
+splitRange(Part)->
+ case inets_regexp:split(Part, "\r\n\r\n") of
+ {ok,[_, Body]} ->
+ string:substr(Body, 1, length(Body) - 2);
+ _ ->
+ []
+ end.
+
+endReached(Body, Boundary)->
+ EndBound = "--" ++ Boundary ++ "--",
+ case string:str(Body, EndBound) of
+ 0 ->
+ false;
+ _ ->
+ true
+ end.
+
+getRangeSize(Head)->
+ case controlMimeType(Head) of
+ {multiPart, BoundaryString}->
+ {multiPart, BoundaryString};
+ _X1 ->
+ case inets_regexp:match(Head, ?CONTENT_RANGE "bytes=.*\r\n") of
+ {match, Start, Lenght} ->
+ %% Get the range data remove the fieldname and the
+ %% end of line.
+ RangeInfo = string:substr(Head, Start + 20,
+ Lenght - (20 - 2)),
+ rangeSize(RangeInfo);
+ _X2 ->
+ error
+ end
+ end.
+%%RangeInfo is NNN1-NNN2/NNN3
+%%NNN1=RangeStartByte
+%%NNN2=RangeEndByte
+%%NNN3=total amount of bytes in file
+rangeSize([$=|RangeInfo]) ->
+ rangeSize(RangeInfo);
+rangeSize(RangeInfo) ->
+ StartByte = lists:takewhile(fun(X)->
+ num(X, true)
+ end, RangeInfo),
+ RangeInfo2 = string:substr(RangeInfo, length(StartByte) + 2),
+ EndByte = lists:takewhile(fun(X)->
+ num(X,true)
+ end, RangeInfo2),
+ case list_to_integer(EndByte) - list_to_integer(StartByte) of
+ Val when is_number(Val) ->
+ %%Add one since it is startByte to endbyte ie 0-0 is 1
+ %%byte 0-99 is 100 bytes
+ Val + 1;
+ _Val ->
+ error
+ end.
+
+num(CharVal, RetVal) when (CharVal >= 48) andalso (CharVal =< 57) ->
+ RetVal;
+num(_CharVal, true) ->
+ false;
+num(_CharVal, false) ->
+ true.
+
+controlMimeType(Head)->
+ case inets_regexp:match(Head,?CONTENT_TYPE "multipart/byteranges.*\r\n") of
+ {match,Start,Length}->
+ FieldNameLen = length(?CONTENT_TYPE "multipart/byteranges"),
+ case clearBoundary(string:substr(Head, Start + FieldNameLen,
+ Length - (FieldNameLen+2))) of
+ error ->
+ error;
+ BoundaryStr ->
+ {multiPart,BoundaryStr}
+ end;
+ nomatch->
+ 0;
+ _ ->
+ error
+ end.
+
+clearBoundary(Boundary)->
+ case inets_regexp:match(Boundary, "boundary=.*\$") of
+ {match, Start1, Length1}->
+ BoundLen = length("boundary="),
+ string:substr(Boundary, Start1 + BoundLen, Length1 - BoundLen);
+ _ ->
+ error
+ end.
+
+
+end_of_header(HeaderPart) ->
+ case httpd_util:split(HeaderPart,"\r\n\r\n",2) of
+ {ok, [Head, Body]} ->
+ {true, Head, Body, get_body_size(Head)};
+ _Pos ->
+ false
+ end.
+
+get_body_size(Head) ->
+ case inets_regexp:match(Head,?CONTENT_LENGTH ".*\r\n") of
+ {match, Start, Length} ->
+ %% 15 is length of Content-Length,
+ %% 17 Is length of Content-Length and \r\
+ S = list_to_integer(
+ string:strip(string:substr(Head, Start + 15, Length-17))),
+ S;
+ _->
+ 0
+ end.
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
new file mode 100644
index 0000000000..7403d4a643
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -0,0 +1,2081 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(httpd_SUITE).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("inets_test_lib.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+%% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, end_per_testcase/2,
+ init_per_suite/1, end_per_suite/1]).
+
+%% Test cases must be exported.
+-export([ip/1, ssl/1, http_1_1_ip/1, http_1_0_ip/1, http_0_9_ip/1,
+ ipv6/1, tickets/1]).
+
+%% Core Server tests
+-export([ip_mod_alias/1, ip_mod_actions/1, ip_mod_security/1, ip_mod_auth/1,
+ ip_mod_auth_api/1, ip_mod_auth_mnesia_api/1,
+ ip_mod_htaccess/1, ip_mod_cgi/1, ip_mod_esi/1,
+ ip_mod_get/1, ip_mod_head/1, ip_mod_all/1, ip_load_light/1,
+ ip_load_medium/1, ip_load_heavy/1, ip_dos_hostname/1,
+ ip_time_test/1, ip_block_disturbing_idle/1,
+ ip_block_non_disturbing_idle/1, ip_block_503/1,
+ ip_block_disturbing_active/1, ip_block_non_disturbing_active/1,
+ ip_block_disturbing_active_timeout_not_released/1,
+ ip_block_disturbing_active_timeout_released/1,
+ ip_block_non_disturbing_active_timeout_not_released/1,
+ ip_block_non_disturbing_active_timeout_released/1,
+ ip_block_disturbing_blocker_dies/1,
+ ip_block_non_disturbing_blocker_dies/1,
+ ip_restart_no_block/1, ip_restart_disturbing_block/1,
+ ip_restart_non_disturbing_block/1
+ ]).
+
+-export([ssl_mod_alias/1, ssl_mod_actions/1, ssl_mod_security/1,
+ ssl_mod_auth/1, ssl_mod_auth_api/1,
+ ssl_mod_auth_mnesia_api/1, ssl_mod_htaccess/1,
+ ssl_mod_cgi/1, ssl_mod_esi/1, ssl_mod_get/1, ssl_mod_head/1,
+ ssl_mod_all/1, ssl_load_light/1, ssl_load_medium/1,
+ ssl_load_heavy/1, ssl_dos_hostname/1, ssl_time_test/1,
+ ssl_restart_no_block/1, ssl_restart_disturbing_block/1,
+ ssl_restart_non_disturbing_block/1, ssl_block_disturbing_idle/1,
+ ssl_block_non_disturbing_idle/1, ssl_block_503/1,
+ ssl_block_disturbing_active/1, ssl_block_non_disturbing_active/1,
+ ssl_block_disturbing_active_timeout_not_released/1,
+ ssl_block_disturbing_active_timeout_released/1,
+ ssl_block_non_disturbing_active_timeout_not_released/1,
+ ssl_block_non_disturbing_active_timeout_released/1,
+ ssl_block_disturbing_blocker_dies/1,
+ ssl_block_non_disturbing_blocker_dies/1]).
+
+%%% HTTP 1.1 tests
+-export([ip_host/1, ip_chunked/1, ip_expect/1, ip_range/1,
+ ip_if_test/1, ip_http_trace/1, ip_http1_1_head/1,
+ ip_mod_cgi_chunked_encoding_test/1]).
+
+%%% HTTP 1.0 tests
+-export([ip_head_1_0/1, ip_get_1_0/1, ip_post_1_0/1]).
+
+%%% HTTP 0.9 tests
+-export([ip_get_0_9/1]).
+
+%%% Ticket tests
+-export([ticket_5775/1,ticket_5865/1,ticket_5913/1,ticket_6003/1,
+ ticket_7304/1]).
+
+%%% Misc
+-export([ipv6_hostname/1, ipv6_address/1]).
+
+%% Help functions
+-export([cleanup_mnesia/0, setup_mnesia/0, setup_mnesia/1]).
+
+-define(IP_PORT, 8898).
+-define(SSL_PORT, 8899).
+-define(MAX_HEADER_SIZE, 256).
+-define(IPV6_LOCAL_HOST, "0:0:0:0:0:0:0:1").
+
+%% Minutes before failed auths timeout.
+-define(FAIL_EXPIRE_TIME,1).
+
+%% Seconds before successful auths timeout.
+-define(AUTH_TIMEOUT,5).
+
+-record(httpd_user, {user_name, password, user_data}).
+-record(httpd_group,{group_name, userlist}).
+
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test the http server in the intes application."];
+all(suite) ->
+ [
+ ip,
+ ssl,
+ http_1_1_ip,
+ http_1_0_ip,
+ http_0_9_ip,
+ %% ipv6,
+ tickets
+ ].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ io:format(user, "init_per_suite -> entry with"
+ "~n Config: ~p"
+ "~n", [Config]),
+
+ PrivDir = ?config(priv_dir, Config),
+ SuiteTopDir = filename:join(PrivDir, ?MODULE),
+ case file:make_dir(SuiteTopDir) of
+ ok ->
+ ok;
+ {error, eexist} ->
+ ok;
+ Error ->
+ throw({error, {failed_creating_suite_top_dir, Error}})
+ end,
+
+ [{suite_top_dir, SuiteTopDir},
+ {node, node()},
+ {host, inets_test_lib:hostname()},
+ {address, getaddr()} | Config].
+
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+
+end_per_suite(_Config) ->
+ %% SuiteTopDir = ?config(suite_top_dir, Config),
+ %% inets_test_lib:del_dirs(SuiteTopDir),
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ NewConfig = init_per_testcase2(Case, Config),
+ init_per_testcase3(Case, NewConfig).
+
+
+init_per_testcase2(Case, Config) ->
+
+ io:format(user, "~w:init_per_testcase2(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [?MODULE, Case, Config]),
+
+ IpNormal = integer_to_list(?IP_PORT) ++ ".conf",
+ IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf",
+ SslNormal = integer_to_list(?SSL_PORT) ++ ".conf",
+ SslHtacess = integer_to_list(?SSL_PORT) ++ "htacess.conf",
+
+ DataDir = ?config(data_dir, Config),
+ SuiteTopDir = ?config(suite_top_dir, Config),
+
+ io:format(user, "~w:init_per_testcase2(~w) -> "
+ "~n SuiteDir: ~p"
+ "~n DataDir: ~p"
+ "~n", [?MODULE, Case, SuiteTopDir, DataDir]),
+
+ TcTopDir = filename:join(SuiteTopDir, Case),
+ ?line ok = file:make_dir(TcTopDir),
+
+ io:format(user, "~w:init_per_testcase2(~w) -> "
+ "~n TcTopDir: ~p"
+ "~n", [?MODULE, Case, TcTopDir]),
+
+ DataSrc = filename:join([DataDir, "server_root"]),
+ ServerRoot = filename:join([TcTopDir, "server_root"]),
+
+ io:format(user, "~w:init_per_testcase2(~w) -> "
+ "~n DataSrc: ~p"
+ "~n ServerRoot: ~p"
+ "~n", [?MODULE, Case, DataSrc, ServerRoot]),
+
+ ok = file:make_dir(ServerRoot),
+ ok = file:make_dir(filename:join([TcTopDir, "logs"])),
+
+ NewConfig = [{tc_top_dir, TcTopDir}, {server_root, ServerRoot} | Config],
+
+ io:format(user, "~w:init_per_testcase2(~w) -> "
+ "copy DataSrc to ServerRoot~n",
+ [?MODULE, Case]),
+
+ inets_test_lib:copy_dirs(DataSrc, ServerRoot),
+
+ io:format(user, "~w:init_per_testcase2(~w) -> fix cgi~n",
+ [?MODULE, Case]),
+ EnvCGI = filename:join([ServerRoot, "cgi-bin", "printenv.sh"]),
+ {ok, FileInfo} = file:read_file_info(EnvCGI),
+ ok = file:write_file_info(EnvCGI,
+ FileInfo#file_info{mode = 8#00755}),
+
+ EchoCGI = case test_server:os_type() of
+ {win32, _} ->
+ "cgi_echo.exe";
+ _ ->
+ "cgi_echo"
+ end,
+ CGIDir = filename:join([ServerRoot, "cgi-bin"]),
+ inets_test_lib:copy_file(EchoCGI, DataDir, CGIDir),
+ NewEchoCGI = filename:join([CGIDir, EchoCGI]),
+ {ok, FileInfo1} = file:read_file_info(NewEchoCGI),
+ ok = file:write_file_info(NewEchoCGI,
+ FileInfo1#file_info{mode = 8#00755}),
+
+ %% To be used by IP test cases
+ io:format(user, "~w:init_per_testcase2(~w) -> ip testcase setups~n",
+ [?MODULE, Case]),
+ create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig],
+ normal_acess, IpNormal),
+ create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig],
+ mod_htaccess, IpHtacess),
+
+ %% To be used by SSL test cases
+ io:format(user, "~w:init_per_testcase2(~w) -> ssl testcase setups~n",
+ [?MODULE, Case]),
+ create_config([{port, ?SSL_PORT}, {sock_type, ssl} | NewConfig],
+ normal_acess, SslNormal),
+ create_config([{port, ?SSL_PORT}, {sock_type, ssl} | NewConfig],
+ mod_htaccess, SslHtacess),
+
+ %% To be used by IPv6 test cases. Case-clause is so that
+ %% you can do ts:run(inets, httpd_SUITE, <test case>)
+ %% for all cases except the ipv6 cases as they depend
+ %% on 'test_host_ipv6_only' that will only be present
+ %% when you run the whole test suite due to shortcomings
+ %% of the test server.
+ %% case (catch ?config(test_host_ipv6_only, Config)) of
+ %% {_,IPv6Host,IPv6Adress,_,_} ->
+ %% create_ipv6_config([{port, ?IP_PORT},
+ %% {sock_type, ip_comm} | NewConfig],
+ %% "ipv6_hostname.conf", IPv6Host),
+ %% create_ipv6_config([{port, ?IP_PORT},
+ %% {sock_type, ip_comm} | NewConfig],
+ %% "ipv6_address.conf", IPv6Adress);
+ %% _ ->
+ %% ok
+ %% end,
+
+ io:format(user, "~w:init_per_testcase2(~w) -> done~n",
+ [?MODULE, Case]),
+
+ NewConfig.
+
+
+init_per_testcase3(Case, Config) ->
+ io:format(user, "~w:init_per_testcase3(~w) -> entry with"
+ "~n Config: ~p", [?MODULE, Case, Config]),
+
+ %% Clean up (we do not want this clean up in end_per_testcase
+ %% if init_per_testcase crases for some testcase it will
+ %% have contaminated the environment and there will be no clean up.)
+ %% This init can take a few different paths so that one crashes
+ %% does not mean that all invocations will.
+
+ application:unset_env(inets, services),
+ application:stop(inets),
+ application:stop(ssl),
+ cleanup_mnesia(),
+
+ %% TraceLevel = max,
+ TraceLevel = 70,
+ TraceDest = io,
+ inets:enable_trace(TraceLevel, TraceDest),
+
+ %% Start initialization
+ io:format(user, "~w:init_per_testcase3(~w) -> start init",
+ [?MODULE, Case]),
+
+ Dog = test_server:timetrap(inets_test_lib:minutes(10)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ TcTopDir = ?config(tc_top_dir, Config),
+ CaseRest =
+ case atom_to_list(Case) of
+ "ip_mod_htaccess" ->
+ inets_test_lib:start_http_server(
+ filename:join(TcTopDir,
+ integer_to_list(?IP_PORT) ++
+ "htacess.conf")),
+ "mod_htaccess";
+ "ip_" ++ Rest ->
+ inets_test_lib:start_http_server(
+ filename:join(TcTopDir,
+ integer_to_list(?IP_PORT) ++ ".conf")),
+ Rest;
+ "ticket_5913" ->
+ HttpdOptions =
+ [{file,
+ filename:join(TcTopDir,
+ integer_to_list(?IP_PORT) ++ ".conf")},
+ {accept_timeout,30000},
+ {debug,[{exported_functions,
+ [httpd_manager,httpd_request_handler]}]}],
+ inets_test_lib:start_http_server(HttpdOptions);
+ "ticket_"++Rest ->
+ %% OTP-5913 use the new syntax of inets.config
+ inets_test_lib:start_http_server([{file,
+ filename:join(TcTopDir,
+ integer_to_list(?IP_PORT) ++ ".conf")}]),
+ Rest;
+ "ssl_mod_htaccess" ->
+ case inets_test_lib:start_http_server_ssl(
+ filename:join(TcTopDir,
+ integer_to_list(?SSL_PORT) ++
+ "htacess.conf")) of
+ ok ->
+ "mod_htaccess";
+ Other ->
+ error_logger:info_report("Other: ~p~n", [Other]),
+ {skip, "SSL does not seem to be supported"}
+ end;
+ "ssl_" ++ Rest ->
+ case inets_test_lib:start_http_server_ssl(
+ filename:join(TcTopDir,
+ integer_to_list(?SSL_PORT) ++
+ ".conf")) of
+ ok ->
+ Rest;
+ Other ->
+ error_logger:info_report("Other: ~p~n", [Other]),
+ {skip, "SSL does not seem to be supported"}
+ end;
+ "ipv6_" ++ _ = TestCaseStr ->
+ {ok, Hostname} = inet:gethostname(),
+
+ case lists:member(list_to_atom(Hostname),
+ ?config(ipv6_hosts, Config)) of
+ true ->
+ inets_test_lib:start_http_server(
+ filename:join(TcTopDir,
+ TestCaseStr ++ ".conf"));
+
+ false ->
+ {skip, "Host does not support IPv6"}
+ end
+ end,
+
+ case CaseRest of
+ {skip, _} = Skip ->
+ Skip;
+ "mod_auth_" ++ _ ->
+ start_mnesia(?config(node, Config)),
+ [{watchdog, Dog} | NewConfig];
+ "mod_htaccess" ->
+ ServerRoot = ?config(server_root, Config),
+ Path = filename:join([ServerRoot, "htdocs"]),
+ catch remove_htacess(Path),
+ create_htacess_data(Path, ?config(address, Config)),
+ [{watchdog, Dog} | NewConfig];
+ "range" ->
+ ServerRoot = ?config(server_root, Config),
+ Path = filename:join([ServerRoot, "htdocs"]),
+ create_range_data(Path),
+ [{watchdog, Dog} | NewConfig];
+ _ ->
+ [{watchdog, Dog} | NewConfig]
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ end_per_testcase2(Case, lists:keydelete(watchdog, 1, Config)),
+ ok.
+
+end_per_testcase2(Case, Config) ->
+ io:format(user, "~w:end_per_testcase2(~w) -> entry with"
+ "~n Config: ~p~n",
+ [?MODULE, Case, Config]),
+ application:unset_env(inets, services),
+ application:stop(inets),
+ application:stop(ssl),
+ cleanup_mnesia(),
+ io:format(user, "~w:end_per_testcase2(~w) -> done~n",
+ [?MODULE, Case]),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+ip(doc) ->
+ ["HTTP tests using TCP/IP"];
+ip(suite) ->
+ [
+ ip_mod_alias,
+ ip_mod_actions,
+ ip_mod_security,
+ ip_mod_auth,
+ ip_mod_auth_api,
+ ip_mod_auth_mnesia_api,
+ ip_mod_htaccess,
+ ip_mod_cgi,
+ ip_mod_esi,
+ ip_mod_get,
+ ip_mod_head,
+ ip_mod_all,
+ ip_load_light,
+ ip_load_medium,
+ ip_load_heavy,
+ ip_dos_hostname,
+ ip_time_test,
+ ip_block_disturbing_idle,
+ ip_block_non_disturbing_idle,
+ ip_block_503,
+ ip_block_disturbing_active,
+ ip_block_non_disturbing_active,
+ ip_block_disturbing_active_timeout_not_released,
+ ip_block_disturbing_active_timeout_released,
+ ip_block_non_disturbing_active_timeout_not_released,
+ ip_block_non_disturbing_active_timeout_released,
+ ip_block_disturbing_blocker_dies,
+ ip_block_non_disturbing_blocker_dies,
+ ip_restart_no_block,
+ ip_restart_disturbing_block,
+ ip_restart_non_disturbing_block
+ ].
+
+%%-------------------------------------------------------------------------
+ssl(doc) ->
+ ["HTTP test using SSL"];
+ssl(suite) ->
+ [
+ ssl_mod_alias,
+ ssl_mod_actions,
+ ssl_mod_security,
+ ssl_mod_auth,
+ ssl_mod_auth_api,
+ ssl_mod_auth_mnesia_api,
+ ssl_mod_htaccess,
+ ssl_mod_cgi,
+ ssl_mod_esi,
+ ssl_mod_get,
+ ssl_mod_head,
+ ssl_mod_all,
+ ssl_load_light,
+ ssl_load_medium,
+ ssl_load_heavy,
+ ssl_dos_hostname,
+ ssl_time_test,
+ ssl_restart_no_block,
+ ssl_restart_disturbing_block,
+ ssl_restart_non_disturbing_block,
+ ssl_block_disturbing_idle,
+ ssl_block_non_disturbing_idle,
+ ssl_block_503,
+ ssl_block_disturbing_active,
+ ssl_block_non_disturbing_active,
+ ssl_block_disturbing_active_timeout_not_released,
+ ssl_block_disturbing_active_timeout_released,
+ ssl_block_non_disturbing_active_timeout_not_released,
+ ssl_block_non_disturbing_active_timeout_released,
+ ssl_block_disturbing_blocker_dies,
+ ssl_block_non_disturbing_blocker_dies
+ ].
+
+%%-------------------------------------------------------------------------
+http_1_1_ip(doc) ->
+ ["HTTP/1.1"];
+http_1_1_ip(suite) ->
+ [
+ ip_host,
+ ip_chunked,
+ ip_expect,
+ ip_range,
+ ip_if_test,
+ ip_http_trace,
+ ip_http1_1_head,
+ ip_mod_cgi_chunked_encoding_test
+ ].
+
+%%-------------------------------------------------------------------------
+http_1_0_ip(doc) ->
+ ["HTTP/1.0"];
+http_1_0_ip(suite) ->
+ [
+ ip_head_1_0,
+ ip_get_1_0,
+ ip_post_1_0
+ ].
+
+%%-------------------------------------------------------------------------
+http_0_9_ip(doc) ->
+ ["HTTP/0.9"];
+http_0_9_ip(suite) ->
+ [
+ ip_get_0_9
+ ].
+
+%%-------------------------------------------------------------------------
+ipv6(doc) ->
+ ["Tests ipv6 functionality."];
+ipv6(suite) ->
+ [
+ ipv6_hostname,
+ ipv6_address
+ ].
+
+%%-------------------------------------------------------------------------
+tickets(doc) ->
+ ["Test cases for reported bugs."];
+tickets(suite) ->
+ [
+ ticket_5775,
+ ticket_5865,
+ ticket_5913,
+ ticket_6003,
+ ticket_7304
+ ].
+
+%%-------------------------------------------------------------------------
+ip_mod_alias(doc) ->
+ ["Module test: mod_alias"];
+ip_mod_alias(suite) ->
+ [];
+ip_mod_alias(Config) when is_list(Config) ->
+ httpd_mod:alias(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_actions(doc) ->
+ ["Module test: mod_actions"];
+ip_mod_actions(suite) ->
+ [];
+ip_mod_actions(Config) when is_list(Config) ->
+ httpd_mod:actions(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_security(doc) ->
+ ["Module test: mod_security"];
+ip_mod_security(suite) ->
+ [];
+ip_mod_security(Config) when is_list(Config) ->
+ ServerRoot = ?config(server_root, Config),
+ httpd_mod:security(ServerRoot, ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_mod_auth(doc) ->
+ ["Module test: mod_auth"];
+ip_mod_auth(suite) ->
+ [];
+ip_mod_auth(Config) when is_list(Config) ->
+ httpd_mod:auth(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_mod_auth_api(doc) ->
+ ["Module test: mod_auth_api"];
+ip_mod_auth_api(suite) ->
+ [];
+ip_mod_auth_api(Config) when is_list(Config) ->
+ ServerRoot = ?config(server_root, Config),
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_mod:auth_api(ServerRoot, "", ip_comm, ?IP_PORT, Host, Node),
+ httpd_mod:auth_api(ServerRoot, "dets_", ip_comm, ?IP_PORT, Host, Node),
+ httpd_mod:auth_api(ServerRoot, "mnesia_", ip_comm, ?IP_PORT, Host, Node),
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_auth_mnesia_api(doc) ->
+ ["Module test: mod_auth_mnesia_api"];
+ip_mod_auth_mnesia_api(suite) ->
+ [];
+ip_mod_auth_mnesia_api(Config) when is_list(Config) ->
+ httpd_mod:auth_mnesia_api(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_htaccess(doc) ->
+ ["Module test: mod_htaccess"];
+ip_mod_htaccess(suite) ->
+ [];
+ip_mod_htaccess(Config) when is_list(Config) ->
+ httpd_mod:htaccess(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_cgi(doc) ->
+ ["Module test: mod_cgi"];
+ip_mod_cgi(suite) ->
+ [];
+ip_mod_cgi(Config) when is_list(Config) ->
+ case test_server:os_type() of
+ vxworks ->
+ {skip, cgi_not_supported_on_vxwoks};
+ _ ->
+ httpd_mod:cgi(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+ip_mod_esi(doc) ->
+ ["Module test: mod_esi"];
+ip_mod_esi(suite) ->
+ [];
+ip_mod_esi(Config) when is_list(Config) ->
+ httpd_mod:esi(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_mod_get(doc) ->
+ ["Module test: mod_get"];
+ip_mod_get(suite) ->
+ [];
+ip_mod_get(Config) when is_list(Config) ->
+ httpd_mod:get(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_mod_head(doc) ->
+ ["Module test: mod_head"];
+ip_mod_head(suite) ->
+ [];
+ip_mod_head(Config) when is_list(Config) ->
+ httpd_mod:head(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_all(doc) ->
+ ["All modules test"];
+ip_mod_all(suite) ->
+ [];
+ip_mod_all(Config) when is_list(Config) ->
+ httpd_mod:all(ip_comm, ?IP_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_load_light(doc) ->
+ ["Test light load"];
+ip_load_light(suite) ->
+ [];
+ip_load_light(Config) when is_list(Config) ->
+ httpd_load:load_test(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config),
+ get_nof_clients(ip_comm, light)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_load_medium(doc) ->
+ ["Test medium load"];
+ip_load_medium(suite) ->
+ [];
+ip_load_medium(Config) when is_list(Config) ->
+ httpd_load:load_test(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config),
+ get_nof_clients(ip_comm, medium)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_load_heavy(doc) ->
+ ["Test heavy load"];
+ip_load_heavy(suite) ->
+ [];
+ip_load_heavy(Config) when is_list(Config) ->
+ httpd_load:load_test(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config),
+ get_nof_clients(ip_comm, heavy)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_dos_hostname(doc) ->
+ ["Denial Of Service (DOS) attack test case"];
+ip_dos_hostname(suite) ->
+ [];
+ip_dos_hostname(Config) when is_list(Config) ->
+ dos_hostname(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config), ?MAX_HEADER_SIZE),
+ ok.
+%%-------------------------------------------------------------------------
+ip_time_test(doc) ->
+ [""];
+ip_time_test(suite) ->
+ [];
+ip_time_test(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Skippable = [win32],
+ Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_time_test:t(ip_comm, ?config(host, Config), ?IP_PORT),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_block_503(doc) ->
+ ["Check that you will receive status code 503 when the server"
+ " is blocked and 200 when its not blocked."];
+ip_block_503(suite) ->
+ [];
+ip_block_503(Config) when is_list(Config) ->
+ httpd_block:block_503(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "distribing does not really make a difference in this case."];
+ip_block_disturbing_idle(suite) ->
+ [];
+ip_block_disturbing_idle(Config) when is_list(Config) ->
+ httpd_block:block_disturbing_idle(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_non_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing does not really make a difference in this case."];
+ip_block_non_disturbing_idle(suite) ->
+ [];
+ip_block_non_disturbing_idle(Config) when is_list(Config) ->
+ httpd_block:block_non_disturbing_idle(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_disturbing_active(doc) ->
+ ["Check that you can block/unblock an active server. The strategy "
+ "distribing means ongoing requests should be terminated."];
+ip_block_disturbing_active(suite) ->
+ [];
+ip_block_disturbing_active(Config) when is_list(Config) ->
+ httpd_block:block_disturbing_active(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_non_disturbing_active(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing means the ongoing requests should be compleated."];
+ip_block_non_disturbing_active(suite) ->
+ [];
+ip_block_non_disturbing_active(Config) when is_list(Config) ->
+ httpd_block:block_non_disturbing_idle(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_block_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be compleated"
+ "if the timeout does not occur."];
+ip_block_disturbing_active_timeout_not_released(suite) ->
+ [];
+ip_block_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ httpd_block:block_disturbing_active_timeout_not_released(ip_comm,
+ ?IP_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be terminated when"
+ "the timeout occurs."];
+ip_block_disturbing_active_timeout_released(suite) ->
+ [];
+ip_block_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ httpd_block:block_disturbing_active_timeout_released(ip_comm,
+ ?IP_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_block_non_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non non distribing means ongoing requests should be completed."];
+ip_block_non_disturbing_active_timeout_not_released(suite) ->
+ [];
+ip_block_non_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ httpd_block:
+ block_non_disturbing_active_timeout_not_released(ip_comm,
+ ?IP_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_non_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non non distribing means ongoing requests should be completed. "
+ "When the timeout occurs the block operation sohould be canceled." ];
+ip_block_non_disturbing_active_timeout_released(suite) ->
+ [];
+ip_block_non_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ httpd_block:
+ block_non_disturbing_active_timeout_released(ip_comm,
+ ?IP_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_disturbing_blocker_dies(doc) ->
+ [];
+ip_block_disturbing_blocker_dies(suite) ->
+ [];
+ip_block_disturbing_blocker_dies(Config) when is_list(Config) ->
+ httpd_block:disturbing_blocker_dies(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_block_non_disturbing_blocker_dies(doc) ->
+ [];
+ip_block_non_disturbing_blocker_dies(suite) ->
+ [];
+ip_block_non_disturbing_blocker_dies(Config) when is_list(Config) ->
+ httpd_block:non_disturbing_blocker_dies(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_restart_no_block(doc) ->
+ [""];
+ip_restart_no_block(suite) ->
+ [];
+ip_restart_no_block(Config) when is_list(Config) ->
+ httpd_block:restart_no_block(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_restart_disturbing_block(doc) ->
+ [""];
+ip_restart_disturbing_block(suite) ->
+ [];
+ip_restart_disturbing_block(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Condition =
+ fun() ->
+ case os:type() of
+ {unix, linux} ->
+ HW = string:strip(os:cmd("uname -m"), right, $\n),
+ case HW of
+ "ppc" ->
+ case inet:gethostname() of
+ {ok, "peach"} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_block:restart_disturbing_block(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_restart_non_disturbing_block(doc) ->
+ [""];
+ip_restart_non_disturbing_block(suite) ->
+ [];
+ip_restart_non_disturbing_block(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Condition =
+ fun() ->
+ case os:type() of
+ {unix, linux} ->
+ HW = string:strip(os:cmd("uname -m"), right, $\n),
+ case HW of
+ "ppc" ->
+ case inet:gethostname() of
+ {ok, "peach"} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_block:restart_non_disturbing_block(ip_comm, ?IP_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_mod_alias(doc) ->
+ ["Module test: mod_alias"];
+ssl_mod_alias(suite) ->
+ [];
+ssl_mod_alias(Config) when is_list(Config) ->
+ httpd_mod:alias(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_actions(doc) ->
+ ["Module test: mod_actions"];
+ssl_mod_actions(suite) ->
+ [];
+ssl_mod_actions(Config) when is_list(Config) ->
+ httpd_mod:actions(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_security(doc) ->
+ ["Module test: mod_security"];
+ssl_mod_security(suite) ->
+ [];
+ssl_mod_security(Config) when is_list(Config) ->
+ ServerRoot = ?config(server_root, Config),
+ httpd_mod:security(ServerRoot, ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_auth(doc) ->
+ ["Module test: mod_auth"];
+ssl_mod_auth(suite) ->
+ [];
+ssl_mod_auth(Config) when is_list(Config) ->
+ httpd_mod:auth(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_auth_api(doc) ->
+ ["Module test: mod_auth"];
+ssl_mod_auth_api(suite) ->
+ [];
+ssl_mod_auth_api(Config) when is_list(Config) ->
+ ServerRoot = ?config(server_root, Config),
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_mod:auth_api(ServerRoot, "", ssl, ?SSL_PORT, Host, Node),
+ httpd_mod:auth_api(ServerRoot, "dets_", ssl, ?SSL_PORT, Host, Node),
+ httpd_mod:auth_api(ServerRoot, "mnesia_", ssl, ?SSL_PORT, Host, Node),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_mod_auth_mnesia_api(doc) ->
+ ["Module test: mod_auth_mnesia_api"];
+ssl_mod_auth_mnesia_api(suite) ->
+ [];
+ssl_mod_auth_mnesia_api(Config) when is_list(Config) ->
+ httpd_mod:auth_mnesia_api(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_htaccess(doc) ->
+ ["Module test: mod_htaccess"];
+ssl_mod_htaccess(suite) ->
+ [];
+ssl_mod_htaccess(Config) when is_list(Config) ->
+ httpd_mod:htaccess(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_cgi(doc) ->
+ ["Module test: mod_cgi"];
+ssl_mod_cgi(suite) ->
+ [];
+ssl_mod_cgi(Config) when is_list(Config) ->
+ case test_server:os_type() of
+ vxworks ->
+ {skip, cgi_not_supported_on_vxwoks};
+ _ ->
+ httpd_mod:cgi(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+ssl_mod_esi(doc) ->
+ ["Module test: mod_esi"];
+ssl_mod_esi(suite) ->
+ [];
+ssl_mod_esi(Config) when is_list(Config) ->
+ httpd_mod:esi(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_mod_get(doc) ->
+ ["Module test: mod_get"];
+ssl_mod_get(suite) ->
+ [];
+ssl_mod_get(Config) when is_list(Config) ->
+ httpd_mod:get(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_head(doc) ->
+ ["Module test: mod_head"];
+ssl_mod_head(suite) ->
+ [];
+ssl_mod_head(Config) when is_list(Config) ->
+ httpd_mod:head(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_mod_all(doc) ->
+ ["All modules test"];
+ssl_mod_all(suite) ->
+ [];
+ssl_mod_all(Config) when is_list(Config) ->
+ httpd_mod:all(ssl, ?SSL_PORT,
+ ?config(host, Config), ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_load_light(doc) ->
+ ["Test light load"];
+ssl_load_light(suite) ->
+ [];
+ssl_load_light(Config) when is_list(Config) ->
+ httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config),
+ ?config(node, Config),
+ get_nof_clients(ssl, light)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_load_medium(doc) ->
+ ["Test medium load"];
+ssl_load_medium(suite) ->
+ [];
+ssl_load_medium(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Skippable = [win32],
+ Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config),
+ ?config(node, Config),
+ get_nof_clients(ssl, medium)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_load_heavy(doc) ->
+ ["Test heavy load"];
+ssl_load_heavy(suite) ->
+ [];
+ssl_load_heavy(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Skippable = [win32],
+ Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config),
+ ?config(node, Config),
+ get_nof_clients(ssl, heavy)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_dos_hostname(doc) ->
+ ["Denial Of Service (DOS) attack test case"];
+ssl_dos_hostname(suite) ->
+ [];
+ssl_dos_hostname(Config) when is_list(Config) ->
+ dos_hostname(ssl, ?SSL_PORT, ?config(host, Config),
+ ?config(node, Config), ?MAX_HEADER_SIZE),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_time_test(doc) ->
+ [""];
+ssl_time_test(suite) ->
+ [];
+ssl_time_test(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Condition = fun() -> true end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_time_test:t(ssl, ?config(host, Config), ?SSL_PORT),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_block_503(doc) ->
+ ["Check that you will receive status code 503 when the server"
+ " is blocked and 200 when its not blocked."];
+ssl_block_503(suite) ->
+ [];
+ssl_block_503(Config) when is_list(Config) ->
+ httpd_block:block_503(ssl, ?SSL_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "distribing does not really make a difference in this case."];
+ssl_block_disturbing_idle(suite) ->
+ [];
+ssl_block_disturbing_idle(Config) when is_list(Config) ->
+ httpd_block:block_disturbing_idle(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_non_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing does not really make a difference in this case."];
+ssl_block_non_disturbing_idle(suite) ->
+ [];
+ssl_block_non_disturbing_idle(Config) when is_list(Config) ->
+ httpd_block:block_non_disturbing_idle(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_disturbing_active(doc) ->
+ ["Check that you can block/unblock an active server. The strategy "
+ "distribing means ongoing requests should be terminated."];
+ssl_block_disturbing_active(suite) ->
+ [];
+ssl_block_disturbing_active(Config) when is_list(Config) ->
+ httpd_block:block_disturbing_active(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_non_disturbing_active(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing means the ongoing requests should be compleated."];
+ssl_block_non_disturbing_active(suite) ->
+ [];
+ssl_block_non_disturbing_active(Config) when is_list(Config) ->
+ httpd_block:block_non_disturbing_idle(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_block_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be compleated"
+ "if the timeout does not occur."];
+ssl_block_disturbing_active_timeout_not_released(suite) ->
+ [];
+ssl_block_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ httpd_block:
+ block_disturbing_active_timeout_not_released(ssl,
+ ?SSL_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be terminated when"
+ "the timeout occurs."];
+ssl_block_disturbing_active_timeout_released(suite) ->
+ [];
+ssl_block_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ httpd_block:block_disturbing_active_timeout_released(ssl,
+ ?SSL_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_block_non_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non non distribing means ongoing requests should be completed."];
+ssl_block_non_disturbing_active_timeout_not_released(suite) ->
+ [];
+ssl_block_non_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ httpd_block:
+ block_non_disturbing_active_timeout_not_released(ssl,
+ ?SSL_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_non_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non non distribing means ongoing requests should be completed. "
+ "When the timeout occurs the block operation sohould be canceled." ];
+ssl_block_non_disturbing_active_timeout_released(suite) ->
+ [];
+ssl_block_non_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ httpd_block:
+ block_non_disturbing_active_timeout_released(ssl,
+ ?SSL_PORT,
+ ?config(host,
+ Config),
+ ?config(node,
+ Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_block_disturbing_blocker_dies(doc) ->
+ [];
+ssl_block_disturbing_blocker_dies(suite) ->
+ [];
+ssl_block_disturbing_blocker_dies(Config) when is_list(Config) ->
+ httpd_block:disturbing_blocker_dies(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_block_non_disturbing_blocker_dies(doc) ->
+ [];
+ssl_block_non_disturbing_blocker_dies(suite) ->
+ [];
+ssl_block_non_disturbing_blocker_dies(Config) when is_list(Config) ->
+ httpd_block:non_disturbing_blocker_dies(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_restart_no_block(doc) ->
+ [""];
+ssl_restart_no_block(suite) ->
+ [];
+ssl_restart_no_block(Config) when is_list(Config) ->
+ httpd_block:restart_no_block(ssl, ?SSL_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ssl_restart_disturbing_block(doc) ->
+ [""];
+ssl_restart_disturbing_block(suite) ->
+ [];
+ssl_restart_disturbing_block(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Condition =
+ fun() ->
+ case os:type() of
+ {unix, linux} ->
+ HW = string:strip(os:cmd("uname -m"), right, $\n),
+ case HW of
+ "ppc" ->
+ case inet:gethostname() of
+ {ok, "peach"} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_block:restart_disturbing_block(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ssl_restart_non_disturbing_block(doc) ->
+ [""];
+ssl_restart_non_disturbing_block(suite) ->
+ [];
+ssl_restart_non_disturbing_block(Config) when is_list(Config) ->
+ %% <CONDITIONAL-SKIP>
+ Condition =
+ fun() ->
+ case os:type() of
+ {unix, linux} ->
+ HW = string:strip(os:cmd("uname -m"), right, $\n),
+ case HW of
+ "ppc" ->
+ case inet:gethostname() of
+ {ok, "peach"} ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
+ httpd_block:restart_non_disturbing_block(ssl, ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_host(doc) ->
+ ["Control that the server accepts/rejects requests with/ without host"];
+ip_host(suite)->
+ [];
+ip_host(Config) when is_list(Config) ->
+ httpd_1_1:host(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_chunked(doc) ->
+ ["Control that the server accepts chunked requests"];
+ip_chunked(suite) ->
+ [];
+ip_chunked(Config) when is_list(Config) ->
+ httpd_1_1:chunked(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_expect(doc) ->
+ ["Control that the server handles request with the expect header "
+ "field appropiate"];
+ip_expect(suite)->
+ [];
+ip_expect(Config) when is_list(Config) ->
+ httpd_1_1:expect(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_range(doc) ->
+ ["Control that the server can handle range requests to plain files"];
+ip_range(suite)->
+ [];
+ip_range(Config) when is_list(Config) ->
+ httpd_1_1:range(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_if_test(doc) ->
+ ["Test that the if - request header fields is handled correclty"];
+ip_if_test(suite) ->
+ [];
+ip_if_test(Config) when is_list(Config) ->
+ ServerRoot = ?config(server_root, Config),
+ DocRoot = filename:join([ServerRoot, "htdocs"]),
+ httpd_1_1:if_test(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config), DocRoot),
+ ok.
+%%-------------------------------------------------------------------------
+ip_http_trace(doc) ->
+ ["Test the trace module "];
+ip_http_trace(suite) ->
+ [];
+ip_http_trace(Config) when is_list(Config) ->
+ httpd_1_1:http_trace(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+%%-------------------------------------------------------------------------
+ip_http1_1_head(doc) ->
+ ["Test the trace module "];
+ip_http1_1_head(suite)->
+ [];
+ip_http1_1_head(Config) when is_list(Config) ->
+ httpd_1_1:head(ip_comm, ?IP_PORT, ?config(host, Config),
+ ?config(node, Config)),
+ ok.
+
+%%-------------------------------------------------------------------------
+ip_get_0_9(doc) ->
+ ["Test simple HTTP/0.9 GET"];
+ip_get_0_9(suite)->
+ [];
+ip_get_0_9(Config) when is_list(Config) ->
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ ok = httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, Node,
+ "GET / \r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/0.9"} ]),
+ %% Without space after uri
+ ok = httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, Node,
+ "GET /\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/0.9"} ]),
+ ok = httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, Node,
+ "GET / HTTP/0.9\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/0.9"}]),
+
+ ok.
+%%-------------------------------------------------------------------------
+ip_head_1_0(doc) ->
+ ["Test HTTP/1.0 HEAD"];
+ip_head_1_0(suite)->
+ [];
+ip_head_1_0(Config) when is_list(Config) ->
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ ok = httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, Node,
+ "HEAD / HTTP/1.0\r\n\r\n", [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+
+ ok.
+%%-------------------------------------------------------------------------
+ip_get_1_0(doc) ->
+ ["Test HTTP/1.0 GET"];
+ip_get_1_0(suite)->
+ [];
+ip_get_1_0(Config) when is_list(Config) ->
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ ok = httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, Node,
+ "GET / HTTP/1.0\r\n\r\n", [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+
+ ok.
+%%-------------------------------------------------------------------------
+ip_post_1_0(doc) ->
+ ["Test HTTP/1.0 POST"];
+ip_post_1_0(suite)->
+ [];
+ip_post_1_0(Config) when is_list(Config) ->
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ %% Test the post message formatin 1.0! Real post are testes elsewhere
+ ok = httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, Node,
+ "POST / HTTP/1.0\r\n\r\n "
+ "Content-Length:6 \r\n\r\nfoobar",
+ [{statuscode, 500}, {version, "HTTP/1.0"}]),
+
+ ok.
+%%-------------------------------------------------------------------------
+ip_mod_cgi_chunked_encoding_test(doc) ->
+ ["Test the trace module "];
+ip_mod_cgi_chunked_encoding_test(suite)->
+ [];
+ip_mod_cgi_chunked_encoding_test(Config) when is_list(Config) ->
+ Host = ?config(host, Config),
+ Script =
+ case test_server:os_type() of
+ {win32, _} ->
+ "/cgi-bin/printenv.bat";
+ _ ->
+ "/cgi-bin/printenv.sh"
+ end,
+ Requests =
+ ["GET " ++ Script ++ " HTTP/1.1\r\nHost:"++ Host ++"\r\n\r\n",
+ "GET /cgi-bin/erl/httpd_example/newformat HTTP/1.1\r\nHost:"
+ ++ Host ++"\r\n\r\n"],
+ httpd_1_1:mod_cgi_chunked_encoding_test(ip_comm, ?IP_PORT,
+ Host,
+ ?config(node, Config),
+ Requests),
+ ok.
+
+%-------------------------------------------------------------------------
+ipv6_hostname(doc) ->
+ ["Test standard ipv6 address"];
+ipv6_hostname(suite)->
+ [];
+ipv6_hostname(Config) when is_list(Config) ->
+ Host = ?config(host, Config),
+ httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, node(),
+ "GET / HTTP/1.1\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.1"}]),
+ ok.
+
+%%-------------------------------------------------------------------------
+ipv6_address(doc) ->
+ ["Test standard ipv6 address"];
+ipv6_address(suite)->
+ [];
+ipv6_address(Config) when is_list(Config) ->
+ httpd_test_lib:verify_request(ip_comm, ?IPV6_LOCAL_HOST, ?IP_PORT,
+ node(), "GET / HTTP/1.1\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.1"}]),
+ ok.
+
+%%--------------------------------------------------------------------
+ticket_5775(doc) ->
+ ["Tests that content-length is correct"];
+ticket_5775(suite) ->
+ [];
+ticket_5775(Config) ->
+ ok=httpd_test_lib:verify_request(ip_comm, ?config(host, Config),
+ ?IP_PORT, ?config(node, Config),
+ "GET /cgi-bin/erl/httpd_example:get_bin "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok.
+ticket_5865(doc) ->
+ ["Tests that a header without last-modified is handled"];
+ticket_5865(suite) ->
+ [];
+ticket_5865(Config) ->
+ Host = ?config(host,Config),
+ ServerRoot = ?config(server_root, Config),
+ DocRoot = filename:join([ServerRoot, "htdocs"]),
+ File = filename:join([DocRoot,"last_modified.html"]),
+
+ Bad_mtime = case test_server:os_type() of
+ {win32, _} ->
+ {{1600,12,31},{23,59,59}};
+ {unix, _} ->
+ {{1969,12,31},{23,59,59}}
+ end,
+
+ {ok,FI}=file:read_file_info(File),
+
+ case file:write_file_info(File,FI#file_info{mtime=Bad_mtime}) of
+ ok ->
+ ok = httpd_test_lib:verify_request(ip_comm, Host,
+ ?IP_PORT, ?config(node, Config),
+ "GET /last_modified.html"
+ " HTTP/1.1\r\nHost:"
+ ++Host++"\r\n\r\n",
+ [{statuscode, 200},
+ {no_last_modified,
+ "last-modified"}]),
+ ok;
+ {error, Reason} ->
+ Fault =
+ io_lib:format("Attempt to change the file info to set the"
+ " preconditions of the test case failed ~p~n",
+ [Reason]),
+ {skip, Fault}
+ end.
+
+ticket_5913(doc) ->
+ ["Tests that a header without last-modified is handled"];
+ticket_5913(suite) -> [];
+ticket_5913(Config) ->
+ ok=httpd_test_lib:verify_request(ip_comm, ?config(host, Config),
+ ?IP_PORT, ?config(node, Config),
+ "GET /cgi-bin/erl/httpd_example:get_bin "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok.
+
+ticket_6003(doc) ->
+ ["Tests that a URI with a bad hexadecimal code is handled"];
+ticket_6003(suite) -> [];
+ticket_6003(Config) ->
+ ok=httpd_test_lib:verify_request(ip_comm, ?config(host, Config),
+ ?IP_PORT, ?config(node, Config),
+ "GET http://www.erlang.org/%skalle "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 400},
+ {version, "HTTP/1.0"}]),
+ ok.
+
+ticket_7304(doc) ->
+ ["Tests missing CR in delimiter"];
+ticket_7304(suite) ->
+ [];
+ticket_7304(Config) ->
+ ok = httpd_test_lib:verify_request(ip_comm, ?config(host, Config),
+ ?IP_PORT, ?config(node, Config),
+ "GET / HTTP/1.0\r\n\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+dos_hostname(Type, Port, Host, Node, Max) ->
+ H1 = {"", 200},
+ H2 = {"dummy-host.ericsson.se", 200},
+ TooLongHeader = lists:append(lists:duplicate(Max + 1, "a")),
+ H3 = {TooLongHeader, 403},
+ Hosts = [H1,H2,H3],
+ dos_hostname_poll(Type, Host, Port, Node, Hosts).
+
+%% make_ipv6(T) when is_tuple(T) andalso (size(T) =:= 8) ->
+%% make_ipv6(tuple_to_list(T));
+
+%% make_ipv6([_, _, _, _, _, _, _, _] = IPV6) ->
+%% lists:flatten(io_lib:format("~s:~s:~s:~s:~s:~s:~s:~s", IPV6)).
+
+
+%%--------------------------------------------------------------------
+%% Other help functions
+create_config(Config, Access, FileName) ->
+ ServerRoot = ?config(server_root, Config),
+ TcTopDir = ?config(tc_top_dir, Config),
+ Port = ?config(port, Config),
+ Type = ?config(sock_type, Config),
+ Host = ?config(host, Config),
+ Mods = io_lib:format("~p", [httpd_mod]),
+ Funcs = io_lib:format("~p", [ssl_password_cb]),
+ MaxHdrSz = io_lib:format("~p", [256]),
+ MaxHdrAct = io_lib:format("~p", [close]),
+ SSL =
+ case Type of
+ ssl ->
+ [cline(["SSLCertificateFile ",
+ filename:join(ServerRoot, "ssl/ssl_server.pem")]),
+ cline(["SSLCertificateKeyFile ",
+ filename:join(ServerRoot, "ssl/ssl_server.pem")]),
+ cline(["SSLCACertificateFile ",
+ filename:join(ServerRoot, "ssl/ssl_server.pem")]),
+ cline(["SSLPasswordCallbackModule ", Mods]),
+ cline(["SSLPasswordCallbackFunction ", Funcs]),
+ cline(["SSLVerifyClient 0"]),
+ cline(["SSLVerifyDepth 1"])];
+ _ ->
+ []
+ end,
+ Mod_order = case Access of
+ mod_htaccess ->
+ "Modules mod_alias mod_htaccess mod_auth "
+ "mod_security "
+ "mod_responsecontrol mod_trace mod_esi "
+ "mod_actions mod_cgi mod_include mod_dir "
+ "mod_range mod_get "
+ "mod_head mod_log mod_disk_log";
+ _ ->
+ "Modules mod_alias mod_auth mod_security "
+ "mod_responsecontrol mod_trace mod_esi "
+ "mod_actions mod_cgi mod_include mod_dir "
+ "mod_range mod_get "
+ "mod_head mod_log mod_disk_log"
+ end,
+
+%% The test suite currently does not handle an explicit BindAddress.
+%% They assume any has been used, that is Addr is always set to undefined!
+
+%% {ok, Hostname} = inet:gethostname(),
+%% {ok, Addr} = inet:getaddr(Hostname, inet6),
+%% AddrStr = make_ipv6(Addr),
+%% BindAddress = lists:flatten(io_lib:format("~s|inet6", [AddrStr])),
+
+ %% BindAddress = "*|inet",
+ BindAddress = "*",
+
+ HttpConfig = [
+ cline(["Port ", integer_to_list(Port)]),
+ cline(["ServerName ", Host]),
+ cline(["SocketType ", atom_to_list(Type)]),
+ cline([Mod_order]),
+ %% cline(["LogFormat ", "erlang"]),
+ cline(["ServerAdmin [email protected]"]),
+ cline(["BindAddress ", BindAddress]),
+ cline(["ServerRoot ", ServerRoot]),
+ cline(["ErrorLog ", TcTopDir,
+ "/logs/error_log_", integer_to_list(Port)]),
+ cline(["TransferLog ", TcTopDir,
+ "/logs/access_log_", integer_to_list(Port)]),
+ cline(["SecurityLog ", TcTopDir,
+ "/logs/security_log_", integer_to_list(Port)]),
+ cline(["ErrorDiskLog ", TcTopDir,
+ "/logs/error_disk_log_", integer_to_list(Port)]),
+ cline(["ErrorDiskLogSize ", "190000 ", "11"]),
+ cline(["TransferDiskLog ", TcTopDir,
+ "/logs/access_disk_log_", integer_to_list(Port)]),
+ cline(["TransferDiskLogSize ", "200000 ", "10"]),
+ cline(["SecurityDiskLog ", TcTopDir,
+ "/logs/security_disk_log_", integer_to_list(Port)]),
+ cline(["SecurityDiskLogSize ", "210000 ", "9"]),
+ cline(["MaxClients 10"]),
+ cline(["MaxHeaderSize ", MaxHdrSz]),
+ cline(["MaxHeaderAction ", MaxHdrAct]),
+ cline(["DocumentRoot ",
+ filename:join(ServerRoot, "htdocs")]),
+ cline(["DirectoryIndex ", "index.html ", "welcome.html"]),
+ cline(["DefaultType ", "text/plain"]),
+ SSL,
+ mod_alias_config(ServerRoot),
+
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "open"]),
+ "Open Area",
+ filename:join(ServerRoot, "auth/passwd"),
+ filename:join(ServerRoot, "auth/group"),
+ plain,
+ "user one Aladdin",
+ filename:join(ServerRoot, "security_data")),
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "secret"]),
+ "Secret Area",
+ filename:join(ServerRoot, "auth/passwd"),
+ filename:join(ServerRoot, "auth/group"),
+ plain,
+ "group group1 group2",
+ filename:join(ServerRoot, "security_data")),
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "secret",
+ "top_secret"]),
+ "Top Secret Area",
+ filename:join(ServerRoot, "auth/passwd"),
+ filename:join(ServerRoot, "auth/group"),
+ plain,
+ "group group3",
+ filename:join(ServerRoot, "security_data")),
+
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "dets_open"]),
+ "Dets Open Area",
+ filename:join(ServerRoot, "passwd"),
+ filename:join(ServerRoot, "group"),
+ dets,
+ "user one Aladdin",
+ filename:join(ServerRoot, "security_data")),
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "dets_secret"]),
+ "Dets Secret Area",
+ filename:join(ServerRoot, "passwd"),
+ filename:join(ServerRoot, "group"),
+ dets,
+ "group group1 group2",
+ filename:join(ServerRoot, "security_data")),
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "dets_secret",
+ "top_secret"]),
+ "Dets Top Secret Area",
+ filename:join(ServerRoot, "passwd"),
+ filename:join(ServerRoot, "group"),
+ dets,
+ "group group3",
+ filename:join(ServerRoot, "security_data")),
+
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "mnesia_open"]),
+ "Mnesia Open Area",
+ false,
+ false,
+ mnesia,
+ "user one Aladdin",
+ filename:join(ServerRoot, "security_data")),
+ config_directory(filename:join([ServerRoot,"htdocs",
+ "mnesia_secret"]),
+ "Mnesia Secret Area",
+ false,
+ false,
+ mnesia,
+ "group group1 group2",
+ filename:join(ServerRoot, "security_data")),
+ config_directory(filename:join(
+ [ServerRoot, "htdocs", "mnesia_secret",
+ "top_secret"]),
+ "Mnesia Top Secret Area",
+ false,
+ false,
+ mnesia,
+ "group group3",
+ filename:join(ServerRoot, "security_data"))
+ ],
+ ConfigFile = filename:join([TcTopDir, FileName]),
+ {ok, Fd} = file:open(ConfigFile, [write]),
+ ok = file:write(Fd, lists:flatten(HttpConfig)),
+ ok = file:close(Fd).
+
+config_directory(Dir, AuthName, AuthUserFile, AuthGroupFile, AuthDBType,
+ Require, SF) ->
+ file:delete(SF),
+ [
+ cline(["<Directory ", Dir, ">"]),
+ cline(["SecurityDataFile ", SF]),
+ cline(["SecurityMaxRetries 3"]),
+ cline(["SecurityFailExpireTime ", integer_to_list(?FAIL_EXPIRE_TIME)]),
+ cline(["SecurityBlockTime 1"]),
+ cline(["SecurityAuthTimeout ", integer_to_list(?AUTH_TIMEOUT)]),
+ cline(["SecurityCallbackModule ", "httpd_mod"]),
+ cline_if_set("AuthUserFile", AuthUserFile),
+ cline_if_set("AuthGroupFile", AuthGroupFile),
+ cline_if_set("AuthName", AuthName),
+ cline_if_set("AuthDBType", AuthDBType),
+ cline(["require ", Require]),
+ cline(["</Directory>\r\n"])
+ ].
+
+mod_alias_config(Root) ->
+ [
+ cline(["Alias /icons/ ", filename:join(Root,"icons"), "/"]),
+ cline(["Alias /pics/ ", filename:join(Root, "icons"), "/"]),
+ cline(["ScriptAlias /cgi-bin/ ", filename:join(Root, "cgi-bin"), "/"]),
+ cline(["ScriptAlias /htbin/ ", filename:join(Root, "cgi-bin"), "/"]),
+ cline(["ErlScriptAlias /cgi-bin/erl httpd_example io"]),
+ cline(["EvalScriptAlias /eval httpd_example io"])
+ ].
+
+cline(List) ->
+ lists:flatten([List, "\r\n"]).
+
+cline_if_set(_, false) ->
+ [];
+cline_if_set(Name, Var) when is_list(Var) ->
+ cline([Name, " ", Var]);
+cline_if_set(Name, Var) when is_atom(Var) ->
+ cline([Name, " ", atom_to_list(Var)]).
+
+getaddr() ->
+ {ok,HostName} = inet:gethostname(),
+ {ok,{A1,A2,A3,A4}} = inet:getaddr(HostName,inet),
+ lists:flatten(io_lib:format("~p.~p.~p.~p",[A1,A2,A3,A4])).
+
+start_mnesia(Node) ->
+ case rpc:call(Node, ?MODULE, cleanup_mnesia, []) of
+ ok ->
+ ok;
+ Other ->
+ test_server:fail({failed_to_cleanup_mnesia, Other})
+ end,
+ case rpc:call(Node, ?MODULE, setup_mnesia, []) of
+ {atomic, ok} ->
+ ok;
+ Other2 ->
+ test_server:fail({failed_to_setup_mnesia, Other2})
+ end,
+ ok.
+
+setup_mnesia() ->
+ setup_mnesia([node()]).
+
+setup_mnesia(Nodes) ->
+ ok = mnesia:create_schema(Nodes),
+ ok = mnesia:start(),
+ {atomic, ok} = mnesia:create_table(httpd_user,
+ [{attributes,
+ record_info(fields, httpd_user)},
+ {disc_copies,Nodes}, {type, set}]),
+ {atomic, ok} = mnesia:create_table(httpd_group,
+ [{attributes,
+ record_info(fields,
+ httpd_group)},
+ {disc_copies,Nodes}, {type,bag}]).
+
+cleanup_mnesia() ->
+ mnesia:start(),
+ mnesia:delete_table(httpd_user),
+ mnesia:delete_table(httpd_group),
+ stopped = mnesia:stop(),
+ mnesia:delete_schema([node()]),
+ ok.
+
+create_htacess_data(Path, IpAddress)->
+ create_htacess_dirs(Path),
+
+ create_html_file(filename:join([Path,"ht/open/dummy.html"])),
+ create_html_file(filename:join([Path,"ht/blocknet/dummy.html"])),
+ create_html_file(filename:join([Path,"ht/secret/dummy.html"])),
+ create_html_file(filename:join([Path,"ht/secret/top_secret/dummy.html"])),
+
+ create_htacess_file(filename:join([Path,"ht/open/.htaccess"]),
+ Path, "user one Aladdin"),
+ create_htacess_file(filename:join([Path,"ht/secret/.htaccess"]),
+ Path, "group group1 group2"),
+ create_htacess_file(filename:join([Path,
+ "ht/secret/top_secret/.htaccess"]),
+ Path, "user four"),
+ create_htacess_file(filename:join([Path,"ht/blocknet/.htaccess"]),
+ Path, nouser, IpAddress),
+
+ create_user_group_file(filename:join([Path,"ht","users.file"]),
+ "one:OnePassword\ntwo:TwoPassword\nthree:"
+ "ThreePassword\nfour:FourPassword\nAladdin:"
+ "AladdinPassword"),
+ create_user_group_file(filename:join([Path,"ht","groups.file"]),
+ "group1: two one\ngroup2: two three").
+
+create_html_file(PathAndFileName)->
+ file:write_file(PathAndFileName,list_to_binary(
+ "<html><head><title>test</title></head>
+ <body>testar</body></html>")).
+
+create_htacess_file(PathAndFileName, BaseDir, RequireData)->
+ file:write_file(PathAndFileName,
+ list_to_binary(
+ "AuthUserFile "++ BaseDir ++
+ "/ht/users.file\nAuthGroupFile "++ BaseDir
+ ++ "/ht/groups.file\nAuthName Test\nAuthType"
+ " Basic\n<Limit>\nrequire " ++ RequireData ++
+ "\n</Limit>")).
+
+create_htacess_file(PathAndFileName, BaseDir, nouser, IpAddress)->
+ file:write_file(PathAndFileName,list_to_binary(
+ "AuthUserFile "++ BaseDir ++
+ "/ht/users.file\nAuthGroupFile " ++
+ BaseDir ++ "/ht/groups.file\nAuthName"
+ " Test\nAuthType"
+ " Basic\n<Limit GET>\n\tallow from " ++
+ format_ip(IpAddress,
+ string:rchr(IpAddress,$.)) ++
+ "\n</Limit>")).
+
+create_user_group_file(PathAndFileName, Data)->
+ file:write_file(PathAndFileName, list_to_binary(Data)).
+
+create_htacess_dirs(Path)->
+ ok = file:make_dir(filename:join([Path,"ht"])),
+ ok = file:make_dir(filename:join([Path,"ht/open"])),
+ ok = file:make_dir(filename:join([Path,"ht/blocknet"])),
+ ok = file:make_dir(filename:join([Path,"ht/secret"])),
+ ok = file:make_dir(filename:join([Path,"ht/secret/top_secret"])).
+
+remove_htacess_dirs(Path)->
+ file:del_dir(filename:join([Path,"ht/secret/top_secret"])),
+ file:del_dir(filename:join([Path,"ht/secret"])),
+ file:del_dir(filename:join([Path,"ht/blocknet"])),
+ file:del_dir(filename:join([Path,"ht/open"])),
+ file:del_dir(filename:join([Path,"ht"])).
+
+format_ip(IpAddress,Pos)when Pos > 0->
+ case lists:nth(Pos,IpAddress) of
+ $.->
+ case lists:nth(Pos-2,IpAddress) of
+ $.->
+ format_ip(IpAddress,Pos-3);
+ _->
+ lists:sublist(IpAddress,Pos-2) ++ "."
+ end;
+ _ ->
+ format_ip(IpAddress,Pos-1)
+ end;
+
+format_ip(IpAddress, _Pos)->
+ "1" ++ IpAddress.
+
+remove_htacess(Path)->
+ file:delete(filename:join([Path,"ht/open/dummy.html"])),
+ file:delete(filename:join([Path,"ht/secret/dummy.html"])),
+ file:delete(filename:join([Path,"ht/secret/top_secret/dummy.html"])),
+ file:delete(filename:join([Path,"ht/blocknet/dummy.html"])),
+ file:delete(filename:join([Path,"ht/blocknet/.htaccess"])),
+ file:delete(filename:join([Path,"ht/open/.htaccess"])),
+ file:delete(filename:join([Path,"ht/secret/.htaccess"])),
+ file:delete(filename:join([Path,"ht/secret/top_secret/.htaccess"])),
+ file:delete(filename:join([Path,"ht","users.file"])),
+ file:delete(filename:join([Path,"ht","groups.file"])),
+ remove_htacess_dirs(Path).
+
+
+dos_hostname_poll(Type, Host, Port, Node, Hosts) ->
+ [dos_hostname_poll1(Type, Host, Port, Node, Host1, Code)
+ || {Host1,Code} <- Hosts].
+
+dos_hostname_poll1(Type, Host, Port, Node, Host1, Code) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ dos_hostname_request(Host1),
+ [{statuscode, Code},
+ {version, "HTTP/1.0"}]).
+
+dos_hostname_request(Host) ->
+ "GET / HTTP/1.0\r\n" ++ Host ++ "\r\n\r\n".
+
+get_nof_clients(Mode, Load) ->
+ get_nof_clients(test_server:os_type(), Mode, Load).
+
+get_nof_clients(vxworks, _, light) -> 1;
+get_nof_clients(vxworks, ip_comm, medium) -> 3;
+get_nof_clients(vxworks, ssl, medium) -> 3;
+get_nof_clients(vxworks, ip_comm, heavy) -> 5;
+get_nof_clients(vxworks, ssl, heavy) -> 5;
+get_nof_clients(_, ip_comm, light) -> 5;
+get_nof_clients(_, ssl, light) -> 2;
+get_nof_clients(_, ip_comm, medium) -> 10;
+get_nof_clients(_, ssl, medium) -> 4;
+get_nof_clients(_, ip_comm, heavy) -> 20;
+get_nof_clients(_, ssl, heavy) -> 6.
+
+%% Make a file 100 bytes long containing 012...9*10
+create_range_data(Path)->
+ PathAndFileName=filename:join([Path,"range.txt"]),
+ file:write_file(PathAndFileName,list_to_binary(["12345678901234567890",
+ "12345678901234567890",
+ "12345678901234567890",
+ "12345678901234567890",
+ "12345678901234567890"])).
+
+%% create_ipv6_config(Config, FileName, Ipv6Address) ->
+%% ServerRoot = ?config(server_root, Config),
+%% TcTopDir = ?config(tc_top_dir, Config),
+%% Port = ?config(port, Config),
+%% SockType = ?config(sock_type, Config),
+%%
+%% MaxHdrSz = io_lib:format("~p", [256]),
+%% MaxHdrAct = io_lib:format("~p", [close]),
+%%
+%% Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi"
+%% " mod_include mod_dir mod_get mod_head"
+%% " mod_log mod_disk_log mod_trace",
+%%
+%% HttpConfig = [cline(["BindAddress ", "[" ++ Ipv6Address ++"]|inet6"]),
+%% cline(["Port ", integer_to_list(Port)]),
+%% cline(["ServerName ", "httpc_test"]),
+%% cline(["SocketType ", atom_to_list(SockType)]),
+%% cline([Mod_order]),
+%% cline(["ServerRoot ", ServerRoot]),
+%% cline(["DocumentRoot ",
+%% filename:join(ServerRoot, "htdocs")]),
+%% cline(["MaxHeaderSize ",MaxHdrSz]),
+%% cline(["MaxHeaderAction ",MaxHdrAct]),
+%% cline(["DirectoryIndex ", "index.html "]),
+%% cline(["DefaultType ", "text/plain"])],
+%% ConfigFile = filename:join([TcTopDir,FileName]),
+%% {ok, Fd} = file:open(ConfigFile, [write]),
+%% ok = file:write(Fd, lists:flatten(HttpConfig)),
+%% ok = file:close(Fd).
diff --git a/lib/inets/test/httpd_SUITE_data/Makefile.src b/lib/inets/test/httpd_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..b0fdb43d8d
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/Makefile.src
@@ -0,0 +1,14 @@
+CC = @CC@
+LD = @LD@
+CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@
+CROSSLDFLAGS = @CROSSLDFLAGS@
+
+PROGS = cgi_echo@exe@
+
+all: $(PROGS)
+
+cgi_echo@exe@: cgi_echo@obj@
+ $(LD) $(CROSSLDFLAGS) -o cgi_echo cgi_echo@obj@ @LIBS@
+
+cgi_echo@obj@: cgi_echo.c
+ $(CC) -c -o cgi_echo@obj@ $(CFLAGS) cgi_echo.c
diff --git a/lib/inets/test/httpd_SUITE_data/cgi_echo.c b/lib/inets/test/httpd_SUITE_data/cgi_echo.c
new file mode 100644
index 0000000000..580f860e96
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/cgi_echo.c
@@ -0,0 +1,97 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#if defined __WIN32__
+#include <windows.h>
+#include <fcntl.h>
+#endif
+
+static int read_exact(char *buffer, int len);
+static int write_exact(char *buffer, int len);
+
+int main(void)
+{
+ char msg[100];
+ int msg_len;
+#ifdef __WIN32__
+ _setmode(_fileno( stdin), _O_BINARY);
+ _setmode(_fileno( stdout), _O_BINARY);
+#endif
+ msg_len = read_exact(msg, 100);
+
+ write_exact("Content-type: text/plain\r\n\r\n", 28);
+ write_exact(msg, msg_len);
+ exit(EXIT_SUCCESS);
+}
+
+
+/* read from stdin */
+#ifdef __WIN32__
+static int read_exact(char *buffer, int len)
+{
+ HANDLE standard_input = GetStdHandle(STD_INPUT_HANDLE);
+
+ unsigned read_result;
+ unsigned sofar = 0;
+
+ if (!len) { /* Happens for "empty packages */
+ return 0;
+ }
+ for (;;) {
+ if (!ReadFile(standard_input, buffer + sofar,
+ len - sofar, &read_result, NULL)) {
+ return -1; /* EOF */
+ }
+ if (!read_result) {
+ return -2; /* Interrupted while reading? */
+ }
+ sofar += read_result;
+ if (sofar == len) {
+ return len;
+ }
+ }
+}
+#else
+static int read_exact(char *buffer, int len) {
+ int i, got = 0;
+
+ do {
+ if ((i = read(0, buffer + got, len - got)) <= 0)
+ return(i);
+ got += i;
+ } while (got < len);
+ return len;
+
+}
+#endif
+
+/* write to stdout */
+#ifdef __WIN32__
+ static int write_exact(char *buffer, int len)
+ {
+ HANDLE standard_output = GetStdHandle(STD_OUTPUT_HANDLE);
+ unsigned written;
+
+ if (!WriteFile(standard_output, buffer, len, &written, NULL)) {
+ return -1; /* Broken Pipe */
+ }
+ if (written < ((unsigned) len)) {
+ /* This should not happen, standard output is not blocking? */
+ return -2;
+ }
+
+ return (int) written;
+}
+
+#else
+ static int write_exact(char *buffer, int len) {
+ int i, wrote = 0;
+
+ do {
+ if ((i = write(1, buffer + wrote, len - wrote)) <= 0)
+ return i;
+ wrote += i;
+ } while (wrote < len);
+ return len;
+ }
+#endif
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/auth/group b/lib/inets/test/httpd_SUITE_data/server_root/auth/group
new file mode 100644
index 0000000000..b3da0ccbd3
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/auth/group
@@ -0,0 +1,3 @@
+group1: one two
+group2: two three
+group3: three Aladdin
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/auth/passwd b/lib/inets/test/httpd_SUITE_data/server_root/auth/passwd
new file mode 100644
index 0000000000..8c980ff547
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/auth/passwd
@@ -0,0 +1,4 @@
+one:onePassword
+two:twoPassword
+three:threePassword
+Aladdin:AladdinPassword
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.bat b/lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.bat
new file mode 100644
index 0000000000..25a49a1536
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.bat
@@ -0,0 +1,9 @@
+@echo off
+echo tomrad > c:\cygwin\tmp\hej
+echo Content-type: text/html
+echo.
+echo ^<HTML^> ^<HEAD^> ^<TITLE^>OS Environment^</TITLE^> ^</HEAD^> ^<BODY^>^<PRE^>
+set
+echo ^</PRE^>^</BODY^>^</HTML^>
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.sh b/lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.sh
new file mode 100755
index 0000000000..de81de9bde
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/cgi-bin/printenv.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+echo "Content-type: text/html"
+echo ""
+echo "<HTML> <HEAD> <TITLE>OS Environment</TITLE> </HEAD> <BODY><PRE>"
+env
+echo "</PRE></BODY></HTML>" \ No newline at end of file
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/conf/8080.conf b/lib/inets/test/httpd_SUITE_data/server_root/conf/8080.conf
new file mode 100644
index 0000000000..48e66f0114
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/conf/8080.conf
@@ -0,0 +1,79 @@
+Port 8080
+#ServerName your.server.net
+SocketType ip_comm
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_include mod_dir mod_get mod_head mod_log mod_disk_log
+ServerAdmin [email protected]
+ServerRoot /var/tmp/server_root
+ErrorLog logs/error_log_8080
+TransferLog logs/access_log_8080
+SecurityLog logs/security_log_8080
+ErrorDiskLog logs/error_disk_log_8080
+ErrorDiskLogSize 200000 10
+TransferDiskLog logs/access_disk_log_8080
+TransferDiskLogSize 200000 10
+SecurityDiskLog logs/security_disk_log
+SecurityDiskLogSize 200000 10
+MaxClients 50
+#KeepAlive 5
+#KeepAliveTimeout 10
+DocumentRoot /var/tmp/server_root/htdocs
+DirectoryIndex index.html welcome.html
+DefaultType text/plain
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+ErlScriptAlias /cgi-bin/erl httpd_example io
+EvalScriptAlias /eval httpd_example io
+#Script HEAD /cgi-bin/printenv.sh
+#Action image/gif /cgi-bin/printenv.sh
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthDBType plain
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthDBType plain
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthDBType plain
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthDBType mnesia
+AuthName Open Area
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthDBType mnesia
+AuthName Secret Area
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthDBType mnesia
+AuthName Top Secret Area
+require group group3
+allow from 130.100.34 130.100.35
+deny from 100.234.22.12 194.100.34.1 130.100.34.25
+SecurityDataFile logs/security_data
+SecurityMaxRetries 3
+SecurityBlockTime 10
+SecurityFailExpireTime 1
+SecurityAuthTimeout 1
+SecurityCallbackModule security_callback
+</Directory>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/conf/8888.conf b/lib/inets/test/httpd_SUITE_data/server_root/conf/8888.conf
new file mode 100644
index 0000000000..79bb7fcca4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/conf/8888.conf
@@ -0,0 +1,63 @@
+Port 8888
+#ServerName your.server.net
+SocketType ip_comm
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_include mod_dir mod_get mod_head mod_log mod_disk_log
+ServerAdmin [email protected]
+ServerRoot /var/tmp/server_root
+ErrorLog logs/error_log_8888
+TransferLog logs/access_log_8888
+ErrorDiskLog logs/error_disk_log_8888
+ErrorDiskLogSize 200000 10
+TransferDiskLog logs/access_disk_log_8888
+TransferDiskLogSize 200000 10
+MaxClients 150
+DocumentRoot /var/tmp/server_root/htdocs
+DirectoryIndex index.html welcome.html
+DefaultType text/plain
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+ErlScriptAlias /cgi-bin/erl httpd_example io
+EvalScriptAlias /eval httpd_example io
+#Script HEAD /cgi-bin/printenv.sh
+#Action image/gif /cgi-bin/printenv.sh
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthName Open Area
+AuthMnesiaDB On
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthName Secret Area
+AuthMnesiaDB On
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthName Top Secret Area
+AuthMnesiaDB On
+require group group3
+</Directory>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/conf/httpd.conf b/lib/inets/test/httpd_SUITE_data/server_root/conf/httpd.conf
new file mode 100644
index 0000000000..8a74ed1afd
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/conf/httpd.conf
@@ -0,0 +1,268 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+#
+
+# Port: The port the standalone listens to. For ports < 1023, you will
+# need httpd to be run as root initially.
+
+Port 8888
+
+# BindAddress: This directive is used to tell the server which IP address
+# to listen to. It can either contain "*", an IP address, or a fully
+# qualified Internet domain name.
+#
+# It is also possible to specify the ip-family with the directive.
+# There ar three possible value: inet, inet6 and inet6fb4
+# inet: Use IpFamily inet when retreiving the address and
+# fail if that does not work.
+# inet6: Use IpFamily inet6 when retreiving the address and
+# fail if that does not work.
+# inet6fb4: First IpFamily inet6 is tried and if that does not work,
+# inet is used as fallback.
+# Default value for ip-family is inet6fb4
+#
+# The syntax is: <address>[|<ip-family>]
+#
+#BindAddress *
+#BindAddress *|inet
+
+
+# ServerName allows you to set a host name which is sent back to clients for
+# your server if it's different than the one the program would get (i.e. use
+# "www" instead of the host's real name).
+#
+# Note: You cannot just invent host names and hope they work. The name you
+# define here must be a valid DNS name for your host. If you don't understand
+# this, ask your network administrator.
+
+#ServerName your.server.net
+
+# SocketType is either ip_comm, sockets or ssl.
+
+SocketType ip_comm
+
+# Modules: Server run-time plug-in modules written using the Erlang
+# Web Server API (EWSAPI). The server API make it easy to add functionality
+# to the server. Read more about EWSAPI in the Reference Manual.
+# WARNING! Do not tamper with this directive unless you are familiar with
+# EWSAPI.
+
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_include mod_dir mod_get mod_log mod_disk_log
+
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed.
+
+ServerAdmin [email protected]
+
+# ServerRoot: The directory the server's config, error, and log files
+# are kept in
+
+ServerRoot /var/tmp/server_root
+
+# ErrorLog: The location of the error log file. If this does not start
+# with /, ServerRoot is prepended to it.
+
+ErrorLog logs/error_log
+
+# TransferLog: The location of the transfer log file. If this does not
+# start with /, ServerRoot is prepended to it.
+
+TransferLog logs/access_log
+
+# SecurityLog: The location of the security log file (mod_security required)
+#
+SecurityLog logs/security_log
+
+# ErrorDiskLog: The location of the error log file. If this does not
+# start with /, ServerRoot is prepended to it. This log file is managed
+# with the disk_log module [See disk_log(3)]. The ErrorDiskLogSize directive
+# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+# truncates the first file.
+
+ErrorDiskLog logs/error_disk_log
+ErrorDiskLogSize 200000 10
+
+# TransferDiskLog: The location of the transfer log file. If this does not
+# start with /, ServerRoot is prepended to it. This log file is managed
+# with the disk_log module [See disk_log(3)]. The TransferDiskLogSize directive
+# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+# truncates the first file.
+
+TransferDiskLog logs/access_disk_log
+TransferDiskLogSize 200000 10
+
+# SecurityDiskLog: The location of the security log file. If this does not
+# start with /, ServerRoot is prepended to it. This log file is managed
+# with the disk_log module [See disk_log(3)]. The SecurityDiskLogSize directive
+# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+# truncates the first file.
+
+SecurityDiskLog logs/security_disk_log
+SecurityDiskLogSize 200000 10
+
+# Limit on total number of servers running, i.e., limit on the number
+# of clients who can simultaneously connect --- if this limit is ever
+# reached, clients will be LOCKED OUT, so it should NOT BE SET TOO LOW.
+# It is intended mainly as a brake to keep a runaway server from taking
+# the server with it as it spirals down...
+
+MaxClients 50
+
+# KeepAlive set the flag for persistent connections. For peristent connections
+# set KeepAlive to on. To use One request per connection set the flag to off
+# Note: The value has changed since previous version of INETS.
+KeepAlive on
+
+# KeepAliveTimeout sets the number of seconds before a persistent connection
+# times out and closes.
+KeepAliveTimeout 10
+
+# MaxKeepAliveRequests sets the number of seconds before a persistent connection
+# times out and closes.
+MaxKeepAliveRequests 10
+
+
+
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+
+DocumentRoot /var/tmp/server_root/htdocs
+
+# DirectoryIndex: Name of the file or files to use as a pre-written HTML
+# directory index. Separate multiple entries with spaces.
+
+DirectoryIndex index.html welcome.html
+
+# DefaultType is the default MIME type for documents which the server
+# cannot find the type of from filename extensions.
+
+DefaultType text/plain
+
+# Aliases: Add here as many aliases as you need (with no limit). The format is
+# Alias fakename realname
+
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+
+# ScriptAlias: This controls which directories contain server scripts.
+# Format: ScriptAlias fakename realname
+
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+
+# This directive adds an action, which will activate cgi-script when a
+# file is requested using the method of method, which can be one of
+# GET, POST and HEAD. It sends the URL and file path of the requested
+# document using the standard CGI PATH_INFO and PATH_TRANSLATED
+# environment variables.
+
+#Script HEAD /cgi-bin/printenv.sh
+
+# This directive adds an action, which will activate cgi-script when a
+# file of content type mime-type is requested. It sends the URL and
+# file path of the requested document using the standard CGI PATH_INFO
+# and PATH_TRANSLATED environment variables.
+
+#Action image/gif /cgi-bin/printenv.sh
+
+# ErlScriptAlias: This specifies how "Erl" server scripts are called.
+# Format: ErlScriptAlias fakename realname allowed_modules
+
+ErlScriptAlias /down/erl httpd_example io
+
+# EvalScriptAlias: This specifies how "Eval" server scripts are called.
+# Format: EvalScriptAlias fakename realname allowed_modules
+
+EvalScriptAlias /eval httpd_example io
+
+# Point SSLCertificateFile at a PEM encoded certificate.
+
+SSLCertificateFile /var/tmp/server_root/ssl/ssl_server.pem
+
+# If the key is not combined with the certificate, use this directive to
+# point at the key file.
+
+SSLCertificateKeyFile /var/tmp/server_root/ssl/ssl_server.pem
+
+# Set SSLVerifyClient to:
+# 0 if no certicate is required
+# 1 if the client may present a valid certificate
+# 2 if the client must present a valid certificate
+# 3 if the client may present a valid certificate but it is not required to
+# have a valid CA
+
+SSLVerifyClient 0
+
+# Each directory to which INETS has access, can be configured with respect
+# to which services and features are allowed and/or disabled in that
+# directory (and its subdirectories).
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthDBType plain
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthDBType plain
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthDBType plain
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthDBType mnesia
+AuthName Open Area
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthDBType mnesia
+AuthName Secret Area
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthDBType mnesia
+AuthName Top Secret Area
+require group group3
+allow from 130.100.34 130.100.35
+deny from 100.234.22.12 194.100.34.1 130.100.34.25
+SecurityDataFile logs/security_data
+SecurityMaxRetries 3
+SecurityBlockTime 10
+SecurityFailExpireTime 1
+SecurityAuthTimeout 1
+SecurityCallbackModule security_callback
+</Directory>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/conf/mime.types b/lib/inets/test/httpd_SUITE_data/server_root/conf/mime.types
new file mode 100644
index 0000000000..d2f81e4e5e
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/conf/mime.types
@@ -0,0 +1,465 @@
+# This is a comment. I love comments.
+
+# MIME type Extension
+application/EDI-Consent
+application/EDI-X12
+application/EDIFACT
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atomicmail
+application/batch-SMTP
+application/beep+xml
+application/cals-1840
+application/commonground
+application/cybercash
+application/dca-rft
+application/dec-dx
+application/dvcs
+application/eshop
+application/http
+application/hyperstudio
+application/iges
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/font-tdpfr
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc
+application/mathematica
+application/mathematica-old
+application/msword doc
+application/news-message-id
+application/news-transmission
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh exe class so dll
+application/oda oda
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted
+application/pgp-keys
+application/pgp-signature
+application/pkcs10
+application/pkcs7-mime
+application/pkcs7-signature
+application/pkix-cert
+application/pkix-crl
+application/pkixcmp
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww
+application/prs.nprend
+application/qsig
+application/remote-printing
+application/riscos
+application/rtf
+application/sdp
+application/set-payment
+application/set-payment-initiation
+application/set-registration
+application/set-registration-initiation
+application/sgml
+application/sgml-open-catalog
+application/sieve
+application/slate
+application/smil smi smil
+application/timestamp-query
+application/timestamp-reply
+application/vemmi
+application/vnd.3M.Post-it-Notes
+application/vnd.FloGraphIt
+application/vnd.accpac.simply.aso
+application/vnd.accpac.simply.imp
+application/vnd.acucobol
+application/vnd.aether.imp
+application/vnd.anser-web-certificate-issue-initiation
+application/vnd.anser-web-funds-transfer-initiation
+application/vnd.audiograph
+application/vnd.businessobjects
+application/vnd.bmi
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.claymore
+application/vnd.commerce-battelle
+application/vnd.commonspace
+application/vnd.comsocaller
+application/vnd.contact.cmsg
+application/vnd.cosmocaller
+application/vnd.cups-postscript
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.ctc-posml
+application/vnd.cybank
+application/vnd.dna
+application/vnd.dpgraph
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven
+application/vnd.epson.esf
+application/vnd.epson.msf
+application/vnd.epson.quickanime
+application/vnd.epson.salt
+application/vnd.epson.ssf
+application/vnd.ericsson.quickcall
+application/vnd.eudora.data
+application/vnd.fdf
+application/vnd.ffsns
+application/vnd.framemaker
+application/vnd.fsc.weblaunch
+application/vnd.fujitsu.oasys
+application/vnd.fujitsu.oasys2
+application/vnd.fujitsu.oasys3
+application/vnd.fujitsu.oasysgp
+application/vnd.fujitsu.oasysprs
+application/vnd.fujixerox.ddd
+application/vnd.fujixerox.docuworks
+application/vnd.fujixerox.docuworks.binder
+application/vnd.fut-misnet
+application/vnd.grafeq
+application/vnd.groove-account
+application/vnd.groove-identity-message
+application/vnd.groove-injector
+application/vnd.groove-tool-message
+application/vnd.groove-tool-template
+application/vnd.groove-vcard
+application/vnd.hhe.lesson-player
+application/vnd.hp-HPGL
+application/vnd.hp-PCL
+application/vnd.hp-PCLXL
+application/vnd.hp-hpid
+application/vnd.hp-hps
+application/vnd.httphone
+application/vnd.hzn-3d-crossword
+application/vnd.ibm.afplinedata
+application/vnd.ibm.MiniPay
+application/vnd.ibm.modcap
+application/vnd.informix-visionary
+application/vnd.intercon.formnet
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo
+application/vnd.intu.qfx
+application/vnd.irepository.package+xml
+application/vnd.is-xpr
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.koan
+application/vnd.lotus-1-2-3
+application/vnd.lotus-approach
+application/vnd.lotus-freelance
+application/vnd.lotus-notes
+application/vnd.lotus-organizer
+application/vnd.lotus-screencam
+application/vnd.lotus-wordpro
+application/vnd.mcd
+application/vnd.mediastation.cdkey
+application/vnd.meridian-slingshot
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf
+application/vnd.mobius.dis
+application/vnd.mobius.msl
+application/vnd.mobius.plc
+application/vnd.mobius.txf
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml
+application/vnd.ms-artgalry
+application/vnd.ms-asf
+application/vnd.ms-excel xls
+application/vnd.ms-lrm
+application/vnd.ms-powerpoint ppt
+application/vnd.ms-project
+application/vnd.ms-tnef
+application/vnd.ms-works
+application/vnd.mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician
+application/vnd.netfpx
+application/vnd.noblenet-directory
+application/vnd.noblenet-sealer
+application/vnd.noblenet-web
+application/vnd.novadigm.EDM
+application/vnd.novadigm.EDX
+application/vnd.novadigm.EXT
+application/vnd.osa.netdeploy
+application/vnd.palm
+application/vnd.pg.format
+application/vnd.pg.osasli
+application/vnd.powerbuilder6
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.previewsystems.box
+application/vnd.publishare-delta-tree
+application/vnd.pvi.ptid1
+application/vnd.pwg-xhtml-print+xml
+application/vnd.rapid
+application/vnd.s3sms
+application/vnd.seemail
+application/vnd.shana.informed.formdata
+application/vnd.shana.informed.formtemplate
+application/vnd.shana.informed.interchange
+application/vnd.shana.informed.package
+application/vnd.sss-cod
+application/vnd.sss-dtf
+application/vnd.sss-ntf
+application/vnd.street-stream
+application/vnd.svd
+application/vnd.swiftview-ics
+application/vnd.triscape.mxs
+application/vnd.trueapp
+application/vnd.truedoc
+application/vnd.tve-trigger
+application/vnd.ufdl
+application/vnd.uplanet.alert
+application/vnd.uplanet.alert-wbxml
+application/vnd.uplanet.bearer-choice-wbxml
+application/vnd.uplanet.bearer-choice
+application/vnd.uplanet.cacheop
+application/vnd.uplanet.cacheop-wbxml
+application/vnd.uplanet.channel
+application/vnd.uplanet.channel-wbxml
+application/vnd.uplanet.list
+application/vnd.uplanet.list-wbxml
+application/vnd.uplanet.listcmd
+application/vnd.uplanet.listcmd-wbxml
+application/vnd.uplanet.signal
+application/vnd.vcx
+application/vnd.vectorworks
+application/vnd.vidsoft.vidconference
+application/vnd.visio
+application/vnd.vividence.scriptfile
+application/vnd.wap.sic
+application/vnd.wap.slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo
+application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf
+application/vnd.xara
+application/vnd.xfdl
+application/vnd.yellowriver-custom-menu
+application/whoispp-query
+application/whoispp-response
+application/wita
+application/wordperfect5.1
+application/x-bcpio bcpio
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-gzip
+application/x-hdf hdf
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/x400-bp
+application/xml
+application/xml-dtd
+application/xml-external-parsed-entity
+application/zip zip
+audio/32kadpcm
+audio/basic au snd
+audio/g.722.1
+audio/l16
+audio/midi mid midi kar
+audio/mp4a-latm
+audio/mpa-robust
+audio/mpeg mpga mp2 mp3
+audio/parityfec
+audio/prs.sid
+audio/telephone-event
+audio/tone
+audio/vnd.cisco.nse
+audio/vnd.cns.anp1
+audio/vnd.cns.inf1
+audio/vnd.digital-winds
+audio/vnd.everad.plj
+audio/vnd.lucent.voice
+audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800
+audio/vnd.nuera.ecelp7470
+audio/vnd.nuera.ecelp9600
+audio/vnd.octel.sbc
+audio/vnd.qcelp
+audio/vnd.rhetorex.32kadpcm
+audio/vnd.vmx.cvsd
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-pn-realaudio ram rm
+audio/x-pn-realaudio-plugin rpm
+audio/x-realaudio ra
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm
+image/g3fax
+image/gif gif
+image/ief ief
+image/jpeg jpeg jpg jpe
+image/naplps
+image/png png
+image/prs.btif
+image/prs.pti
+image/tiff tiff tif
+image/vnd.cns.inf2
+image/vnd.dwg
+image/vnd.dxf
+image/vnd.fastbidsheet
+image/vnd.fpx
+image/vnd.fst
+image/vnd.fujixerox.edmics-mmr
+image/vnd.fujixerox.edmics-rlc
+image/vnd.mix
+image/vnd.net-fpx
+image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff
+image/x-cmu-raster ras
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+message/delivery-status
+message/disposition-notification
+message/external-body
+message/http
+message/news
+message/partial
+message/rfc822
+message/s-http
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.dwf
+model/vnd.flatland.3dml
+model/vnd.gdl
+model/vnd.gs-gdl
+model/vnd.gtw
+model/vnd.mts
+model/vnd.vtu
+model/vrml wrl vrml
+multipart/alternative
+multipart/appledouble
+multipart/byteranges
+multipart/digest
+multipart/encrypted
+multipart/form-data
+multipart/header-set
+multipart/mixed
+multipart/parallel
+multipart/related
+multipart/report
+multipart/signed
+multipart/voice-message
+text/calendar
+text/css css
+text/directory
+text/enriched
+text/html html htm
+text/parityfec
+text/plain asc txt
+text/prs.lines.tag
+text/rfc822-headers
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/t140
+text/uri-list
+text/vnd.DMClientScript
+text/vnd.IPTC.NITF
+text/vnd.IPTC.NewsML
+text/vnd.abc
+text/vnd.curl
+text/vnd.flatland.3dml
+text/vnd.fly
+text/vnd.fmi.flexstor
+text/vnd.in3d.3dml
+text/vnd.in3d.spot
+text/vnd.latex-z
+text/vnd.motorola.reflex
+text/vnd.ms-mediapackage
+text/vnd.wap.si
+text/vnd.wap.sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+text/x-server-parsed-html shtml
+text/xml xml xsl
+text/xml-external-parsed-entity
+video/mp4v-es
+video/mpeg mpeg mpg mpe
+video/parityfec
+video/pointer
+video/quicktime qt mov
+video/vnd.fvt
+video/vnd.motorola.video
+video/vnd.motorola.videop
+video/vnd.mpegurl mxu
+video/vnd.mts
+video/vnd.nokia.interleaved-multimedia
+video/vnd.vivo
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/conf/ssl.conf b/lib/inets/test/httpd_SUITE_data/server_root/conf/ssl.conf
new file mode 100644
index 0000000000..8b8c57a98b
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/conf/ssl.conf
@@ -0,0 +1,66 @@
+Port 8088
+#ServerName your.server.net
+SocketType ssl
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_include mod_dir mod_get mod_head mod_log mod_disk_log
+ServerAdmin [email protected]
+ServerRoot /var/tmp/server_root
+ErrorLog logs/error_log_8088
+TransferLog logs/access_log_8088
+ErrorDiskLog logs/error_disk_log_8088
+ErrorDiskLogSize 200000 10
+TransferDiskLog logs/access_disk_log_8088
+TransferDiskLogSize 200000 10
+MaxClients 150
+DocumentRoot /var/tmp/server_root/htdocs
+DirectoryIndex index.html welcome.html
+DefaultType text/plain
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+ErlScriptAlias /cgi-bin/erl httpd_example io
+EvalScriptAlias /eval httpd_example io
+SSLCertificateFile /var/tmp/server_root/ssl/ssl_server.pem
+SSLCertificateKeyFile /var/tmp/server_root/ssl/ssl_server.pem
+SSLVerifyClient 0
+#Script HEAD /cgi-bin/printenv.sh
+#Action image/gif /cgi-bin/printenv.sh
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthName Open Area
+AuthMnesiaDB On
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthName Secret Area
+AuthMnesiaDB On
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthName Top Secret Area
+AuthMnesiaDB On
+require group group3
+</Directory>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/config.shtml b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/config.shtml
new file mode 100644
index 0000000000..107e3ff610
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/config.shtml
@@ -0,0 +1,70 @@
+<HTML>
+<HEAD>
+<TITLE>/ssi.html (17-Apr-1997)</TITLE>
+</HEAD>
+<BODY>
+<H1>/ssi.html</H1>
+
+<!-- ************* CONFIG ************* -->
+
+<!--#config timefmt="%a %b %e %T %Z %Y" sizefmt="abbrev"-->
+<!--#config errmsg="[an especially ugly error occurred while processing this directive]"-->
+
+<!-- ************* INCLUDE ************* -->
+
+<P>Include /misc/friedrich.html:
+<!--#include virtual="/misc/friedrich.html"-->
+<P>Include /misc/not_defined.html: <!--#include virtual="/misc/not_defined.html"-->
+<P>Include misc/friedrich.html:
+<!--#include file="misc/friedrich.html"-->
+<P>Include not_defined.html: <!--#include file="not_defined.html"-->
+
+<P><HR>
+
+<!-- ************* ECHO ************* -->
+
+<P>DOCUMENT_NAME: <!--#echo var="DOCUMENT_NAME"-->
+<P>DOCUMENT_URI: <!--#echo var="DOCUMENT_URI"-->
+<P>QUERY_STRING_UNESCAPED: <!--#echo var="QUERY_STRING_UNESCAPED"-->
+<P>DATE_LOCAL: <!--#echo var="DATE_LOCAL"-->
+<P>DATE_GMT: <!--#echo var="DATE_GMT"-->
+<P>LAST_MODIFIED: <!--#echo var="LAST_MODIFIED"-->
+<P>NOT_DEFINED: <!--#echo var="NOT_DEFINED"-->
+
+<P><HR>
+
+<!-- ************* FSIZE ************* -->
+
+<P>Size of index.html: <!--#fsize file="index.html"-->
+<P>Size of not_defined.html: <!--#fsize file="not_defined.html"-->
+<!--#config sizefmt="bytes"-->
+<P>Size of /misc/friedrich.html: <!--#fsize virtual="/misc/friedrich.html"-->
+<P>Size of /misc/not_defined.html: <!--#fsize virtual="/misc/not_defined.html"-->
+
+<P><HR>
+
+<!-- ************* FLASTMOD ************* -->
+
+<P>Last modification of index.html: <!--#flastmod file="index.html"-->
+<P>Last modification of not_defined.html: <!--#flastmod file="not_defined.html"-->
+<P>Last modification of /misc/friedrich.html: <!--#flastmod virtual="/misc/friedrich.html"-->
+<P>Last modification of /misc/not_defined.html: <!--#flastmod virtual="/misc/not_defined.html"-->
+
+<!--#exec cmd="ls"-->
+<!--#exec cmd="printenv"-->
+<!--#exec cmd="sunemaja"-->
+
+<!--#exec cgi="/cgi-bin/printenv.sh"-->
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_open/dummy.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_open/dummy.html
new file mode 100644
index 0000000000..a6e8a35a04
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_open/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/open/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/open/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/dummy.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/dummy.html
new file mode 100644
index 0000000000..016b04e540
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/secret/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/top_secret/index.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/top_secret/index.html
new file mode 100644
index 0000000000..34db3d5d1a
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/dets_secret/top_secret/index.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/top_secret/index.html (04-Feb-1998)</TITLE>
+<!-- Created by: Mattias Nilsson, 04-Feb-1998 -->
+</HEAD>
+<BODY>
+<H1>/secret/top_secret/index.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/echo.shtml b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/echo.shtml
new file mode 100644
index 0000000000..141db5be59
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/echo.shtml
@@ -0,0 +1,35 @@
+<HTML>
+<HEAD>
+<TITLE>/echo.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/echo.shtml</H1>
+
+<P>DOCUMENT_NAME: <!--#echo var="DOCUMENT_NAME"-->
+
+<P>DOCUMENT_URI: <!--#echo var="DOCUMENT_URI"-->
+
+<P>QUERY_STRING_UNESCAPED: <!--#echo var="QUERY_STRING_UNESCAPED"-->
+
+<P>DATE_LOCAL: <!--#echo var="DATE_LOCAL"-->
+
+<P>DATE_GMT: <!--#echo var="DATE_GMT"-->
+
+<P>LAST_MODIFIED: <!--#echo var="LAST_MODIFIED"-->
+
+<P>NOT_DEFINED: <!--#echo var="NOT_DEFINED"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/exec.shtml b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/exec.shtml
new file mode 100644
index 0000000000..97333da898
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/exec.shtml
@@ -0,0 +1,30 @@
+<HTML>
+<HEAD>
+<TITLE>/exec.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/exec.shtml</H1>
+<PRE>
+<!--#exec cmd="ls"-->
+<HR>
+<!--#exec cmd="printenv"-->
+<HR>
+<!--#exec cmd="sunemaja"-->
+<HR>
+<!--#exec cgi="/cgi-bin/printenv.sh"-->
+</PRE>
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/flastmod.shtml b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/flastmod.shtml
new file mode 100644
index 0000000000..d54c36fe50
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/flastmod.shtml
@@ -0,0 +1,29 @@
+<HTML>
+<HEAD>
+<TITLE>/flastmod.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/flastmod.shtml</H1>
+
+<P>Last modification of index.html: <!--#flastmod file="index.html"-->
+
+<P>Last modification of not_defined.html: <!--#flastmod file="not_defined.html"-->
+
+<P>Last modification of /misc/friedrich.html: <!--#flastmod virtual="/misc/friedrich.html"-->
+
+<P>Last modification of /misc/not_defined.html: <!--#flastmod virtual="/misc/not_defined.html"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/fsize.shtml b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/fsize.shtml
new file mode 100644
index 0000000000..570ee9cf6d
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/fsize.shtml
@@ -0,0 +1,29 @@
+<HTML>
+<HEAD>
+<TITLE>/fsize.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/fsize.shtml</H1>
+
+<P>Size of index.html: <!--#fsize file="index.html"-->
+
+<P>Size of not_defined.html: <!--#fsize file="not_defined.html"-->
+
+<P>Size of /misc/friedrich.html: <!--#fsize virtual="/misc/friedrich.html"-->
+
+<P>Size of /misc/not_defined.html: <!--#fsize virtual="/misc/not_defined.html"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/include.shtml b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/include.shtml
new file mode 100644
index 0000000000..529aad0437
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/include.shtml
@@ -0,0 +1,33 @@
+<HTML>
+<HEAD>
+<TITLE>/include.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/include.shtml</H1>
+
+<P>Include /misc/friedrich.html:
+<!--#include virtual="/misc/friedrich.html"-->
+
+<P>Include /misc/not_defined.html:
+<!--#include virtual="/misc/not_defined.html"-->
+
+<P>Include misc/friedrich.html:
+<!--#include file="misc/friedrich.html"-->
+
+<P>Include not_defined.html:
+<!--#include file="not_defined.html"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/index.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/index.html
new file mode 100644
index 0000000000..cfdc9f9ab7
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/index.html
@@ -0,0 +1,25 @@
+<HTML>
+<HEAD>
+<TITLE>/index.html</TITLE>
+</HEAD>
+<BODY>
+<H1>/index.html</H1>
+
+<STRONG>Server-Side Include (SSI) commands:</STRONG><BR>
+<A HREF="config.shtml">config</A><BR>
+<A HREF="echo.shtml">echo</A><BR>
+<A HREF="exec.shtml">exec</A><BR>
+<A HREF="flastmod.shtml">flastmod</A><BR>
+<A HREF="fsize.shtml">fsize</A><BR>
+<A HREF="include.shtml">include</A><BR>
+
+<BR>
+<BR>
+
+<STRONG>ESI callback:</STRING><BR>
+<A HREF="cgi-bin/erl/httpd_example/get">cgi-bin/erl/httpd_example/get</A><BR>
+<A HREF="cgi-bin/erl/httpd_example/yahoo">cgi-bin/erl/httpd_example/yahoo</A><BR>
+<A HREF="cgi-bin/erl/httpd_example/test1">cgi-bin/erl/httpd_example/test1</A><BR>
+
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/last_modified.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/last_modified.html
new file mode 100644
index 0000000000..65c1790813
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/last_modified.html
@@ -0,0 +1,22 @@
+<HTML>
+<HEAD>
+<TITLE>/last_modified.html</TITLE>
+</HEAD>
+<BODY>
+<H1>/last_modified.html</H1>
+
+<P>This document is only used for test of illegal last-modified date.</P>
+
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/friedrich.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/friedrich.html
new file mode 100644
index 0000000000..d7953d5df4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/friedrich.html
@@ -0,0 +1,7 @@
+<P><CITE>
+Talking much about oneself can also be a means to conceal oneself.<BR>
+-- Friedrich Nietzsche
+</CITE>
+
+<P>Nested Include:
+<!--#include file="misc/oech.html"-->
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/oech.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/oech.html
new file mode 100644
index 0000000000..506064bf04
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/oech.html
@@ -0,0 +1,4 @@
+<P><CITE>
+What excuses stand in your way? How can you eliminate them?<BR>
+-- Roger von Oech
+</CITE>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/welcome.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/welcome.html
new file mode 100644
index 0000000000..8c17451f91
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/misc/welcome.html
@@ -0,0 +1 @@
+<HTML></HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_open/dummy.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_open/dummy.html
new file mode 100644
index 0000000000..a6e8a35a04
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_open/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/open/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/open/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/dummy.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/dummy.html
new file mode 100644
index 0000000000..016b04e540
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/secret/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/top_secret/index.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/top_secret/index.html
new file mode 100644
index 0000000000..2d17e8b596
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/mnesia_secret/top_secret/index.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+<TITLE>/mnesia_secret/top_secret/index.html (04-Feb-1998)</TITLE>
+<!-- Created by: Mattias Nilsson, 04-Feb-1998 -->
+</HEAD>
+<BODY>
+<H1>/mnesia_secret/top_secret/index.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/open/dummy.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/open/dummy.html
new file mode 100644
index 0000000000..a6e8a35a04
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/open/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/open/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/open/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/dummy.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/dummy.html
new file mode 100644
index 0000000000..016b04e540
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/secret/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/top_secret/index.html b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/top_secret/index.html
new file mode 100644
index 0000000000..34db3d5d1a
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/htdocs/secret/top_secret/index.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/top_secret/index.html (04-Feb-1998)</TITLE>
+<!-- Created by: Mattias Nilsson, 04-Feb-1998 -->
+</HEAD>
+<BODY>
+<H1>/secret/top_secret/index.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/README b/lib/inets/test/httpd_SUITE_data/server_root/icons/README
new file mode 100644
index 0000000000..a1fc5a5a9c
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/README
@@ -0,0 +1,161 @@
+Public Domain Icons
+
+ These icons were originally made for Mosaic for X and have been
+ included in the NCSA httpd and Apache server distributions in the
+ past. They are in the public domain and may be freely included in any
+ application. The originals were done by Kevin Hughes ([email protected]).
+
+ Many thanks to Andy Polyakov for tuning the icon colors and adding a
+ few new images. If you'd like to contribute additions or ideas to
+ this set, please let me know.
+
+ The distribution site for these icons is at:
+
+ http://www.eit.com/goodies/www.icons/
+
+ Kevin Hughes
+ September 11, 1995
+
+
+Suggested Uses
+
+The following are a few suggestions, to serve as a starting point for ideas.
+Please feel free to tweak and rename the icons as you like.
+
+ a.gif
+ This might be used to represent PostScript or text layout
+ languages.
+
+ alert.black.gif, alert.red.gif
+ These can be used to highlight any important items, such as a
+ README file in a directory.
+
+ back.gif, forward.gif
+ These can be used as links to go to previous and next areas.
+
+ ball.gray.gif, ball.red.gif
+ These might be used as bullets.
+
+ binary.gif
+ This can be used to represent binary files.
+
+ binhex.gif
+ This can represent BinHex-encoded data.
+
+ blank.gif
+ This can be used as a placeholder or a spacing element.
+
+ bomb.gif
+ This can be used to repreesnt core files.
+
+ box1.gif, box2.gif
+ These icons can be used to represent generic 3D applications and
+ related files.
+
+ broken.gif
+ This can represent corrupted data.
+
+ burst.gif
+ This can call attention to new and important items.
+
+ c.gif
+ This might represent C source code.
+
+ comp.blue.gif, comp.red.gif
+ These little computer icons can stand for telnet or FTP
+ sessions.
+
+ compressed.gif
+ This may represent compressed data.
+
+ continued.gif
+ This can be a link to a continued listing of a directory.
+
+ down.gif, up.gif, left.gif, right.gif
+ These can be used to scroll up, down, left and right in a
+ listing or may be used to denote items in an outline.
+
+ dvi.gif
+ This can represent DVI files.
+
+ f.gif
+ This might represent FORTRAN or Forth source code.
+
+ folder.gif, folder.open.gif, folder.sec.gif
+ The folder can represent directories. There is also a version
+ that can represent secure directories or directories that cannot
+ be viewed.
+
+ generic.gif, generic.sec.gif, generic.red.gif
+ These can represent generic files, secure files, and important
+ files, respectively.
+
+ hand.right.gif, hand.up.gif
+ These can point out important items (pun intended).
+
+ image1.gif, image2.gif, image3.gif
+ These can represent image formats of various types.
+
+ index.gif
+ This might represent a WAIS index or search facility.
+
+ layout.gif
+ This might represent files and formats that contain graphics as
+ well as text layout, such as HTML and PDF files.
+
+ link.gif
+ This might represent files that are symbolic links.
+
+ movie.gif
+ This can represent various movie formats.
+
+ p.gif
+ This may stand for Perl or Python source code.
+
+ pie0.gif ... pie8.gif
+ These icons can be used in applications where a list of
+ documents is returned from a search. The little pie chart images
+ can denote how relevant the documents may be to your search
+ query.
+
+ patch.gif
+ This may stand for patches and diff files.
+
+ portal.gif
+ This might be a link to an online service or a 3D world.
+
+ ps.gif, quill.gif
+ These may represent PostScript files.
+
+ screw1.gif, screw2.gif
+ These may represent CAD or engineering data and formats.
+
+ script.gif
+ This can represent any of various interpreted languages, such as
+ Perl, python, TCL, and shell scripts, as well as server
+ configuration files.
+
+ sound1.gif, sound2.gif
+ These can represent sound files.
+
+ sphere1.gif, sphere2.gif
+ These can represent 3D worlds or rendering applications and
+ formats.
+
+ tex.gif
+ This can represent TeX files.
+
+ text.gif
+ This can represent generic (plain) text files.
+
+ transfer.gif
+ This can represent FTP transfers or uploads/downloads.
+
+ unknown.gif
+ This may represent a file of an unknown type.
+
+ uuencoded.gif
+ This can stand for uuencoded data.
+
+ world1.gif, world2.gif
+ These can represent 3D worlds or other 3D formats.
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/a.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/a.gif
new file mode 100644
index 0000000000..bb23d971f4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/a.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/alert.black.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/alert.black.gif
new file mode 100644
index 0000000000..eaecd2172a
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/alert.black.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/alert.red.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/alert.red.gif
new file mode 100644
index 0000000000..a423894043
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/alert.red.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/apache_pb.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/apache_pb.gif
new file mode 100644
index 0000000000..3a1c139fc4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/apache_pb.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/back.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/back.gif
new file mode 100644
index 0000000000..a694ae1ec3
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/back.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/ball.gray.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/ball.gray.gif
new file mode 100644
index 0000000000..eb84268c4c
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/ball.gray.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/ball.red.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/ball.red.gif
new file mode 100644
index 0000000000..a8425cb574
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/ball.red.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/binary.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/binary.gif
new file mode 100644
index 0000000000..9a15cbae04
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/binary.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/binhex.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/binhex.gif
new file mode 100644
index 0000000000..62d0363108
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/binhex.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/blank.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/blank.gif
new file mode 100644
index 0000000000..0ccf01e198
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/blank.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/bomb.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/bomb.gif
new file mode 100644
index 0000000000..270fdb1c06
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/bomb.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/box1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/box1.gif
new file mode 100644
index 0000000000..65dcd002ea
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/box1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/box2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/box2.gif
new file mode 100644
index 0000000000..c43bc4faec
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/box2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/broken.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/broken.gif
new file mode 100644
index 0000000000..9f8cbe9f76
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/broken.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/burst.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/burst.gif
new file mode 100644
index 0000000000..fbdcf575f7
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/burst.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button1.gif
new file mode 100644
index 0000000000..eb97cb7333
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button10.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button10.gif
new file mode 100644
index 0000000000..fe0c97998c
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button10.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button2.gif
new file mode 100644
index 0000000000..7698455bf9
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button3.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button3.gif
new file mode 100644
index 0000000000..a8b8319232
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button3.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button4.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button4.gif
new file mode 100644
index 0000000000..0fd15a0d7f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button4.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button5.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button5.gif
new file mode 100644
index 0000000000..64241e5c5d
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button5.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button6.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button6.gif
new file mode 100644
index 0000000000..867cfd1212
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button6.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button7.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button7.gif
new file mode 100644
index 0000000000..b3f5fb248f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button7.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button8.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button8.gif
new file mode 100644
index 0000000000..7a308be8f6
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button8.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/button9.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/button9.gif
new file mode 100644
index 0000000000..9acba576c0
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/button9.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/buttonl.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/buttonl.gif
new file mode 100644
index 0000000000..3883088e7a
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/buttonl.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/buttonr.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/buttonr.gif
new file mode 100644
index 0000000000..c4dc3887db
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/buttonr.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/c.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/c.gif
new file mode 100644
index 0000000000..7555b6c164
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/c.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/comp.blue.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/comp.blue.gif
new file mode 100644
index 0000000000..f8d76a8c23
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/comp.blue.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/comp.gray.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/comp.gray.gif
new file mode 100644
index 0000000000..7664cd0364
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/comp.gray.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/compressed.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/compressed.gif
new file mode 100644
index 0000000000..39e732739f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/compressed.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/continued.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/continued.gif
new file mode 100644
index 0000000000..b0ffb7e0cc
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/continued.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/dir.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/dir.gif
new file mode 100644
index 0000000000..48264601ae
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/dir.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/down.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/down.gif
new file mode 100644
index 0000000000..a354c871cd
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/down.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/dvi.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/dvi.gif
new file mode 100644
index 0000000000..791be33105
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/dvi.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/f.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/f.gif
new file mode 100644
index 0000000000..fbe353c282
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/f.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.gif
new file mode 100644
index 0000000000..48264601ae
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.open.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.open.gif
new file mode 100644
index 0000000000..30979cb528
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.open.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.sec.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.sec.gif
new file mode 100644
index 0000000000..75332d9e59
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/folder.sec.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/forward.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/forward.gif
new file mode 100644
index 0000000000..b2959b4c85
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/forward.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.gif
new file mode 100644
index 0000000000..de60b2940f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.red.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.red.gif
new file mode 100644
index 0000000000..94743981d9
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.red.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.sec.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.sec.gif
new file mode 100644
index 0000000000..88d5240c3c
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/generic.sec.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/hand.right.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/hand.right.gif
new file mode 100644
index 0000000000..5cdbc7206d
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/hand.right.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/hand.up.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/hand.up.gif
new file mode 100644
index 0000000000..85a5d68317
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/hand.up.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/htdig.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/htdig.gif
new file mode 100644
index 0000000000..35443fb63a
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/htdig.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/icon.sheet.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/icon.sheet.gif
new file mode 100644
index 0000000000..ad1686e448
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/icon.sheet.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/image1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/image1.gif
new file mode 100644
index 0000000000..01e442bfa9
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/image1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/image2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/image2.gif
new file mode 100644
index 0000000000..751faeea36
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/image2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/image3.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/image3.gif
new file mode 100644
index 0000000000..4f30484ff6
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/image3.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/index.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/index.gif
new file mode 100644
index 0000000000..162478fb3a
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/index.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/layout.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/layout.gif
new file mode 100644
index 0000000000..c96338a152
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/layout.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/left.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/left.gif
new file mode 100644
index 0000000000..279e6710d4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/left.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/link.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/link.gif
new file mode 100644
index 0000000000..c5b6889a76
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/link.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/movie.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/movie.gif
new file mode 100644
index 0000000000..0035183774
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/movie.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/p.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/p.gif
new file mode 100644
index 0000000000..7b917b4e91
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/p.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/patch.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/patch.gif
new file mode 100644
index 0000000000..39bc90e795
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/patch.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pdf.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pdf.gif
new file mode 100644
index 0000000000..c88fd777c4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pdf.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie0.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie0.gif
new file mode 100644
index 0000000000..6f7a0ae7a7
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie0.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie1.gif
new file mode 100644
index 0000000000..03aa6be71e
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie2.gif
new file mode 100644
index 0000000000..b04c5e0908
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie3.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie3.gif
new file mode 100644
index 0000000000..4db9d023ed
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie3.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie4.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie4.gif
new file mode 100644
index 0000000000..93471fdd88
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie4.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie5.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie5.gif
new file mode 100644
index 0000000000..57aee93f07
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie5.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie6.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie6.gif
new file mode 100644
index 0000000000..0dc327b569
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie6.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie7.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie7.gif
new file mode 100644
index 0000000000..8661337f06
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie7.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/pie8.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie8.gif
new file mode 100644
index 0000000000..59ddb34ce0
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/pie8.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/portal.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/portal.gif
new file mode 100644
index 0000000000..0e6e506e00
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/portal.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/poweredby.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/poweredby.gif
new file mode 100644
index 0000000000..d324ab80ea
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/poweredby.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/ps.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/ps.gif
new file mode 100644
index 0000000000..0f565bc1db
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/ps.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/quill.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/quill.gif
new file mode 100644
index 0000000000..818a5cdc7e
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/quill.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/right.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/right.gif
new file mode 100644
index 0000000000..b256e5f75f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/right.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/screw1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/screw1.gif
new file mode 100644
index 0000000000..af6ba2b097
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/screw1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/screw2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/screw2.gif
new file mode 100644
index 0000000000..06dccb3e44
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/screw2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/script.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/script.gif
new file mode 100644
index 0000000000..d8a853bc58
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/script.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/sound1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/sound1.gif
new file mode 100644
index 0000000000..8efb49f55d
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/sound1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/sound2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/sound2.gif
new file mode 100644
index 0000000000..48e6a7fb2f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/sound2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/sphere1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/sphere1.gif
new file mode 100644
index 0000000000..7067070da2
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/sphere1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/sphere2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/sphere2.gif
new file mode 100644
index 0000000000..a9e462a377
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/sphere2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/star.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/star.gif
new file mode 100644
index 0000000000..4cfe0a5e0f
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/star.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/star_blank.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/star_blank.gif
new file mode 100644
index 0000000000..a0c83cb85b
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/star_blank.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/tar.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/tar.gif
new file mode 100644
index 0000000000..617e779efa
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/tar.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/tex.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/tex.gif
new file mode 100644
index 0000000000..45e43233b8
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/tex.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/text.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/text.gif
new file mode 100644
index 0000000000..4c623909fb
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/text.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/transfer.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/transfer.gif
new file mode 100644
index 0000000000..33697dbb66
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/transfer.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/unknown.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/unknown.gif
new file mode 100644
index 0000000000..32b1ea23fb
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/unknown.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/up.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/up.gif
new file mode 100644
index 0000000000..6d6d6d1ebf
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/up.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/uu.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/uu.gif
new file mode 100644
index 0000000000..4387d529f6
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/uu.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/uuencoded.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/uuencoded.gif
new file mode 100644
index 0000000000..4387d529f6
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/uuencoded.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/world1.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/world1.gif
new file mode 100644
index 0000000000..05b4ec2058
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/world1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/icons/world2.gif b/lib/inets/test/httpd_SUITE_data/server_root/icons/world2.gif
new file mode 100644
index 0000000000..e3203f7a88
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/icons/world2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/logs/Dummy_File_Needed_By_WinZip b/lib/inets/test/httpd_SUITE_data/server_root/logs/Dummy_File_Needed_By_WinZip
new file mode 100644
index 0000000000..8d1c8b69c3
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/logs/Dummy_File_Needed_By_WinZip
@@ -0,0 +1 @@
+
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_client.pem b/lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_client.pem
new file mode 100644
index 0000000000..8221139eb4
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_client.pem
@@ -0,0 +1,22 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAL6Ym/bgUvhhnPkw08sggGg8Tnp759ThGMEjkmDzhuJ3w3PfnF65
+mgHcgunku4G6LxAQfEUougJWf9Phmjj3oRUCAwEAAQJBAKMjvVvzZxFzfAlP4flc
+OI0AEayFokp04dtvtzuFN09f+aBo2dP18xHmKLCZvxrBOaRAROoQYscALiIVpN07
+GAECIQDfi+sSfAFaDlT3vzpL3xE5UEH6IzY8jWpaZfM1QaToJQIhANpEF50H4wGO
+8Sbh7dUutNd+s+NYUjsMySW2DjLKMsoxAiEAzzb2ftrdsempD0F+O0gZwiPIFKLB
+Kp33YLYyHEKuJtUCIDGi+pvDh2R7VWw6RRQOIyI+tjolg83aAoSI+oGiahqBAiEA
+xzmNNajwoaokvWvlaz0na8rhxu45grOvDrflBT9XvSQ=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICDDCCAbYCAQAwDQYJKoZIhvcNAQEEBQAwgZAxCzAJBgNVBAYTAlNFMRIwEAYD
+VQQIEwlTdG9ja2hvbG0xDzANBgNVBAcTBkFsdnNqbzEMMAoGA1UEChMDRVRYMQ4w
+DAYDVQQLEwVETi9TUDEXMBUGA1UEAxMOSm9ha2ltIEdyZWJlbm8xJTAjBgkqhkiG
+9w0BCQEWFmpvY2tlQGVyaXguZXJpY3Nzb24uc2UwHhcNOTcwNzE1MTUzNDM2WhcN
+MDMwMjIyMTUzNDM2WjCBkDELMAkGA1UEBhMCU0UxEjAQBgNVBAgTCVN0b2NraG9s
+bTEPMA0GA1UEBxMGQWx2c2pvMQwwCgYDVQQKEwNFVFgxDjAMBgNVBAsTBUROL1NQ
+MRcwFQYDVQQDEw5Kb2FraW0gR3JlYmVubzElMCMGCSqGSIb3DQEJARYWam9ja2VA
+ZXJpeC5lcmljc3Nvbi5zZTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+mJv24FL4
+YZz5MNPLIIBoPE56e+fU4RjBI5Jg84bid8Nz35xeuZoB3ILp5LuBui8QEHxFKLoC
+Vn/T4Zo496EVAgMBAAEwDQYJKoZIhvcNAQEEBQADQQBYxQVfTydyZCE0UXvZd7Ei
+josNsAaWJk9fFIJaG9uyXCEfg2dVgoT2eBk3D9DI+7OB+78isM5CVlFbL7hilvP8
+-----END CERTIFICATE-----
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_server.pem b/lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_server.pem
new file mode 100644
index 0000000000..fe739c15f7
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/ssl/ssl_server.pem
@@ -0,0 +1,22 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBAL9Bozj3BIjL5Cy8b3rjMT2kPZRychX4wz9bHoIIiKnKo1xXHYjw
+g3N9zWM1f1ZzMADwVry1uAInA8q09+7hL20CAwEAAQJACwu2ao7RozjrV64WXimK
+6X131P/7GMvCMwGHNIlbozqoOqmZcYrbKaF61l+XuwA2QvTo3ywW1Ivxcyr6TeAr
+PQIhAOX+WXT6yiqqwjt08kjBCJyMgfZtdAO6pc/6pKjNWiZfAiEA1OH1iPW/OQe5
+tlQXpiRVdLyneNsPygPRJc4Bdwu3hbMCIQDbI5pA56QxOzqOREOGJsb5wrciAfAE
+jZbnr72sSN2YqQIgAWFpvzagw9Tp/mWzNY+cwkIK7/yzsIKv04fveH8p9IMCIQCr
+td4IiukeUwXmPSvYM4uCE/+J89wEL9qU8Mlc3gDLXA==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICDDCCAbYCAQAwDQYJKoZIhvcNAQEEBQAwgZAxCzAJBgNVBAYTAlNFMRIwEAYD
+VQQIEwlTdG9ja2hvbG0xDzANBgNVBAcTBkFsdnNqbzEMMAoGA1UEChMDRVRYMQ4w
+DAYDVQQLEwVETi9TUDEXMBUGA1UEAxMOSm9ha2ltIEdyZWJlbm8xJTAjBgkqhkiG
+9w0BCQEWFmpvY2tlQGVyaXguZXJpY3Nzb24uc2UwHhcNOTcwNzE1MTUzMzQxWhcN
+MDMwMjIyMTUzMzQxWjCBkDELMAkGA1UEBhMCU0UxEjAQBgNVBAgTCVN0b2NraG9s
+bTEPMA0GA1UEBxMGQWx2c2pvMQwwCgYDVQQKEwNFVFgxDjAMBgNVBAsTBUROL1NQ
+MRcwFQYDVQQDEw5Kb2FraW0gR3JlYmVubzElMCMGCSqGSIb3DQEJARYWam9ja2VA
+ZXJpeC5lcmljc3Nvbi5zZTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC/QaM49wSI
+y+QsvG964zE9pD2UcnIV+MM/Wx6CCIipyqNcVx2I8INzfc1jNX9WczAA8Fa8tbgC
+JwPKtPfu4S9tAgMBAAEwDQYJKoZIhvcNAQEEBQADQQAmXDY1CyJjzvQZX442kkHG
+ic9QFY1UuVfzokzNMwlHYl1Qx9zaodx0cJCrcH5GF9O9LJbhhV77LzoxT1Q5wZp5
+-----END CERTIFICATE-----
diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl
new file mode 100644
index 0000000000..f86c1fcb49
--- /dev/null
+++ b/lib/inets/test/httpd_basic_SUITE.erl
@@ -0,0 +1,136 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(httpd_basic_SUITE).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+all(doc) ->
+ ["Basic test of httpd."];
+
+all(suite) ->
+ [
+ uri_too_long_414,
+ header_too_long_413
+ ].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ ok = inets:start(),
+ PrivDir = ?config(priv_dir, Config),
+ HttpdConf = [{port, 0}, {ipfamily, inet},
+ {server_name, "httpd_test"}, {server_root, PrivDir},
+ {document_root, PrivDir}, {bind_address, "localhost"}],
+ [{httpd_conf, HttpdConf} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ inets:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_Case, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_, Config) ->
+ Config.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+uri_too_long_414(doc) ->
+ ["Test that too long uri's get 414 HTTP code"];
+uri_too_long_414(suite) ->
+ [];
+uri_too_long_414(Config) when is_list(Config) ->
+ HttpdConf = ?config(httpd_conf, Config),
+ {ok, Pid} = inets:start(httpd, [{port, 0}, {max_uri_size, 10}
+ | HttpdConf]),
+ Info = httpd:info(Pid),
+ Port = proplists:get_value(port, Info),
+ Address = proplists:get_value(bind_address, Info),
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ "GET /morethantenchars "
+ "HTTP/1.1\r\n\r\n",
+ [{statuscode, 414},
+ %% Server will send lowest version
+ %% as it will not get to the
+ %% client version
+ %% before aborting
+ {version, "HTTP/0.9"}]),
+ inets:stop(httpd, Pid).
+
+header_too_long_413(doc) ->
+ ["Test that too long headers's get 413 HTTP code"];
+header_too_long_413(suite) ->
+ [];
+header_too_long_413(Config) when is_list(Config) ->
+ HttpdConf = ?config(httpd_conf, Config),
+ {ok, Pid} = inets:start(httpd, [{port, 0}, {max_header_size, 10}
+ | HttpdConf]),
+ Info = httpd:info(Pid),
+ Port = proplists:get_value(port, Info),
+ Address = proplists:get_value(bind_address, Info),
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ "GET index.html "
+ "HTTP/1.1\r\n"
+ "Connection:close \r\n\r\n ",
+ [{statuscode, 413},
+ {version, "HTTP/1.1"}]),
+ inets:stop(httpd, Pid).
+
+
+
+
diff --git a/lib/inets/test/httpd_block.erl b/lib/inets/test/httpd_block.erl
new file mode 100644
index 0000000000..f967d8172a
--- /dev/null
+++ b/lib/inets/test/httpd_block.erl
@@ -0,0 +1,299 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(httpd_block).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% General testcases bodies called from httpd_SUITE
+-export([block_disturbing_idle/4, block_non_disturbing_idle/4,
+ block_503/4, block_disturbing_active/4,
+ block_non_disturbing_active/4,
+ block_disturbing_active_timeout_not_released/4,
+ block_disturbing_active_timeout_released/4,
+ block_non_disturbing_active_timeout_not_released/4,
+ block_non_disturbing_active_timeout_released/4,
+ disturbing_blocker_dies/4,
+ non_disturbing_blocker_dies/4, restart_no_block/4,
+ restart_disturbing_block/4, restart_non_disturbing_block/4
+ ]).
+
+%% Help functions
+-export([do_block_server/4, do_block_nd_server/5, do_long_poll/6]).
+
+-define(report(Label, Content),
+ inets:report_event(20, Label, test_case,
+ [{module, ?MODULE}, {line, ?LINE} | Content])).
+
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+block_disturbing_idle(_Type, Port, Host, Node) ->
+ unblocked = get_admin_state(Node, Host, Port),
+ block_server(Node, Host, Port),
+ blocked = get_admin_state(Node, Host, Port),
+ unblock_server(Node, Host, Port),
+ unblocked = get_admin_state(Node, Host, Port).
+%%--------------------------------------------------------------------
+block_non_disturbing_idle(_Type, Port, Host, Node) ->
+ unblocked = get_admin_state(Node, Host, Port),
+ block_nd_server(Node, Host, Port),
+ blocked = get_admin_state(Node, Host, Port),
+ unblock_server(Node, Host, Port),
+ unblocked = get_admin_state(Node, Host, Port).
+%%--------------------------------------------------------------------
+block_503(Type, Port, Host, Node) ->
+ Req = "GET / HTTP/1.0\r\ndummy-host.ericsson.se:\r\n\r\n",
+ unblocked = get_admin_state(Node, Host, Port),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req,
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = block_server(Node, Host, Port),
+ blocked = get_admin_state(Node, Host, Port),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req,
+ [{statuscode, 503},
+ {version, "HTTP/1.0"}]),
+ ok = unblock_server(Node, Host, Port),
+ unblocked = get_admin_state(Node, Host, Port),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req,
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]).
+%%--------------------------------------------------------------------
+block_disturbing_active(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Pid = long_poll(Type, Host, Port, Node, 200, 60000),
+ test_server:sleep(15000),
+ block_server(Node, Host, Port),
+ await_suite_failed_process_exit(Pid, "poller", 60000,
+ connection_closed),
+ blocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+%%--------------------------------------------------------------------
+block_non_disturbing_active(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 60000),
+ test_server:sleep(15000),
+ ok = block_nd_server(Node, Host, Port),
+ await_normal_process_exit(Poller, "poller", 60000),
+ blocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+block_disturbing_active_timeout_not_released(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 60000),
+ test_server:sleep(15000),
+ Blocker = blocker(Node, Host, Port, 50000),
+ await_normal_process_exit(Blocker, "blocker", 50000),
+ await_normal_process_exit(Poller, "poller", 30000),
+ blocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+block_disturbing_active_timeout_released(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 40000),
+ test_server:sleep(5000),
+ Blocker = blocker(Node, Host, Port, 10000),
+ await_normal_process_exit(Blocker, "blocker", 15000),
+ await_suite_failed_process_exit(Poller, "poller", 40000,
+ connection_closed),
+ blocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+%%--------------------------------------------------------------------
+block_non_disturbing_active_timeout_not_released(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 60000),
+ test_server:sleep(5000),
+ ok = block_nd_server(Node, Host, Port, 40000),
+ await_normal_process_exit(Poller, "poller", 60000),
+ blocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+block_non_disturbing_active_timeout_released(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 45000),
+ test_server:sleep(5000),
+ Blocker = blocker_nd(Node, Host, Port ,10000, {error,timeout}),
+ await_normal_process_exit(Blocker, "blocker", 15000),
+ await_normal_process_exit(Poller, "poller", 50000),
+ unblocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+%%--------------------------------------------------------------------
+disturbing_blocker_dies(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 60000),
+ test_server:sleep(5000),
+ Blocker = blocker(Node, Host, Port, 10000),
+ test_server:sleep(5000),
+ exit(Blocker,simulate_blocker_crash),
+ await_normal_process_exit(Poller, "poller", 60000),
+ unblocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+non_disturbing_blocker_dies(Type, Port, Host, Node) ->
+ process_flag(trap_exit, true),
+ Poller = long_poll(Type, Host, Port, Node, 200, 60000),
+ test_server:sleep(5000),
+ Blocker = blocker_nd(Node, Host, Port, 10000, ok),
+ test_server:sleep(5000),
+ exit(Blocker, simulate_blocker_crash),
+ await_normal_process_exit(Poller, "poller", 60000),
+ unblocked = get_admin_state(Node, Host, Port),
+ process_flag(trap_exit, false),
+ ok.
+%%--------------------------------------------------------------------
+restart_no_block(_, Port, Host, Node) ->
+ {error,_Reason} = restart_server(Node, Host, Port).
+
+%%--------------------------------------------------------------------
+restart_disturbing_block(_, Port, Host, Node) ->
+ ?report("restart_disturbing_block - get_admin_state (unblocked)", []),
+ unblocked = get_admin_state(Node, Host, Port),
+ ?report("restart_disturbing_block - block_server", []),
+ ok = block_server(Node, Host, Port),
+ ?report("restart_disturbing_block - restart_server", []),
+ ok = restart_server(Node, Host, Port),
+ ?report("restart_disturbing_block - unblock_server", []),
+ ok = unblock_server(Node, Host, Port),
+ ?report("restart_disturbing_block - get_admin_state (unblocked)", []),
+ unblocked = get_admin_state(Node, Host, Port).
+
+%%--------------------------------------------------------------------
+restart_non_disturbing_block(_, Port, Host, Node) ->
+ ?report("restart_non_disturbing_block - get_admin_state (unblocked)", []),
+ unblocked = get_admin_state(Node, Host, Port),
+ ?report("restart_non_disturbing_block - block_nd_server", []),
+ ok = block_nd_server(Node, Host, Port),
+ ?report("restart_non_disturbing_block - restart_server", []),
+ ok = restart_server(Node, Host, Port),
+ ?report("restart_non_disturbing_block - unblock_server", []),
+ ok = unblock_server(Node, Host, Port),
+ ?report("restart_non_disturbing_block - get_admin_state (unblocked)", []),
+ unblocked = get_admin_state(Node, Host, Port).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+blocker(Node, Host, Port, Timeout) ->
+ spawn_link(?MODULE, do_block_server,[Node, Host, Port,Timeout]).
+
+do_block_server(Node, Host, Port, Timeout) ->
+ ok = block_server(Node, Host, Port, Timeout),
+ exit(normal).
+
+blocker_nd(Node, Host, Port, Timeout, Reply) ->
+ spawn_link(?MODULE, do_block_nd_server,
+ [Node, Host, Port, Timeout, Reply]).
+
+do_block_nd_server(Node, Host, Port, Timeout, Reply) ->
+ Reply = block_nd_server(Node, Host, Port, Timeout),
+ exit(normal).
+
+restart_server(Node, _Host, Port) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, restart, [Addr, Port]).
+
+block_server(Node, _Host, Port) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, block, [Addr, Port]).
+
+block_server(Node, _Host, Port, Timeout) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, block, [Addr, Port, disturbing, Timeout]).
+
+block_nd_server(Node, _Host, Port) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, block, [Addr, Port, non_disturbing]).
+
+block_nd_server(Node, _Host, Port, Timeout) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, block, [Addr, Port, non_disturbing, Timeout]).
+
+unblock_server(Node, _Host, Port) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, unblock, [Addr, Port]).
+
+get_admin_state(Node,_Host,Port) ->
+ Addr = undefined,
+ rpc:call(Node, httpd, get_admin_state, [Addr, Port]).
+
+await_normal_process_exit(Pid, Name, Timeout) ->
+ receive
+ {'EXIT', Pid, normal} ->
+ ok;
+ {'EXIT', Pid, Reason} ->
+ Err =
+ lists:flatten(
+ io_lib:format("expected normal exit, "
+ "unexpected exit of ~s process: ~p",
+ [Name, Reason])),
+ test_server:fail(Err)
+ after Timeout ->
+ test_server:fail("timeout while waiting for " ++ Name)
+ end.
+
+await_suite_failed_process_exit(Pid, Name, Timeout, Why) ->
+ receive
+ {'EXIT', Pid, {suite_failed, Why}} ->
+ ok;
+ {'EXIT', Pid, Reason} ->
+ Err =
+ lists:flatten(
+ io_lib:format("expected connection_closed, "
+ "unexpected exit of ~s process: ~p",
+ [Name, Reason])),
+ test_server:fail(Err)
+ after Timeout ->
+ test_server:fail("timeout while waiting for " ++ Name)
+ end.
+
+long_poll(Type, Host, Port, Node, StatusCode, Timeout) ->
+ spawn_link(?MODULE, do_long_poll, [Type, Host, Port, Node,
+ StatusCode, Timeout]).
+
+do_long_poll(Type, Host, Port, Node, StatusCode, Timeout) ->
+ Mod = "httpd_example",
+ Func = "delay",
+ Req = lists:flatten(io_lib:format("GET /eval?" ++ Mod ++ ":" ++ Func ++
+ "(~p) HTTP/1.0\r\n\r\n",[30000])),
+ case httpd_test_lib:verify_request(Type, Host, Port, Node, Req,
+ [{statuscode, StatusCode},
+ {version, "HTTP/1.0"}], Timeout) of
+ ok ->
+ exit(normal);
+ Reason ->
+ test_server:fail(Reason)
+ end.
+
+
+
+
+
diff --git a/lib/inets/test/httpd_load.erl b/lib/inets/test/httpd_load.erl
new file mode 100644
index 0000000000..9bb9f9f94e
--- /dev/null
+++ b/lib/inets/test/httpd_load.erl
@@ -0,0 +1,99 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(httpd_load).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% General testcases bodies called from httpd_SUITE
+-export([load_test/5]).
+
+%% Help functions
+-export([load_test_client/8]).
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+load_test(Type, Port, Host, Node, NofTesters) ->
+ URIs =
+ [
+ "/index.html",
+ "/echo.shtml",
+ "/",
+ "/flastmod.shtml",
+ "/misc/"
+ ],
+ Fun = fun(Mod, Host1, Port1, Node1, Req, Exp) ->
+ ok = httpd_test_lib:verify_request(Mod, Host1, Port1,
+ Node1, Req, Exp)
+ end,
+ load_test(Fun, URIs ++ URIs, Type, Host, Port, Node, NofTesters, []).
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+
+load_test(_, _, _, _, _, _, 0, []) ->
+ ok;
+load_test(Fun, URIs, Type, Host, Port, Node, 0, List) ->
+ receive
+ {Pid, done} ->
+ load_test(Fun, URIs, Type, Host, Port, Node, 0,
+ lists:delete(Pid, List));
+ {'EXIT', Pid, normal} ->
+ load_test(Fun, URIs, Type, Host, Port, Node, 0,
+ lists:delete(Pid, List));
+ {'EXIT', Pid, Reason} ->
+ Str = lists:flatten(io_lib:format("client ~p exited: ~p",
+ [Pid,Reason])),
+ test_server:fail(Str);
+ _ ->
+ load_test(Fun, URIs, Type, Host, Port, Node, 0, List)
+ end;
+
+load_test(Fun, URIs, Type, Host, Port, Node, X, List) ->
+ Pid = spawn_link(?MODULE, load_test_client,
+ [Fun, URIs, Type, Host, Port, Node, self(), 100]),
+ load_test(Fun, lists:reverse(URIs), Type, Host, Port, Node, X-1,
+ [Pid | List]).
+
+load_test_client(_Fun, [], _Type, _Host, _Port, _Node, Boss, _Timeout) ->
+ load_test_client_done(Boss);
+load_test_client(Fun, [URI|URIs], Type, Host, Port, Node, Boss, Timeout) ->
+ Req = "GET "++URI++" HTTP/1.0\r\nConnection: Close\r\n"
+ "From: m@erix\r\nReferer: http://www.ericsson.se/\r\n\r\n",
+ Timeout1 =
+ case (catch Fun(Type, Host, Port, Node, Req,
+ [{statuscode, 200}, {statuscode, 500},
+ {statuscode, 503}, {version, "HTTP/1.0"}])) of
+ {'EXIT', {suite_failed, connection_closed, _, _}} ->
+ %% Some platforms seems to handle heavy load badly.
+ %% So, back off and see if this helps
+ %%?LOG("load_test_client->requestfailed:connection_closed"[]),
+ 2 * Timeout;
+ _ ->
+ Timeout
+ end,
+ test_server:sleep(Timeout1),
+ load_test_client(Fun, URIs, Type, Host, Port, Node, Boss, Timeout1).
+
+load_test_client_done(Boss) ->
+ Boss ! {self(), done}.
+
diff --git a/lib/inets/test/httpd_mod.erl b/lib/inets/test/httpd_mod.erl
new file mode 100644
index 0000000000..b03f842e7c
--- /dev/null
+++ b/lib/inets/test/httpd_mod.erl
@@ -0,0 +1,947 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(httpd_mod).
+-author('[email protected]').
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% General testcases bodies called from httpd_SUITE
+-export([alias/4, actions/4, security/5, auth/4, auth_api/6,
+ auth_mnesia_api/4, htaccess/4,
+ cgi/4, esi/4, get/4, head/4, all/4]).
+
+%% Help functions
+-export([event/4, ssl_password_cb/0]).
+
+%% Seconds before successful auths timeout.
+-define(AUTH_TIMEOUT,5).
+
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+alias(Type, Port, Host, Node) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /pics/icon.sheet.gif "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Content-Type","image/gif"},
+ {header, "Server"},
+ {header, "Date"},
+ {version, "HTTP/1.0"}]),
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Content-Type","text/html"},
+ {header, "Server"},
+ {header, "Date"},
+ {version, "HTTP/1.0"}]),
+
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET /misc/ HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Content-Type","text/html"},
+ {header, "Server"},
+ {header, "Date"},
+ {version, "HTTP/1.0"}]),
+
+ %% Check redirection if trailing slash is missing.
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET /misc HTTP/1.0\r\n\r\n",
+ [{statuscode, 301},
+ {header, "Location"},
+ {header, "Content-Type","text/html"},
+ {version, "HTTP/1.0"}]).
+
+%%-------------------------------------------------------------------------
+actions(Type, Port, Host, Node) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD / HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]).
+
+%%-------------------------------------------------------------------------
+security(ServerRoot, Type, Port, Host, Node) ->
+ io:format(user, "~w:security -> entry with"
+ "~n ServerRoot: ~p"
+ "~n Type: ~p"
+ "~n Port: ~p"
+ "~n Host: ~p"
+ "~n Node: ~p"
+ "~n", [?MODULE, ServerRoot, Type, Port, Host, Node]),
+
+ global:register_name(mod_security_test, self()), % Receive events
+
+ test_server:sleep(5000),
+
+ OpenDir = filename:join([ServerRoot, "htdocs", "open"]),
+
+ %% Test blocking / unblocking of users.
+
+ %% /open, require user one Aladdin
+ remove_users(Node, ServerRoot, Host, Port, "open"),
+
+ auth_request(Type, Host, Port, Node, "/open/", "one", "onePassword",
+ [{statuscode, 401}]),
+ receive_security_event({event, auth_fail, Port, OpenDir,
+ [{user, "one"}, {password, "onePassword"}]},
+ Node, Port),
+
+ auth_request(Type,Host,Port,Node,"/open/", "two", "twoPassword",
+ [{statuscode, 401}]),
+ receive_security_event({event, auth_fail, Port, OpenDir,
+ [{user, "two"}, {password, "twoPassword"}]},
+ Node, Port),
+
+ auth_request(Type, Host, Port, Node,"/open/", "Aladdin",
+ "AladdinPassword", [{statuscode, 401}]),
+ receive_security_event({event, auth_fail, Port, OpenDir,
+ [{user, "Aladdin"},
+ {password, "AladdinPassword"}]},
+ Node, Port),
+
+ add_user(Node, ServerRoot, Port, "open", "one", "onePassword", []),
+ add_user(Node, ServerRoot, Port, "open", "two", "twoPassword", []),
+
+ auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword",
+ [{statuscode, 401}]),
+ receive_security_event({event, auth_fail, Port, OpenDir,
+ [{user, "one"}, {password, "WrongPassword"}]},
+ Node, Port),
+
+ auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword",
+ [{statuscode, 401}]),
+ receive_security_event({event, auth_fail, Port, OpenDir,
+ [{user, "one"}, {password, "WrongPassword"}]},
+ Node, Port),
+
+ receive_security_event({event, user_block, Port, OpenDir,
+ [{user, "one"}]}, Node, Port),
+
+ global:unregister_name(mod_security_test), % No more events.
+
+ auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword",
+ [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword",
+ [{statuscode, 403}]),
+
+ %% User "one" should be blocked now..
+ %% [{"one",_, Port, OpenDir,_}] = list_blocked_users(Node,Port),
+ case list_blocked_users(Node, Port) of
+ [{"one",_, Port, OpenDir,_}] ->
+ ok;
+ Blocked ->
+ io:format(user, "~w:security -> Blocked: ~p"
+ "~n", [?MODULE, Blocked]),
+ exit({unexpected_blocked, Blocked})
+ end,
+
+ [{"one",_, Port, OpenDir,_}] = list_blocked_users(Node,Port,OpenDir),
+
+ true = unblock_user(Node, "one", Port, OpenDir),
+ %% User "one" should not be blocked any more..
+ [] = list_blocked_users(Node, Port),
+ [] = list_blocked_users(Node, Port, OpenDir),
+ auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword",
+ [{statuscode, 200}]),
+
+ %% Test list_auth_users & auth_timeout
+ ["one"] = list_auth_users(Node, Port),
+ ["one"] = list_auth_users(Node, Port, OpenDir),
+ auth_request(Type, Host, Port, Node,"/open/", "two", "onePassword",
+ [{statuscode, 401}]),
+ ["one"] = list_auth_users(Node, Port),
+ ["one"] = list_auth_users(Node, Port, OpenDir),
+ auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword",
+ [{statuscode, 401}]),
+ ["one"] = list_auth_users(Node, Port),
+ ["one"] = list_auth_users(Node, Port, OpenDir),
+ %% Wait for successful auth to timeout.
+ test_server:sleep(?AUTH_TIMEOUT*1001),
+ [] = list_auth_users(Node, Port),
+ [] = list_auth_users(Node, Port, OpenDir),
+ %% "two" is blocked.
+ true = unblock_user(Node, "two", Port, OpenDir),
+ %% Test explicit blocking. Block user 'two'.
+ [] = list_blocked_users(Node,Port,OpenDir),
+ true = block_user(Node, "two", Port, OpenDir, 10),
+ auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword",
+ [{statuscode, 401}]).
+
+%%-------------------------------------------------------------------------
+auth(Type, Port, Host, Node) ->
+ %% Authentication required!
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET /open/ HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET /secret/ HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
+ "GET /secret/top_secret/"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+
+ %% Authentication OK! ["one:OnePassword" user first in user list]
+ auth_request(Type, Host, Port, Node, "/open/dummy.html", "one",
+ "onePassword", [{statuscode, 200}]),
+ %% Authentication OK and a directory listing is supplied!
+ %% ["Aladdin:open sesame" user second in user list]
+ auth_request(Type, Host, Port, Node, "/open/","Aladdin",
+ "AladdinPassword", [{statuscode, 200}]),
+
+ %% User correct but wrong password! ["one:one" user first in user list]
+ auth_request(Type, Host, Port, Node, "/open/", "one", "one",
+ [{statuscode, 401},{header, "WWW-Authenticate"}]),
+ %% Make sure Authenticate header is received even the second time
+ %% we try a incorrect password! Otherwise a browser client will hang!
+ auth_request(Type, Host, Port, Node, "/open/", "one", "one",
+ [{statuscode, 401},{header, "WWW-Authenticate"}]),
+
+ %% Neither user or password correct! ["dummy:dummy"]
+ auth_request(Type, Host, Port, Node, "/open/", "dummy", "dummy",
+ [{statuscode, 401}]),
+
+ %% Authentication OK! ["two:TwoPassword" user in first group]
+ auth_request(Type, Host, Port, Node, "/secret/dummy.html", "two",
+ "twoPassword", [{statuscode, 200}]),
+ %% Authentication OK and a directory listing is supplied!
+ %% ["three:ThreePassword" user in second group]
+ auth_request(Type, Host, Port, Node,"/secret/", "three",
+ "threePassword", [{statuscode, 200}]),
+
+ %% User correct but wrong password! ["two:two" user in first group]
+ auth_request(Type, Host, Port, Node, "/secret/", "two", "two",
+ [{statuscode, 401}]),
+ %% Neither user or password correct! ["dummy:dummy"]
+ auth_request(Type, Host, Port, Node,"/secret/", "dummy", "dummy",
+ [{statuscode, 401}]),
+
+ %% Nested secret/top_secret OK! ["Aladdin:open sesame"]
+ auth_request(Type, Host, Port, Node, "/secret/top_secret/", "Aladdin",
+ "AladdinPassword", [{statuscode, 200}]),
+ %% Authentication still required!
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /open/ "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /secret/ "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /secret/top_secret/ "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]).
+
+
+%%-------------------------------------------------------------------------
+%% What to test here:
+%%
+%% /open - plain, require user one Aladdin
+%% /secret - plain, require group group1 group2
+%% /secret/top_secret - plain, require group group3
+%% /dets_open - dets, require user one Aladdin
+%% /dets_secret - dets, require group group1 group2
+%% /dets_secret/top_secret - dets, require group group3
+%% /mnesia_open/ - mnesia, require user one Aladdin
+%% /mnesia_secret/ - mnesia, require group group1 group2
+%% /mnesia_secret/top_secret/ - mnesia, require group group3
+auth_api(ServerRoot, AuthStoreType, Type, Port, Host, Node) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET / HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ auth_request(Type, Host, Port, Node, "/", "one", "WrongPassword",
+ [{statuscode, 200}]),
+
+ %% Make sure Authenticate header is received even the second time
+ %% we try a incorrect password! Otherwise a browser client will hang!
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "dummy", "WrongPassword", [{statuscode, 401},
+ {header, "WWW-Authenticate"}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "dummy", "WrongPassword", [{statuscode, 401},
+ {header, "WWW-Authenticate"}]),
+
+ %% Change the password to DummyPassword then try to add a user
+ %% Get an error and set it to NoPassword
+ ok = update_password(Node, ServerRoot, Host, Port, AuthStoreType ++
+ "open", "NoPassword", "DummyPassword"),
+ {error,bad_password} =
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "one",
+ "onePassword", []),
+ ok = update_password(Node, ServerRoot, Host, Port, AuthStoreType ++"open",
+ "DummyPassword", "NoPassword"),
+
+ %% Test /*open, require user one Aladdin
+ remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++ "open"),
+
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "one", "onePassword", [{statuscode, 401}]),
+
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "two", "twoPassword", [{statuscode, 401}]),
+
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "Aladdin", "onePassword", [{statuscode, 401}]),
+
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "one",
+ "onePassword", []),
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "two",
+ "twoPassword", []),
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "Aladdin",
+ "AladdinPassword", []),
+
+ {ok, [_|_]} = list_users(Node, ServerRoot, Host, Port,
+ AuthStoreType++"open"),
+ auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "open/",
+ "one", "WrongPassword", [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "open/",
+ "one", "onePassword", [{statuscode, 200}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "two", "twoPassword", [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "open/",
+ "Aladdin", "WrongPassword", [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
+ "Aladdin", "AladdinPassword", [{statuscode, 200}]),
+
+ remove_users(Node, ServerRoot, Host, Port, AuthStoreType++"open"),
+ {ok, []} = list_users(Node, ServerRoot, Host, Port,
+ AuthStoreType++"open"),
+
+ %% Phase 2
+ remove_users(Node, ServerRoot, Host, Port, AuthStoreType++"secret"),
+ {ok, []} = list_users(Node, ServerRoot, Host, Port, AuthStoreType ++
+ "secret"),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
+ "one", "onePassword", [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
+ "two", "twoPassword", [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "secret/",
+ "three", "threePassword", [{statuscode, 401}]),
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "secret", "one",
+ "onePassword",
+ []),
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "secret",
+ "two", "twoPassword", []),
+ add_user(Node, ServerRoot, Port, AuthStoreType++"secret", "Aladdin",
+ "AladdinPassword",[]),
+ add_group_member(Node, ServerRoot, Port, AuthStoreType ++ "secret",
+ "one", "group1"),
+ add_group_member(Node, ServerRoot, Port, AuthStoreType ++ "secret",
+ "two", "group1"),
+ add_group_member(Node, ServerRoot, Port, AuthStoreType ++
+ "secret", "Aladdin", "group2"),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
+ "one", "onePassword", [{statuscode, 200}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
+ "two", "twoPassword", [{statuscode, 200}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
+ "Aladdin", "AladdinPassword", [{statuscode, 200}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
+ "three", "threePassword", [{statuscode, 401}]),
+ remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++ "secret"),
+ {ok, []} = list_users(Node, ServerRoot, Host, Port,
+ AuthStoreType ++ "secret"),
+ remove_groups(Node, ServerRoot, Host, Port, AuthStoreType ++ "secret"),
+ Directory = filename:join([ServerRoot, "htdocs", AuthStoreType ++
+ "secret"]),
+ {ok, []} = list_groups(Node, ServerRoot, Host, Port, Directory),
+
+ %% Phase 3
+ remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++
+ "secret/top_secret"),
+ remove_groups(Node, ServerRoot, Host, Port, AuthStoreType ++
+ "secret/top_secret"),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++
+ "secret/top_secret/",
+ "three", "threePassword", [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++
+ "secret/top_secret/", "two", "twoPassword",
+ [{statuscode, 401}]),
+ add_user(Node, ServerRoot, Port, AuthStoreType ++
+ "secret/top_secret","three",
+ "threePassword",[]),
+ add_user(Node, ServerRoot, Port, AuthStoreType ++ "secret/top_secret",
+ "two","twoPassword", []),
+ add_group_member(Node, ServerRoot, Port, AuthStoreType ++
+ "secret/top_secret",
+ "three", "group3"),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++
+ "secret/top_secret/", "three", "threePassword",
+ [{statuscode, 200}]),
+ auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++
+ "secret/top_secret/", "two", "twoPassword",
+ [{statuscode, 401}]),
+ add_group_member(Node, ServerRoot, Port, AuthStoreType ++
+ "secret/top_secret",
+ "two", "group3"),
+ auth_request(Type,Host,Port,Node,"/" ++ AuthStoreType ++
+ "secret/top_secret/",
+ "two", "twoPassword", [{statuscode, 200}]),
+ remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++
+ "secret/top_secret"),
+ {ok, []} = list_users(Node, ServerRoot, Host, Port,
+ AuthStoreType ++ "secret/top_secret"),
+ remove_groups(Node, ServerRoot, Host, Port, AuthStoreType ++
+ "secret/top_secret"),
+ Directory2 = filename:join([ServerRoot, "htdocs",
+ AuthStoreType ++ "secret/top_secret"]),
+ {ok, []} = list_groups(Node, ServerRoot, Host, Port, Directory2),
+ auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++
+ "secret/top_secret/", "two", "twoPassword",
+ [{statuscode, 401}]),
+ auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++
+ "secret/top_secret/","three", "threePassword",
+ [{statuscode, 401}]).
+
+%%--------------------------------------------------------------------------
+auth_mnesia_api(_Type, Port, _Host, _Node) ->
+ %% Create three groups:
+ %% group1 : one Aladdin
+ %% group2 : two
+ %% group3 : three
+ mod_auth_mnesia:store_user("one", "onePassword", Port,
+ "/mnesia_open", ""),
+ mod_auth_mnesia:store_user("Aladdin", "AladdinPassword", Port,
+ "/mnesia_open", ""),
+ mod_auth_mnesia:store_user("two", "twoPassword", Port,
+ "/mnesia_open", ""),
+ mod_auth_mnesia:store_user("three", "threePassword", Port,
+ "/mnesia_open", ""),
+ Users = mod_auth_mnesia:list_users(Port, "/mnesia_open"),
+
+ ok = check_lists_members(Users,["Aladdin","one","two","three"]),
+
+ true = mod_auth_mnesia:store_group_member("group1", "one", Port,
+ "/mnesia_open", ""),
+ true = mod_auth_mnesia:store_group_member("group1","Aladdin", Port,
+ "/mnesia_open", ""),
+ true = mod_auth_mnesia:store_group_member("group2","two", Port,
+ "/mnesia_open", ""),
+ true = mod_auth_mnesia:store_group_member("group3","three", Port,
+ "/mnesia_open", ""),
+ %% Check that all three created groups exist.
+ Groups = mod_auth_mnesia:list_groups(Port, "/mnesia_open"),
+ ok = check_lists_members(Groups, ["group1","group2","group3"]),
+
+ %% Check that the members of all groups are correct.
+ Group1 = mod_auth_mnesia:list_group_members("group1", Port,
+ "/mnesia_open"),
+ ok = check_lists_members(Group1,["one","Aladdin"]),
+ {ok,["two"]} = mod_auth_mnesia:list_group_members("group2", Port,
+ "/mnesia_open"),
+
+ {ok,["three"]} = mod_auth_mnesia:list_group_members("group3", Port,
+ "/mnesia_open"),
+
+ %% Delete user 'one' from group one and check that he was removed
+ %% correctly.
+ true = mod_auth_mnesia:remove_group_member("group1", "one", Port,
+ "/mnesia_open", ""),
+ {ok,["Aladdin"]} = mod_auth_mnesia:list_group_members("group1", Port,
+ "/mnesia_open"),
+
+ %% Remove group1 and check that the group was removed correctly.
+ true = mod_auth_mnesia:remove_group("group1", Port, "/mnesia_open", ""),
+ Groups_1 = mod_auth_mnesia:list_groups(Port, "/mnesia_open"),
+ ok = check_lists_members(Groups_1,["group2","group3"]),
+
+ %% Check that the other users still exist in their groups.
+ Users_1 = mod_auth_mnesia:list_users(Port, "/mnesia_open"),
+ ok = check_lists_members(Users_1,["Aladdin","one","two","three"]),
+ {ok,["two"]} = mod_auth_mnesia:list_group_members("group2", Port,
+ "/mnesia_open"),
+ {ok,["three"]} = mod_auth_mnesia:list_group_members("group3", Port,
+ "/mnesia_open"),
+
+ %% Remove the remaining groups/users and check that all
+ %% users/groups are removed.
+ true = mod_auth_mnesia:remove_group("group2", Port, "/mnesia_open", ""),
+ true = mod_auth_mnesia:remove_group("group3", Port, "/mnesia_open", ""),
+ {ok, []} = mod_auth_mnesia:list_groups(Port, "/mnesia_open"),
+ true = mod_auth_mnesia:remove_user("one", Port, "/mnesia_open", ""),
+ true = mod_auth_mnesia:remove_user("Aladdin", Port, "/mnesia_open", ""),
+ true = mod_auth_mnesia:remove_user("two", Port, "/mnesia_open", ""),
+ true = mod_auth_mnesia:remove_user("three", Port, "/mnesia_open", ""),
+ {ok, []} = mod_auth_mnesia:list_users(Port, "/mnesia_open"),
+ ok.
+%%--------------------------------------------------------------------------
+htaccess(Type, Port, Host, Node) ->
+ %% Control that authentication required!
+ %% Control that the pages that shall be
+ %% authenticated really need authenticatin
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /ht/open/ HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /ht/secret/ HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /ht/secret/top_secret/ "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+
+ %% Make sure Authenticate header is received even the second time
+ %% we try a incorrect password! Otherwise a browser client will hang!
+ auth_request(Type, Host, Port, Node,"/ht/open/",
+ "dummy", "WrongPassword", [{statuscode, 401},
+ {header, "WWW-Authenticate"}]),
+ auth_request(Type, Host, Port, Node,"/ht/open/",
+ "dummy", "WrongPassword", [{statuscode, 401},
+ {header, "WWW-Authenticate"}]),
+
+ %% Control that not just the first user in the list is valid
+ %% Control the first user
+ %% Authennticating ["one:OnePassword" user first in user list]
+ auth_request(Type, Host, Port, Node, "/ht/open/dummy.html", "one",
+ "OnePassword", [{statuscode, 200}]),
+
+ %% Control the second user
+ %% Authentication OK and a directory listing is supplied!
+ %% ["Aladdin:open sesame" user second in user list]
+ auth_request(Type, Host, Port, Node, "/ht/open/","Aladdin",
+ "AladdinPassword", [{statuscode, 200}]),
+
+ %% Contro that bad passwords and userids get a good denial
+ %% User correct but wrong password! ["one:one" user first in user list]
+ auth_request(Type, Host, Port, Node, "/ht/open/", "one", "one",
+ [{statuscode, 401}]),
+ %% Neither user or password correct! ["dummy:dummy"]
+ auth_request(Type, Host, Port, Node, "/ht/open/", "dummy", "dummy",
+ [{statuscode, 401}]),
+
+ %% Control that authetication still works, even if its a member in a group
+ %% Authentication OK! ["two:TwoPassword" user in first group]
+ auth_request(Type, Host, Port, Node, "/ht/secret/dummy.html", "two",
+ "TwoPassword", [{statuscode, 200}]),
+
+ %% Authentication OK and a directory listing is supplied!
+ %% ["three:ThreePassword" user in second group]
+ auth_request(Type, Host, Port, Node,"/ht/secret/", "three",
+ "ThreePassword", [{statuscode, 200}]),
+
+ %% Deny users with bad passwords even if the user is a group member
+ %% User correct but wrong password! ["two:two" user in first group]
+ auth_request(Type, Host, Port, Node, "/ht/secret/", "two", "two",
+ [{statuscode, 401}]),
+ %% Neither user or password correct! ["dummy:dummy"]
+ auth_request(Type, Host, Port, Node,"/ht/secret/", "dummy", "dummy",
+ [{statuscode, 401}]),
+
+ %% control that we deny the users that are in subnet above the allowed
+ auth_request(Type, Host, Port, Node,"/ht/blocknet/dummy.html", "four",
+ "FourPassword", [{statuscode, 403}]),
+ %% Control that we only applies the rules to the right methods
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD /ht/blocknet/dummy.html"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+
+ %% Control that the rerquire directive can be overrideen
+ auth_request(Type, Host, Port, Node,
+ "/ht/secret/top_secret/", "Aladdin", "AladdinPassword",
+ [{statuscode, 401}]),
+
+ %% Authentication still required!
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /ht/open/ "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /ht/secret/ HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /ht/secret/top_secret/ "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {version, "HTTP/1.0"},
+ {header, "WWW-Authenticate"}]).
+%%--------------------------------------------------------------------
+cgi(Type, Port, Host, Node) ->
+ {Script, Script2, Script3} =
+ case test_server:os_type() of
+ {win32, _} ->
+ {"printenv.bat", "printenv.sh", "cgi_echo.exe"};
+ _ ->
+ {"printenv.sh", "printenv.bat", "cgi_echo"}
+ end,
+
+ %% The length (> 100) is intentional
+ ok = httpd_test_lib:
+ verify_request(Type, Host, Port, Node,
+ "POST /cgi-bin/" ++ Script3 ++
+ " HTTP/1.0\r\n"
+ "Content-Length:100 \r\n\r\n "
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
+ " \r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"},
+ {header, "content-type", "text/plain"}]),
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/"++ Script ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/not_there "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 404},{statuscode, 500},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/"++ Script ++
+ "?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "POST /cgi-bin/"++ Script ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /htbin/"++ Script ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /htbin/not_there "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 404},{statuscode, 500},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /htbin/"++ Script ++
+ "?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "POST /htbin/"++ Script ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "POST /htbin/"++ Script ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+
+ %% Execute an existing, but bad CGI script..
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "POST /htbin/"++ Script2 ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 404},
+ {version, "HTTP/1.0"}]),
+
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "POST /cgi-bin/"++ Script2 ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 404},
+ {version, "HTTP/1.0"}]),
+ ok.
+
+%%--------------------------------------------------------------------
+esi(Type, Port, Host, Node) ->
+ %% Check "ErlScriptAlias" and "EvalScriptAlias" directives
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /eval?httpd_example:print(\"Hi!\")"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /eval?not_allowed:print(\"Hi!\")"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 403},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /eval?httpd_example:undef(\"Hi!\")"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 500},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 400},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example:get "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example:"
+ "get?input=4711"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example:"
+ "post HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/not_allowed:post "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 403},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example:undef "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 404},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /cgi-bin/erl/httpd_example/yahoo"
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 302},
+ {version, "HTTP/1.0"}]),
+ ok.
+%%--------------------------------------------------------------------
+get(Type, Port, Host, Node) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /index.html HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /fsize.shtml HTTP/1.1\r\nHost:"
+ ++ Host ++ "\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Content-Type", "text/html"},
+ {header, "Date"},
+ {header, "Server"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /fsize.shtml HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Content-Type"},
+ {header, "Server"},
+ {header, "Date"},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /secret/dummy.html "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 401},
+ {header, "WWW-Authenticate"},
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "GET /index.html HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {header, "Server"},
+ {header, "Date"},
+ {header, "Content-Type",
+ "text/html"},
+ {version, "HTTP/1.0"}]),
+ ok.
+
+%%--------------------------------------------------------------------
+head(Type, Port, Host, Node) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ "HEAD /index.html HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ {version, "HTTP/1.0"}]),
+ ok.
+%%--------------------------------------------------------------------
+all(Type, Port, Host, Node) ->
+ actions(Type, Port, Host, Node),
+ alias(Type, Port, Host, Node),
+ auth(Type, Port, Host, Node),
+ cgi(Type, Port, Host, Node),
+ esi(Type, Port, Host, Node),
+ get(Type, Port, Host, Node),
+ head(Type, Port, Host, Node),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+auth_request(Type, Host, Port, Node, URI, User, Passwd, Expect) ->
+ Req = ["GET ", URI, " HTTP/1.0\r\n",
+ "Authorization: Basic ",
+ base64:encode_to_string(User++":"++Passwd),
+ "\r\n\r\n"],
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ lists:flatten(Req),
+ [{version, "HTTP/1.0"} | Expect]).
+
+remove_users(Node, ServerRoot, Host, Port, Dir) ->
+ %% List users, delete them, and make sure they are gone.
+ case list_users(Node, ServerRoot, Host, Port, Dir) of
+ {ok, Users} ->
+ lists:foreach(fun(User) ->
+ delete_user(Node, ServerRoot, Host,
+ Port, Dir, User)
+ end,
+ Users),
+ {ok, []} = list_users(Node, ServerRoot, Host, Port, Dir);
+ _ ->
+ ok
+ end.
+
+add_user(Node, Root, Port, Dir, User, Password, UserData) ->
+ Addr = undefined,
+ Directory = filename:join([Root, "htdocs", Dir]),
+ rpc:call(Node, mod_auth, add_user,
+ [User, Password, UserData, Addr, Port, Directory]).
+
+delete_user(Node, Root, _Host, Port, Dir, User) ->
+ Addr = undefined,
+ Directory = filename:join([Root, "htdocs", Dir]),
+ rpc:call(Node, mod_auth, delete_user, [User, Addr, Port, Directory]).
+
+list_users(Node, Root, _Host, Port, Dir) ->
+ Addr = undefined,
+ Directory = filename:join([Root, "htdocs", Dir]),
+ rpc:call(Node, mod_auth, list_users, [Addr, Port, Directory]).
+
+receive_security_event(Event, Node, Port) ->
+ io:format(user, "~w:receive_security_event -> entry with"
+ "~n Event: ~p"
+ "~n Node: ~p"
+ "~n Port: ~p"
+ "~n", [?MODULE, Event, Node, Port]),
+ receive
+ Event ->
+ ok;
+ {'EXIT', _, _} ->
+ receive_security_event(Event, Node, Port);
+ Other ->
+ test_server:fail({unexpected_event,
+ {expected, Event}, {received, Other}})
+ after 5000 ->
+ test_server:fail(no_event_recived)
+
+ end.
+
+list_blocked_users(Node,Port) ->
+ Addr = undefined, % Assumed to be on the same host
+ rpc:call(Node, mod_security, list_blocked_users, [Addr,Port]).
+
+list_blocked_users(Node,Port,Dir) ->
+ Addr = undefined, % Assumed to be on the same host
+ rpc:call(Node, mod_security, list_blocked_users, [Addr,Port,Dir]).
+
+block_user(Node,User,Port,Dir,Sec) ->
+ Addr = undefined, % Assumed to be on the same host
+ rpc:call(Node, mod_security, block_user, [User, Addr, Port, Dir, Sec]).
+
+unblock_user(Node,User,Port,Dir) ->
+ Addr = undefined, % Assumed to be on the same host
+ rpc:call(Node, mod_security, unblock_user, [User, Addr, Port, Dir]).
+
+list_auth_users(Node,Port) ->
+ Addr = undefined, % Assumed to be on the same host
+ rpc:call(Node, mod_security, list_auth_users, [Addr,Port]).
+
+list_auth_users(Node,Port,Dir) ->
+ Addr = undefined, % Assumed to be on the same host
+ rpc:call(Node, mod_security, list_auth_users, [Addr,Port,Dir]).
+
+update_password(Node, ServerRoot, _Address, Port, Dir, Old, New)->
+ Directory = filename:join([ServerRoot, "htdocs", Dir]),
+ rpc:call(Node, mod_auth, update_password,
+ [undefined, Port, Directory, Old, New, New]).
+
+remove_groups(Node, ServerRoot, Host, Port, Dir) ->
+ Directory = filename:join([ServerRoot, "htdocs", Dir]),
+ {ok, Groups} = list_groups(Node, ServerRoot, Host, Port, Directory),
+ lists:foreach(fun(Group) ->
+ delete_group(Node, Group, Port, Directory)
+ end,
+ Groups),
+ {ok, []} = list_groups(Node, ServerRoot, Host, Port, Directory),
+ ok.
+
+delete_group(Node, Group, Port, Dir) ->
+ Addr = undefined,
+ rpc:call(Node, mod_auth, delete_group, [Group, Addr, Port, Dir]).
+
+list_groups(Node, _, _, Port, Dir) ->
+ Addr = undefined,
+ rpc:call(Node, mod_auth, list_groups, [Addr, Port, Dir]).
+
+add_group_member(Node, ServerRoot, Port, Dir, User, Group) ->
+ Addr = undefined,
+ rpc:call(Node, mod_auth, add_group_member, [Group, User, Addr, Port,
+ filename:join(
+ [ServerRoot,
+ "htdocs",Dir])]).
+event(What, Port, Dir, Data) ->
+ Msg = {event, What, Port, Dir, Data},
+ case global:whereis_name(mod_security_test) of
+ undefined ->
+ ok;
+ _Pid ->
+ global:send(mod_security_test, Msg)
+ end.
+
+ssl_password_cb() ->
+ "dummy-ssl-password".
+
+check_lists_members({ok,L},L) ->
+ ok;
+check_lists_members({ok,L1},L2) ->
+ check_lists_members1(lists:sort(L1),lists:sort(L2));
+check_lists_members(Error,_L) ->
+ Error.
+
+check_lists_members1(L,L) ->
+ ok;
+check_lists_members1(L1,L2) ->
+ {error,{lists_not_equal,L1,L2}}.
diff --git a/lib/inets/test/httpd_poll.erl b/lib/inets/test/httpd_poll.erl
new file mode 100644
index 0000000000..1cc10365a7
--- /dev/null
+++ b/lib/inets/test/httpd_poll.erl
@@ -0,0 +1,496 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(httpd_poll).
+-behaviour(gen_server).
+
+
+%% External API
+-export([start/0, start_appup/2, start/3,stop/0,verbosity/1,poll_time/1]).
+
+%% gen_server exports
+-export([init/1,
+ handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
+
+
+-define(default_verbosity,error).
+-define(default_poll_time,60000). %% 60 seconds
+
+
+-record(state,{host = "", port = -1, ptime = -1, tref = none, uris = []}).
+
+
+%% start/0
+%%
+%% Description: Start polling HTTPD with default values
+%%
+start() ->
+ Options = default_options(otp),
+ start("gandalf", 8000, Options).
+
+start_appup(Host, Port) ->
+ Options = default_options(top),
+ start(Host, Port, Options).
+
+%% start/3
+%%
+%% Description: Start polling HTTPD
+%%
+%% Parameters:
+%% Host = string()
+%% Host name of HTTPD
+%% Port = integer()
+%% Port number of HTTPD
+%% Options = [Option]
+%% Option = {poll_time,integer()} | {verbosity,verbosity()} |
+%% {log_file,string()} | {uris,[uri()]}
+%% verbosity() = silence | error | log | debug | trace
+%% uri() = {string(),string}
+%% First part is a descriptive string and the second
+%% part is the actual URI.
+%%
+start(Host,Port,Options) ->
+ gen_server:start({local,httpd_tester},?MODULE,[Host,Port,Options],[]).
+
+stop() ->
+ gen_server:call(httpd_tester,stop).
+
+
+default_options(UriDesc) ->
+ Verbosity = {verbosity,?default_verbosity},
+ Uris = {uris,uris(UriDesc)},
+ PollTime = {poll_time,?default_poll_time},
+ Logging = {log_file,"httpd_poll.log"},
+ [Verbosity, Uris, PollTime, Logging].
+
+
+options(Options) ->
+ options(Options, default_options(otp), []).
+
+options([], Defaults, Options) ->
+ Options ++ Defaults;
+options([{Key,Val} = Opt|Opts], Defaults, Options) ->
+ options(Opts, lists:keydelete(Key, 1, Defaults), [Opt|Options]).
+
+
+verbosity(silence) ->
+ set_verbosity(silence);
+verbosity(error) ->
+ set_verbosity(error);
+verbosity(log) ->
+ set_verbosity(log);
+verbosity(debug) ->
+ set_verbosity(debug);
+verbosity(trace) ->
+ set_verbosity(trace).
+
+set_verbosity(Verbosity) ->
+ gen_server:cast(httpd_tester,{verbosity,Verbosity}).
+
+poll_time(NewTime) ->
+ gen_server:call(httpd_tester,{poll_time,NewTime}).
+
+
+%% ----------------------------------------------------------------------
+
+
+init([Host, Port, Options0]) ->
+ process_flag(trap_exit,true),
+ Options = options(Options0),
+ put(verbosity,get_verbosity(Options)),
+ log_open(get_log_file(Options)),
+ tstart(),
+ PollTime = get_poll_time(Options),
+ Ref = tcreate(PollTime),
+ log("created"),
+ {ok,#state{host = Host,
+ port = Port,
+ ptime = PollTime,
+ tref = Ref,
+ uris = get_uris(Options)}}.
+
+uris(top) ->
+ [uri_top_index()];
+
+uris(otp) ->
+ [
+ uri_top_index(),
+ uri_internal_product1(),
+ uri_internal_product2(),
+ uri_p7a_test_results(),
+ uri_bjorn1(),
+ uri_bjorn2(),
+ uri_top_ronja()
+ ].
+
+uri_top_index() ->
+ {"top page","/"}.
+
+uri_internal_product1() ->
+ {"product internal page (1)","/product/internal/"}.
+
+uri_internal_product2() ->
+ {"product internal page (2)","/product/internal"}.
+
+uri_p7a_test_results() ->
+ {"test summery index page",
+ "/product/internal/test/test_results/progress_P7A/index.html"}.
+
+uri_bjorn1() ->
+ {"bjorns home page (1)","/~bjorn/"}.
+
+uri_bjorn2() ->
+ {"bjorns home page (2)","/~bjorn"}.
+
+uri_top_ronja() ->
+ {"ronja top page","/ronja/"}.
+
+
+handle_call(stop, _From, State) ->
+ vlog("stop request"),
+ {stop, normal, ok, State};
+
+handle_call({poll_time,NewTime}, _From, State) ->
+ vlog("set new poll time: ~p",[NewTime]),
+ OldTime = State#state.ptime,
+ {stop, normal, OldTime, State#state{ptime = NewTime}};
+
+handle_call(Request, _From, State) ->
+ vlog("unexpected request(call): ~p",[Request]),
+ {reply, ok, State}.
+
+
+handle_cast({verbosity,Verbosity}, State) ->
+ vlog("set (new) verbosity to: ~p",[Verbosity]),
+ put(verbosity,Verbosity),
+ {noreply, State};
+
+handle_cast(Message, State) ->
+ vlog("unexpected message(call): ~p",[Message]),
+ {noreply, State}.
+
+
+handle_info(poll_time,State) ->
+ {{Description,Uri},Uris} = get_uri(State#state.uris),
+ vlog("poll time for ~s",[Description]),
+ do_poll(State#state.host,State#state.port,Uri),
+ Ref = tcreate(State#state.ptime),
+ {noreply, State#state{tref = Ref, uris = Uris}};
+
+handle_info(Info, State) ->
+ vlog("unexpected message(info): ~p",[Info]),
+ {noreply, State}.
+
+
+terminate(Reason,State) ->
+ tcancel(State#state.tref),
+ log_close(get(log_file)),
+ ok.
+
+
+get_uri([Uri|Uris]) ->
+ {Uri,Uris++[Uri]}.
+
+
+do_poll(Host,Port,Uri) ->
+ (catch poll(create(Host,Port),Uri,"200")).
+
+poll({ok,Socket},Uri,ExpStatus) ->
+ vtrace("poll -> entry with Socket: ~p",[Socket]),
+ put(latest_requested_uri,Uri),
+ Req = "GET " ++ Uri ++ " HTTP/1.0\r\n\r\n",
+ await_poll_response(send(Socket,Req),Socket,ExpStatus);
+poll({error,Reason},_Req,_ExpStatus) ->
+ verror("failed creating socket: ~p",[Reason]),
+ log("failed creating socket: ~p",[Reason]),
+ exit({error,Reason});
+poll(O,_Req,_ExpStatus) ->
+ verror("unexpected result from socket create: ~p",[O]),
+ log("unexpected result from socket create: ~p",[O]),
+ exit({unexpected_result,O}).
+
+await_poll_response(ok,Socket,ExpStatusCode) ->
+ vtrace("await_poll_response -> awaiting response with status ~s",
+ [ExpStatusCode]),
+ receive
+ {tcp_closed,Socket} ->
+ verror("connection closed when awaiting poll response"),
+ log("connection closed when awaiting reply to GET of '~s'",
+ [get(latest_requested_uri)]),
+ exit(connection_closed);
+ {tcp,Socket,Response} ->
+ vdebug("received response"),
+ validate(ExpStatusCode,Socket,Response)
+ after 10000 ->
+ verror("connection timeout waiting for poll response",[]),
+ log("connection timeout waiting for reply to GET of '~s'",
+ [get(latest_requested_uri)]),
+ exit(connection_timed_out)
+ end;
+await_poll_response(Error,_Socket,_ExpStatusCode) ->
+ verror("failed sending GET request for '~s' for reason: ~p",
+ [get(latest_requested_uri),Error]),
+ log("failed sending GET request for '~s' for reason: ~p",
+ [get(latest_requested_uri),Error]),
+ exit(Error).
+
+
+validate(ExpStatusCode,Socket,Response) ->
+ Sz = sz(Response),
+ vtrace("validate -> Entry with ~p bytes response",[Sz]),
+ Size = trash_the_rest(Socket,Sz),
+ close(Socket),
+ case inets_regexp:split(Response," ") of
+ {ok,["HTTP/1.0",ExpStatusCode|_]} ->
+ vlog("response (~p bytes) was ok",[Size]),
+ ok;
+ {ok,["HTTP/1.0",StatusCode|_]} ->
+ verror("unexpected response status received: ~s => ~s",
+ [StatusCode,status_to_message(StatusCode)]),
+ log("unexpected result to GET of '~s': ~s => ~s",
+ [get(latest_requested_uri),StatusCode,
+ status_to_message(StatusCode)]),
+ exit({unexpected_response_code,StatusCode,ExpStatusCode})
+ end.
+
+
+%% ------------------------------------------------------------------
+
+trash_the_rest(Socket,N) ->
+ receive
+ {tcp, Socket, Trash} ->
+ vtrace("trash_the_rest -> trash ~p bytes",[sz(Trash)]),
+ trash_the_rest(Socket,add(N,sz(Trash)));
+ {tcp_closed, Socket} ->
+ vdebug("socket closed after receiving ~p bytes",[N]),
+ N
+ after 10000 ->
+ verror("connection timeout waiting for message"),
+ exit(connection_timed_out)
+ end.
+
+
+add(N1,N2) when integer(N1),integer(N2) ->
+ N1 + N2;
+add(N1,N2) when integer(N1) ->
+ N1;
+add(N1,N2) when integer(N2) ->
+ N2.
+
+sz(L) when list(L) ->
+ length(lists:flatten(L));
+sz(B) when binary(B) ->
+ size(B);
+sz(O) ->
+ {unknown_size,O}.
+
+
+%% --------------------------------------------------------------
+%%
+%% Status code to printable string
+%%
+
+status_to_message(L) when list(L) ->
+ case (catch list_to_integer(L)) of
+ I when integer(I) ->
+ status_to_message(I);
+ _ ->
+ io_lib:format("UNKNOWN STATUS CODE: '~p'",[L])
+ end;
+status_to_message(100) -> "Section 10.1.1: Continue";
+status_to_message(101) -> "Section 10.1.2: Switching Protocols";
+status_to_message(200) -> "Section 10.2.1: OK";
+status_to_message(201) -> "Section 10.2.2: Created";
+status_to_message(202) -> "Section 10.2.3: Accepted";
+status_to_message(203) -> "Section 10.2.4: Non-Authoritative Information";
+status_to_message(204) -> "Section 10.2.5: No Content";
+status_to_message(205) -> "Section 10.2.6: Reset Content";
+status_to_message(206) -> "Section 10.2.7: Partial Content";
+status_to_message(300) -> "Section 10.3.1: Multiple Choices";
+status_to_message(301) -> "Section 10.3.2: Moved Permanently";
+status_to_message(302) -> "Section 10.3.3: Found";
+status_to_message(303) -> "Section 10.3.4: See Other";
+status_to_message(304) -> "Section 10.3.5: Not Modified";
+status_to_message(305) -> "Section 10.3.6: Use Proxy";
+status_to_message(307) -> "Section 10.3.8: Temporary Redirect";
+status_to_message(400) -> "Section 10.4.1: Bad Request";
+status_to_message(401) -> "Section 10.4.2: Unauthorized";
+status_to_message(402) -> "Section 10.4.3: Peyment Required";
+status_to_message(403) -> "Section 10.4.4: Forbidden";
+status_to_message(404) -> "Section 10.4.5: Not Found";
+status_to_message(405) -> "Section 10.4.6: Method Not Allowed";
+status_to_message(406) -> "Section 10.4.7: Not Acceptable";
+status_to_message(407) -> "Section 10.4.8: Proxy Authentication Required";
+status_to_message(408) -> "Section 10.4.9: Request Time-Out";
+status_to_message(409) -> "Section 10.4.10: Conflict";
+status_to_message(410) -> "Section 10.4.11: Gone";
+status_to_message(411) -> "Section 10.4.12: Length Required";
+status_to_message(412) -> "Section 10.4.13: Precondition Failed";
+status_to_message(413) -> "Section 10.4.14: Request Entity Too Large";
+status_to_message(414) -> "Section 10.4.15: Request-URI Too Large";
+status_to_message(415) -> "Section 10.4.16: Unsupported Media Type";
+status_to_message(416) -> "Section 10.4.17: Requested range not satisfiable";
+status_to_message(417) -> "Section 10.4.18: Expectation Failed";
+status_to_message(500) -> "Section 10.5.1: Internal Server Error";
+status_to_message(501) -> "Section 10.5.2: Not Implemented";
+status_to_message(502) -> "Section 10.5.3: Bad Gatteway";
+status_to_message(503) -> "Section 10.5.4: Service Unavailable";
+status_to_message(504) -> "Section 10.5.5: Gateway Time-out";
+status_to_message(505) -> "Section 10.5.6: HTTP Version not supported";
+status_to_message(Code) -> io_lib:format("Unknown status code: ~p",[Code]).
+
+
+%% ----------------------------------------------------------------
+
+create(Host,Port) ->
+ vtrace("create -> ~n\tHost: ~s~n\tPort: ~p",[Host,Port]),
+ case gen_tcp:connect(Host,Port,[{packet,0},{reuseaddr,true}]) of
+ {ok,Socket} ->
+ {ok,Socket};
+ {error,{enfile,_}} ->
+ {error,enfile};
+ Error ->
+ Error
+ end.
+
+close(Socket) ->
+ gen_tcp:close(Socket).
+
+
+send(Socket,Data) ->
+ vtrace("send -> send ~p bytes of data",[length(Data)]),
+ gen_tcp:send(Socket,Data).
+
+
+%% ----------------------------------------------------------------
+
+tstart() ->
+ timer:start().
+
+tcreate(Time) ->
+ {ok,Ref} = timer:send_after(Time,poll_time),
+ Ref.
+
+tcancel(Ref) ->
+ timer:cancel(Ref).
+
+%% ----------------------------------------------------------------
+
+log_open(undefined) ->
+ ok;
+log_open(FileName) ->
+ put(log_file,fopen(FileName)).
+
+log_close(undefined) ->
+ ok;
+log_close(Fd) ->
+ fclose(Fd).
+
+log(F) ->
+ log(F,[]).
+
+log(F,A) ->
+ {{Year,Month,Day},{Hour,Min,Sec}} = local_time(),
+ fwrite(get(log_file),
+ "~w.~w.~w ~w.~w.~w " ++ F ++ "~n",
+ [Year,Month,Day,Hour,Min,Sec] ++ A).
+
+%% ----------------------------------------------------------------
+
+fopen(Name) ->
+ {ok,Fd} = file:open(Name,[write]),
+ Fd.
+
+fclose(Fd) ->
+ file:close(Fd).
+
+fwrite(undefined,_F,_A) ->
+ ok;
+fwrite(Fd,F,A) ->
+ io:format(Fd,F,A).
+
+
+%% ----------------------------------------------------------------
+
+get_poll_time(Opts) ->
+ get_option(poll_time,Opts,?default_poll_time).
+
+get_log_file(Opts) ->
+ get_option(log_file,Opts).
+
+get_uris(Opts) ->
+ get_option(uris,Opts,[]).
+
+get_verbosity(Opts) ->
+ get_option(verbosity,Opts,?default_verbosity).
+
+get_option(Opt,Opts) ->
+ get_option(Opt,Opts,undefined).
+
+get_option(Opt,Opts,Default) ->
+ case lists:keysearch(Opt,1,Opts) of
+ {value,{Opt,Value}} ->
+ Value;
+ false ->
+ Default
+ end.
+
+%% ----------------------------------------------------------------
+
+%% sleep(T) -> receive after T -> ok end.
+
+%% ----------------------------------------------------------------
+
+%% vtrace(F) -> vprint(get(verbosity),trace,F,[]).
+vtrace(F,A) -> vprint(get(verbosity),trace,F,A).
+
+vdebug(F) -> vprint(get(verbosity),debug,F,[]).
+vdebug(F,A) -> vprint(get(verbosity),debug,F,A).
+
+vlog(F) -> vprint(get(verbosity),log,F,[]).
+vlog(F,A) -> vprint(get(verbosity),log,F,A).
+
+verror(F) -> vprint(get(verbosity),error,F,[]).
+verror(F,A) -> vprint(get(verbosity),error,F,A).
+
+vprint(trace,Severity,F,A) -> vprint(Severity,F,A);
+vprint(debug,trace,F,A) -> ok;
+vprint(debug,Severity,F,A) -> vprint(Severity,F,A);
+vprint(log,log,F,A) -> vprint(log,F,A);
+vprint(log,error,F,A) -> vprint(log,F,A);
+vprint(error,error,F,A) -> vprint(error,F,A);
+vprint(_Verbosity,_Severity,_F,_A) -> ok.
+
+vprint(Severity,F,A) ->
+ {{Year,Month,Day},{Hour,Min,Sec}} = local_time(),
+ io:format("~w.~w.~w ~w.~w.~w " ++ image_of(Severity) ++ F ++ "~n",
+ [Year,Month,Day,Hour,Min,Sec] ++ A).
+
+image_of(error) -> "ERR: ";
+image_of(log) -> "LOG: ";
+image_of(debug) -> "DBG: ";
+image_of(trace) -> "TRC: ".
+
+local_time() -> calendar:local_time().
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/auth/group b/lib/inets/test/httpd_test_data/server_root/auth/group
new file mode 100644
index 0000000000..b3da0ccbd3
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/auth/group
@@ -0,0 +1,3 @@
+group1: one two
+group2: two three
+group3: three Aladdin
diff --git a/lib/inets/test/httpd_test_data/server_root/auth/passwd b/lib/inets/test/httpd_test_data/server_root/auth/passwd
new file mode 100644
index 0000000000..8c980ff547
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/auth/passwd
@@ -0,0 +1,4 @@
+one:onePassword
+two:twoPassword
+three:threePassword
+Aladdin:AladdinPassword
diff --git a/lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.bat b/lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.bat
new file mode 100644
index 0000000000..25a49a1536
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.bat
@@ -0,0 +1,9 @@
+@echo off
+echo tomrad > c:\cygwin\tmp\hej
+echo Content-type: text/html
+echo.
+echo ^<HTML^> ^<HEAD^> ^<TITLE^>OS Environment^</TITLE^> ^</HEAD^> ^<BODY^>^<PRE^>
+set
+echo ^</PRE^>^</BODY^>^</HTML^>
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.sh b/lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.sh
new file mode 100755
index 0000000000..de81de9bde
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/cgi-bin/printenv.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+echo "Content-type: text/html"
+echo ""
+echo "<HTML> <HEAD> <TITLE>OS Environment</TITLE> </HEAD> <BODY><PRE>"
+env
+echo "</PRE></BODY></HTML>" \ No newline at end of file
diff --git a/lib/inets/test/httpd_test_data/server_root/conf/8080.conf b/lib/inets/test/httpd_test_data/server_root/conf/8080.conf
new file mode 100644
index 0000000000..48e66f0114
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/conf/8080.conf
@@ -0,0 +1,79 @@
+Port 8080
+#ServerName your.server.net
+SocketType ip_comm
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_include mod_dir mod_get mod_head mod_log mod_disk_log
+ServerAdmin [email protected]
+ServerRoot /var/tmp/server_root
+ErrorLog logs/error_log_8080
+TransferLog logs/access_log_8080
+SecurityLog logs/security_log_8080
+ErrorDiskLog logs/error_disk_log_8080
+ErrorDiskLogSize 200000 10
+TransferDiskLog logs/access_disk_log_8080
+TransferDiskLogSize 200000 10
+SecurityDiskLog logs/security_disk_log
+SecurityDiskLogSize 200000 10
+MaxClients 50
+#KeepAlive 5
+#KeepAliveTimeout 10
+DocumentRoot /var/tmp/server_root/htdocs
+DirectoryIndex index.html welcome.html
+DefaultType text/plain
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+ErlScriptAlias /cgi-bin/erl httpd_example io
+EvalScriptAlias /eval httpd_example io
+#Script HEAD /cgi-bin/printenv.sh
+#Action image/gif /cgi-bin/printenv.sh
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthDBType plain
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthDBType plain
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthDBType plain
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthDBType mnesia
+AuthName Open Area
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthDBType mnesia
+AuthName Secret Area
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthDBType mnesia
+AuthName Top Secret Area
+require group group3
+allow from 130.100.34 130.100.35
+deny from 100.234.22.12 194.100.34.1 130.100.34.25
+SecurityDataFile logs/security_data
+SecurityMaxRetries 3
+SecurityBlockTime 10
+SecurityFailExpireTime 1
+SecurityAuthTimeout 1
+SecurityCallbackModule security_callback
+</Directory>
diff --git a/lib/inets/test/httpd_test_data/server_root/conf/8888.conf b/lib/inets/test/httpd_test_data/server_root/conf/8888.conf
new file mode 100644
index 0000000000..79bb7fcca4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/conf/8888.conf
@@ -0,0 +1,63 @@
+Port 8888
+#ServerName your.server.net
+SocketType ip_comm
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_include mod_dir mod_get mod_head mod_log mod_disk_log
+ServerAdmin [email protected]
+ServerRoot /var/tmp/server_root
+ErrorLog logs/error_log_8888
+TransferLog logs/access_log_8888
+ErrorDiskLog logs/error_disk_log_8888
+ErrorDiskLogSize 200000 10
+TransferDiskLog logs/access_disk_log_8888
+TransferDiskLogSize 200000 10
+MaxClients 150
+DocumentRoot /var/tmp/server_root/htdocs
+DirectoryIndex index.html welcome.html
+DefaultType text/plain
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+ErlScriptAlias /cgi-bin/erl httpd_example io
+EvalScriptAlias /eval httpd_example io
+#Script HEAD /cgi-bin/printenv.sh
+#Action image/gif /cgi-bin/printenv.sh
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthName Open Area
+AuthMnesiaDB On
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthName Secret Area
+AuthMnesiaDB On
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthName Top Secret Area
+AuthMnesiaDB On
+require group group3
+</Directory>
diff --git a/lib/inets/test/httpd_test_data/server_root/conf/httpd.conf b/lib/inets/test/httpd_test_data/server_root/conf/httpd.conf
new file mode 100644
index 0000000000..8a74ed1afd
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/conf/httpd.conf
@@ -0,0 +1,268 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+#
+
+# Port: The port the standalone listens to. For ports < 1023, you will
+# need httpd to be run as root initially.
+
+Port 8888
+
+# BindAddress: This directive is used to tell the server which IP address
+# to listen to. It can either contain "*", an IP address, or a fully
+# qualified Internet domain name.
+#
+# It is also possible to specify the ip-family with the directive.
+# There ar three possible value: inet, inet6 and inet6fb4
+# inet: Use IpFamily inet when retreiving the address and
+# fail if that does not work.
+# inet6: Use IpFamily inet6 when retreiving the address and
+# fail if that does not work.
+# inet6fb4: First IpFamily inet6 is tried and if that does not work,
+# inet is used as fallback.
+# Default value for ip-family is inet6fb4
+#
+# The syntax is: <address>[|<ip-family>]
+#
+#BindAddress *
+#BindAddress *|inet
+
+
+# ServerName allows you to set a host name which is sent back to clients for
+# your server if it's different than the one the program would get (i.e. use
+# "www" instead of the host's real name).
+#
+# Note: You cannot just invent host names and hope they work. The name you
+# define here must be a valid DNS name for your host. If you don't understand
+# this, ask your network administrator.
+
+#ServerName your.server.net
+
+# SocketType is either ip_comm, sockets or ssl.
+
+SocketType ip_comm
+
+# Modules: Server run-time plug-in modules written using the Erlang
+# Web Server API (EWSAPI). The server API make it easy to add functionality
+# to the server. Read more about EWSAPI in the Reference Manual.
+# WARNING! Do not tamper with this directive unless you are familiar with
+# EWSAPI.
+
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_include mod_dir mod_get mod_log mod_disk_log
+
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed.
+
+ServerAdmin [email protected]
+
+# ServerRoot: The directory the server's config, error, and log files
+# are kept in
+
+ServerRoot /var/tmp/server_root
+
+# ErrorLog: The location of the error log file. If this does not start
+# with /, ServerRoot is prepended to it.
+
+ErrorLog logs/error_log
+
+# TransferLog: The location of the transfer log file. If this does not
+# start with /, ServerRoot is prepended to it.
+
+TransferLog logs/access_log
+
+# SecurityLog: The location of the security log file (mod_security required)
+#
+SecurityLog logs/security_log
+
+# ErrorDiskLog: The location of the error log file. If this does not
+# start with /, ServerRoot is prepended to it. This log file is managed
+# with the disk_log module [See disk_log(3)]. The ErrorDiskLogSize directive
+# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+# truncates the first file.
+
+ErrorDiskLog logs/error_disk_log
+ErrorDiskLogSize 200000 10
+
+# TransferDiskLog: The location of the transfer log file. If this does not
+# start with /, ServerRoot is prepended to it. This log file is managed
+# with the disk_log module [See disk_log(3)]. The TransferDiskLogSize directive
+# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+# truncates the first file.
+
+TransferDiskLog logs/access_disk_log
+TransferDiskLogSize 200000 10
+
+# SecurityDiskLog: The location of the security log file. If this does not
+# start with /, ServerRoot is prepended to it. This log file is managed
+# with the disk_log module [See disk_log(3)]. The SecurityDiskLogSize directive
+# takes two argument, i.e. MaxBytes and MaxFiles. The wrap log writes at most
+# MaxBytes bytes on each file, and it uses MaxFiles files before it wraps, and
+# truncates the first file.
+
+SecurityDiskLog logs/security_disk_log
+SecurityDiskLogSize 200000 10
+
+# Limit on total number of servers running, i.e., limit on the number
+# of clients who can simultaneously connect --- if this limit is ever
+# reached, clients will be LOCKED OUT, so it should NOT BE SET TOO LOW.
+# It is intended mainly as a brake to keep a runaway server from taking
+# the server with it as it spirals down...
+
+MaxClients 50
+
+# KeepAlive set the flag for persistent connections. For peristent connections
+# set KeepAlive to on. To use One request per connection set the flag to off
+# Note: The value has changed since previous version of INETS.
+KeepAlive on
+
+# KeepAliveTimeout sets the number of seconds before a persistent connection
+# times out and closes.
+KeepAliveTimeout 10
+
+# MaxKeepAliveRequests sets the number of seconds before a persistent connection
+# times out and closes.
+MaxKeepAliveRequests 10
+
+
+
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+
+DocumentRoot /var/tmp/server_root/htdocs
+
+# DirectoryIndex: Name of the file or files to use as a pre-written HTML
+# directory index. Separate multiple entries with spaces.
+
+DirectoryIndex index.html welcome.html
+
+# DefaultType is the default MIME type for documents which the server
+# cannot find the type of from filename extensions.
+
+DefaultType text/plain
+
+# Aliases: Add here as many aliases as you need (with no limit). The format is
+# Alias fakename realname
+
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+
+# ScriptAlias: This controls which directories contain server scripts.
+# Format: ScriptAlias fakename realname
+
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+
+# This directive adds an action, which will activate cgi-script when a
+# file is requested using the method of method, which can be one of
+# GET, POST and HEAD. It sends the URL and file path of the requested
+# document using the standard CGI PATH_INFO and PATH_TRANSLATED
+# environment variables.
+
+#Script HEAD /cgi-bin/printenv.sh
+
+# This directive adds an action, which will activate cgi-script when a
+# file of content type mime-type is requested. It sends the URL and
+# file path of the requested document using the standard CGI PATH_INFO
+# and PATH_TRANSLATED environment variables.
+
+#Action image/gif /cgi-bin/printenv.sh
+
+# ErlScriptAlias: This specifies how "Erl" server scripts are called.
+# Format: ErlScriptAlias fakename realname allowed_modules
+
+ErlScriptAlias /down/erl httpd_example io
+
+# EvalScriptAlias: This specifies how "Eval" server scripts are called.
+# Format: EvalScriptAlias fakename realname allowed_modules
+
+EvalScriptAlias /eval httpd_example io
+
+# Point SSLCertificateFile at a PEM encoded certificate.
+
+SSLCertificateFile /var/tmp/server_root/ssl/ssl_server.pem
+
+# If the key is not combined with the certificate, use this directive to
+# point at the key file.
+
+SSLCertificateKeyFile /var/tmp/server_root/ssl/ssl_server.pem
+
+# Set SSLVerifyClient to:
+# 0 if no certicate is required
+# 1 if the client may present a valid certificate
+# 2 if the client must present a valid certificate
+# 3 if the client may present a valid certificate but it is not required to
+# have a valid CA
+
+SSLVerifyClient 0
+
+# Each directory to which INETS has access, can be configured with respect
+# to which services and features are allowed and/or disabled in that
+# directory (and its subdirectories).
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthDBType plain
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthDBType plain
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthDBType plain
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthDBType mnesia
+AuthName Open Area
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthDBType mnesia
+AuthName Secret Area
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthDBType mnesia
+AuthName Top Secret Area
+require group group3
+allow from 130.100.34 130.100.35
+deny from 100.234.22.12 194.100.34.1 130.100.34.25
+SecurityDataFile logs/security_data
+SecurityMaxRetries 3
+SecurityBlockTime 10
+SecurityFailExpireTime 1
+SecurityAuthTimeout 1
+SecurityCallbackModule security_callback
+</Directory>
diff --git a/lib/inets/test/httpd_test_data/server_root/conf/mime.types b/lib/inets/test/httpd_test_data/server_root/conf/mime.types
new file mode 100644
index 0000000000..d2f81e4e5e
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/conf/mime.types
@@ -0,0 +1,465 @@
+# This is a comment. I love comments.
+
+# MIME type Extension
+application/EDI-Consent
+application/EDI-X12
+application/EDIFACT
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atomicmail
+application/batch-SMTP
+application/beep+xml
+application/cals-1840
+application/commonground
+application/cybercash
+application/dca-rft
+application/dec-dx
+application/dvcs
+application/eshop
+application/http
+application/hyperstudio
+application/iges
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/font-tdpfr
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc
+application/mathematica
+application/mathematica-old
+application/msword doc
+application/news-message-id
+application/news-transmission
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh exe class so dll
+application/oda oda
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted
+application/pgp-keys
+application/pgp-signature
+application/pkcs10
+application/pkcs7-mime
+application/pkcs7-signature
+application/pkix-cert
+application/pkix-crl
+application/pkixcmp
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww
+application/prs.nprend
+application/qsig
+application/remote-printing
+application/riscos
+application/rtf
+application/sdp
+application/set-payment
+application/set-payment-initiation
+application/set-registration
+application/set-registration-initiation
+application/sgml
+application/sgml-open-catalog
+application/sieve
+application/slate
+application/smil smi smil
+application/timestamp-query
+application/timestamp-reply
+application/vemmi
+application/vnd.3M.Post-it-Notes
+application/vnd.FloGraphIt
+application/vnd.accpac.simply.aso
+application/vnd.accpac.simply.imp
+application/vnd.acucobol
+application/vnd.aether.imp
+application/vnd.anser-web-certificate-issue-initiation
+application/vnd.anser-web-funds-transfer-initiation
+application/vnd.audiograph
+application/vnd.businessobjects
+application/vnd.bmi
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.claymore
+application/vnd.commerce-battelle
+application/vnd.commonspace
+application/vnd.comsocaller
+application/vnd.contact.cmsg
+application/vnd.cosmocaller
+application/vnd.cups-postscript
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.ctc-posml
+application/vnd.cybank
+application/vnd.dna
+application/vnd.dpgraph
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven
+application/vnd.epson.esf
+application/vnd.epson.msf
+application/vnd.epson.quickanime
+application/vnd.epson.salt
+application/vnd.epson.ssf
+application/vnd.ericsson.quickcall
+application/vnd.eudora.data
+application/vnd.fdf
+application/vnd.ffsns
+application/vnd.framemaker
+application/vnd.fsc.weblaunch
+application/vnd.fujitsu.oasys
+application/vnd.fujitsu.oasys2
+application/vnd.fujitsu.oasys3
+application/vnd.fujitsu.oasysgp
+application/vnd.fujitsu.oasysprs
+application/vnd.fujixerox.ddd
+application/vnd.fujixerox.docuworks
+application/vnd.fujixerox.docuworks.binder
+application/vnd.fut-misnet
+application/vnd.grafeq
+application/vnd.groove-account
+application/vnd.groove-identity-message
+application/vnd.groove-injector
+application/vnd.groove-tool-message
+application/vnd.groove-tool-template
+application/vnd.groove-vcard
+application/vnd.hhe.lesson-player
+application/vnd.hp-HPGL
+application/vnd.hp-PCL
+application/vnd.hp-PCLXL
+application/vnd.hp-hpid
+application/vnd.hp-hps
+application/vnd.httphone
+application/vnd.hzn-3d-crossword
+application/vnd.ibm.afplinedata
+application/vnd.ibm.MiniPay
+application/vnd.ibm.modcap
+application/vnd.informix-visionary
+application/vnd.intercon.formnet
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo
+application/vnd.intu.qfx
+application/vnd.irepository.package+xml
+application/vnd.is-xpr
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.koan
+application/vnd.lotus-1-2-3
+application/vnd.lotus-approach
+application/vnd.lotus-freelance
+application/vnd.lotus-notes
+application/vnd.lotus-organizer
+application/vnd.lotus-screencam
+application/vnd.lotus-wordpro
+application/vnd.mcd
+application/vnd.mediastation.cdkey
+application/vnd.meridian-slingshot
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf
+application/vnd.mobius.dis
+application/vnd.mobius.msl
+application/vnd.mobius.plc
+application/vnd.mobius.txf
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml
+application/vnd.ms-artgalry
+application/vnd.ms-asf
+application/vnd.ms-excel xls
+application/vnd.ms-lrm
+application/vnd.ms-powerpoint ppt
+application/vnd.ms-project
+application/vnd.ms-tnef
+application/vnd.ms-works
+application/vnd.mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician
+application/vnd.netfpx
+application/vnd.noblenet-directory
+application/vnd.noblenet-sealer
+application/vnd.noblenet-web
+application/vnd.novadigm.EDM
+application/vnd.novadigm.EDX
+application/vnd.novadigm.EXT
+application/vnd.osa.netdeploy
+application/vnd.palm
+application/vnd.pg.format
+application/vnd.pg.osasli
+application/vnd.powerbuilder6
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.previewsystems.box
+application/vnd.publishare-delta-tree
+application/vnd.pvi.ptid1
+application/vnd.pwg-xhtml-print+xml
+application/vnd.rapid
+application/vnd.s3sms
+application/vnd.seemail
+application/vnd.shana.informed.formdata
+application/vnd.shana.informed.formtemplate
+application/vnd.shana.informed.interchange
+application/vnd.shana.informed.package
+application/vnd.sss-cod
+application/vnd.sss-dtf
+application/vnd.sss-ntf
+application/vnd.street-stream
+application/vnd.svd
+application/vnd.swiftview-ics
+application/vnd.triscape.mxs
+application/vnd.trueapp
+application/vnd.truedoc
+application/vnd.tve-trigger
+application/vnd.ufdl
+application/vnd.uplanet.alert
+application/vnd.uplanet.alert-wbxml
+application/vnd.uplanet.bearer-choice-wbxml
+application/vnd.uplanet.bearer-choice
+application/vnd.uplanet.cacheop
+application/vnd.uplanet.cacheop-wbxml
+application/vnd.uplanet.channel
+application/vnd.uplanet.channel-wbxml
+application/vnd.uplanet.list
+application/vnd.uplanet.list-wbxml
+application/vnd.uplanet.listcmd
+application/vnd.uplanet.listcmd-wbxml
+application/vnd.uplanet.signal
+application/vnd.vcx
+application/vnd.vectorworks
+application/vnd.vidsoft.vidconference
+application/vnd.visio
+application/vnd.vividence.scriptfile
+application/vnd.wap.sic
+application/vnd.wap.slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo
+application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf
+application/vnd.xara
+application/vnd.xfdl
+application/vnd.yellowriver-custom-menu
+application/whoispp-query
+application/whoispp-response
+application/wita
+application/wordperfect5.1
+application/x-bcpio bcpio
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-gzip
+application/x-hdf hdf
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/x400-bp
+application/xml
+application/xml-dtd
+application/xml-external-parsed-entity
+application/zip zip
+audio/32kadpcm
+audio/basic au snd
+audio/g.722.1
+audio/l16
+audio/midi mid midi kar
+audio/mp4a-latm
+audio/mpa-robust
+audio/mpeg mpga mp2 mp3
+audio/parityfec
+audio/prs.sid
+audio/telephone-event
+audio/tone
+audio/vnd.cisco.nse
+audio/vnd.cns.anp1
+audio/vnd.cns.inf1
+audio/vnd.digital-winds
+audio/vnd.everad.plj
+audio/vnd.lucent.voice
+audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800
+audio/vnd.nuera.ecelp7470
+audio/vnd.nuera.ecelp9600
+audio/vnd.octel.sbc
+audio/vnd.qcelp
+audio/vnd.rhetorex.32kadpcm
+audio/vnd.vmx.cvsd
+audio/x-aiff aif aiff aifc
+audio/x-mpegurl m3u
+audio/x-pn-realaudio ram rm
+audio/x-pn-realaudio-plugin rpm
+audio/x-realaudio ra
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm
+image/g3fax
+image/gif gif
+image/ief ief
+image/jpeg jpeg jpg jpe
+image/naplps
+image/png png
+image/prs.btif
+image/prs.pti
+image/tiff tiff tif
+image/vnd.cns.inf2
+image/vnd.dwg
+image/vnd.dxf
+image/vnd.fastbidsheet
+image/vnd.fpx
+image/vnd.fst
+image/vnd.fujixerox.edmics-mmr
+image/vnd.fujixerox.edmics-rlc
+image/vnd.mix
+image/vnd.net-fpx
+image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff
+image/x-cmu-raster ras
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+message/delivery-status
+message/disposition-notification
+message/external-body
+message/http
+message/news
+message/partial
+message/rfc822
+message/s-http
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.dwf
+model/vnd.flatland.3dml
+model/vnd.gdl
+model/vnd.gs-gdl
+model/vnd.gtw
+model/vnd.mts
+model/vnd.vtu
+model/vrml wrl vrml
+multipart/alternative
+multipart/appledouble
+multipart/byteranges
+multipart/digest
+multipart/encrypted
+multipart/form-data
+multipart/header-set
+multipart/mixed
+multipart/parallel
+multipart/related
+multipart/report
+multipart/signed
+multipart/voice-message
+text/calendar
+text/css css
+text/directory
+text/enriched
+text/html html htm
+text/parityfec
+text/plain asc txt
+text/prs.lines.tag
+text/rfc822-headers
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/tab-separated-values tsv
+text/t140
+text/uri-list
+text/vnd.DMClientScript
+text/vnd.IPTC.NITF
+text/vnd.IPTC.NewsML
+text/vnd.abc
+text/vnd.curl
+text/vnd.flatland.3dml
+text/vnd.fly
+text/vnd.fmi.flexstor
+text/vnd.in3d.3dml
+text/vnd.in3d.spot
+text/vnd.latex-z
+text/vnd.motorola.reflex
+text/vnd.ms-mediapackage
+text/vnd.wap.si
+text/vnd.wap.sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+text/x-server-parsed-html shtml
+text/xml xml xsl
+text/xml-external-parsed-entity
+video/mp4v-es
+video/mpeg mpeg mpg mpe
+video/parityfec
+video/pointer
+video/quicktime qt mov
+video/vnd.fvt
+video/vnd.motorola.video
+video/vnd.motorola.videop
+video/vnd.mpegurl mxu
+video/vnd.mts
+video/vnd.nokia.interleaved-multimedia
+video/vnd.vivo
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/conf/ssl.conf b/lib/inets/test/httpd_test_data/server_root/conf/ssl.conf
new file mode 100644
index 0000000000..8b8c57a98b
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/conf/ssl.conf
@@ -0,0 +1,66 @@
+Port 8088
+#ServerName your.server.net
+SocketType ssl
+Modules mod_alias mod_auth mod_esi mod_actions mod_cgi mod_include mod_dir mod_get mod_head mod_log mod_disk_log
+ServerAdmin [email protected]
+ServerRoot /var/tmp/server_root
+ErrorLog logs/error_log_8088
+TransferLog logs/access_log_8088
+ErrorDiskLog logs/error_disk_log_8088
+ErrorDiskLogSize 200000 10
+TransferDiskLog logs/access_disk_log_8088
+TransferDiskLogSize 200000 10
+MaxClients 150
+DocumentRoot /var/tmp/server_root/htdocs
+DirectoryIndex index.html welcome.html
+DefaultType text/plain
+Alias /icons/ /var/tmp/server_root/icons/
+Alias /pics/ /var/tmp/server_root/icons/
+ScriptAlias /cgi-bin/ /var/tmp/server_root/cgi-bin/
+ScriptAlias /htbin/ /var/tmp/server_root/cgi-bin/
+ErlScriptAlias /cgi-bin/erl httpd_example io
+EvalScriptAlias /eval httpd_example io
+SSLCertificateFile /var/tmp/server_root/ssl/ssl_server.pem
+SSLCertificateKeyFile /var/tmp/server_root/ssl/ssl_server.pem
+SSLVerifyClient 0
+#Script HEAD /cgi-bin/printenv.sh
+#Action image/gif /cgi-bin/printenv.sh
+
+<Directory /var/tmp/server_root/htdocs/open>
+AuthName Open Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret>
+AuthName Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/secret/top_secret>
+AuthName Top Secret Area
+AuthUserFile /var/tmp/server_root/auth/passwd
+AuthGroupFile /var/tmp/server_root/auth/group
+require group group3
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_open>
+AuthName Open Area
+AuthMnesiaDB On
+require user one Aladdin
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret>
+AuthName Secret Area
+AuthMnesiaDB On
+require group group1 group2
+</Directory>
+
+<Directory /var/tmp/server_root/htdocs/mnesia_secret/top_secret>
+AuthName Top Secret Area
+AuthMnesiaDB On
+require group group3
+</Directory>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/config.shtml b/lib/inets/test/httpd_test_data/server_root/htdocs/config.shtml
new file mode 100644
index 0000000000..107e3ff610
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/config.shtml
@@ -0,0 +1,70 @@
+<HTML>
+<HEAD>
+<TITLE>/ssi.html (17-Apr-1997)</TITLE>
+</HEAD>
+<BODY>
+<H1>/ssi.html</H1>
+
+<!-- ************* CONFIG ************* -->
+
+<!--#config timefmt="%a %b %e %T %Z %Y" sizefmt="abbrev"-->
+<!--#config errmsg="[an especially ugly error occurred while processing this directive]"-->
+
+<!-- ************* INCLUDE ************* -->
+
+<P>Include /misc/friedrich.html:
+<!--#include virtual="/misc/friedrich.html"-->
+<P>Include /misc/not_defined.html: <!--#include virtual="/misc/not_defined.html"-->
+<P>Include misc/friedrich.html:
+<!--#include file="misc/friedrich.html"-->
+<P>Include not_defined.html: <!--#include file="not_defined.html"-->
+
+<P><HR>
+
+<!-- ************* ECHO ************* -->
+
+<P>DOCUMENT_NAME: <!--#echo var="DOCUMENT_NAME"-->
+<P>DOCUMENT_URI: <!--#echo var="DOCUMENT_URI"-->
+<P>QUERY_STRING_UNESCAPED: <!--#echo var="QUERY_STRING_UNESCAPED"-->
+<P>DATE_LOCAL: <!--#echo var="DATE_LOCAL"-->
+<P>DATE_GMT: <!--#echo var="DATE_GMT"-->
+<P>LAST_MODIFIED: <!--#echo var="LAST_MODIFIED"-->
+<P>NOT_DEFINED: <!--#echo var="NOT_DEFINED"-->
+
+<P><HR>
+
+<!-- ************* FSIZE ************* -->
+
+<P>Size of index.html: <!--#fsize file="index.html"-->
+<P>Size of not_defined.html: <!--#fsize file="not_defined.html"-->
+<!--#config sizefmt="bytes"-->
+<P>Size of /misc/friedrich.html: <!--#fsize virtual="/misc/friedrich.html"-->
+<P>Size of /misc/not_defined.html: <!--#fsize virtual="/misc/not_defined.html"-->
+
+<P><HR>
+
+<!-- ************* FLASTMOD ************* -->
+
+<P>Last modification of index.html: <!--#flastmod file="index.html"-->
+<P>Last modification of not_defined.html: <!--#flastmod file="not_defined.html"-->
+<P>Last modification of /misc/friedrich.html: <!--#flastmod virtual="/misc/friedrich.html"-->
+<P>Last modification of /misc/not_defined.html: <!--#flastmod virtual="/misc/not_defined.html"-->
+
+<!--#exec cmd="ls"-->
+<!--#exec cmd="printenv"-->
+<!--#exec cmd="sunemaja"-->
+
+<!--#exec cgi="/cgi-bin/printenv.sh"-->
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/dets_open/dummy.html b/lib/inets/test/httpd_test_data/server_root/htdocs/dets_open/dummy.html
new file mode 100644
index 0000000000..a6e8a35a04
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/dets_open/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/open/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/open/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/dummy.html b/lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/dummy.html
new file mode 100644
index 0000000000..016b04e540
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/secret/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/top_secret/index.html b/lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/top_secret/index.html
new file mode 100644
index 0000000000..34db3d5d1a
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/dets_secret/top_secret/index.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/top_secret/index.html (04-Feb-1998)</TITLE>
+<!-- Created by: Mattias Nilsson, 04-Feb-1998 -->
+</HEAD>
+<BODY>
+<H1>/secret/top_secret/index.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/echo.shtml b/lib/inets/test/httpd_test_data/server_root/htdocs/echo.shtml
new file mode 100644
index 0000000000..141db5be59
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/echo.shtml
@@ -0,0 +1,35 @@
+<HTML>
+<HEAD>
+<TITLE>/echo.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/echo.shtml</H1>
+
+<P>DOCUMENT_NAME: <!--#echo var="DOCUMENT_NAME"-->
+
+<P>DOCUMENT_URI: <!--#echo var="DOCUMENT_URI"-->
+
+<P>QUERY_STRING_UNESCAPED: <!--#echo var="QUERY_STRING_UNESCAPED"-->
+
+<P>DATE_LOCAL: <!--#echo var="DATE_LOCAL"-->
+
+<P>DATE_GMT: <!--#echo var="DATE_GMT"-->
+
+<P>LAST_MODIFIED: <!--#echo var="LAST_MODIFIED"-->
+
+<P>NOT_DEFINED: <!--#echo var="NOT_DEFINED"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/exec.shtml b/lib/inets/test/httpd_test_data/server_root/htdocs/exec.shtml
new file mode 100644
index 0000000000..97333da898
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/exec.shtml
@@ -0,0 +1,30 @@
+<HTML>
+<HEAD>
+<TITLE>/exec.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/exec.shtml</H1>
+<PRE>
+<!--#exec cmd="ls"-->
+<HR>
+<!--#exec cmd="printenv"-->
+<HR>
+<!--#exec cmd="sunemaja"-->
+<HR>
+<!--#exec cgi="/cgi-bin/printenv.sh"-->
+</PRE>
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/flastmod.shtml b/lib/inets/test/httpd_test_data/server_root/htdocs/flastmod.shtml
new file mode 100644
index 0000000000..d54c36fe50
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/flastmod.shtml
@@ -0,0 +1,29 @@
+<HTML>
+<HEAD>
+<TITLE>/flastmod.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/flastmod.shtml</H1>
+
+<P>Last modification of index.html: <!--#flastmod file="index.html"-->
+
+<P>Last modification of not_defined.html: <!--#flastmod file="not_defined.html"-->
+
+<P>Last modification of /misc/friedrich.html: <!--#flastmod virtual="/misc/friedrich.html"-->
+
+<P>Last modification of /misc/not_defined.html: <!--#flastmod virtual="/misc/not_defined.html"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/fsize.shtml b/lib/inets/test/httpd_test_data/server_root/htdocs/fsize.shtml
new file mode 100644
index 0000000000..570ee9cf6d
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/fsize.shtml
@@ -0,0 +1,29 @@
+<HTML>
+<HEAD>
+<TITLE>/fsize.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/fsize.shtml</H1>
+
+<P>Size of index.html: <!--#fsize file="index.html"-->
+
+<P>Size of not_defined.html: <!--#fsize file="not_defined.html"-->
+
+<P>Size of /misc/friedrich.html: <!--#fsize virtual="/misc/friedrich.html"-->
+
+<P>Size of /misc/not_defined.html: <!--#fsize virtual="/misc/not_defined.html"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/include.shtml b/lib/inets/test/httpd_test_data/server_root/htdocs/include.shtml
new file mode 100644
index 0000000000..529aad0437
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/include.shtml
@@ -0,0 +1,33 @@
+<HTML>
+<HEAD>
+<TITLE>/include.shtml</TITLE>
+</HEAD>
+<BODY>
+<H1>/include.shtml</H1>
+
+<P>Include /misc/friedrich.html:
+<!--#include virtual="/misc/friedrich.html"-->
+
+<P>Include /misc/not_defined.html:
+<!--#include virtual="/misc/not_defined.html"-->
+
+<P>Include misc/friedrich.html:
+<!--#include file="misc/friedrich.html"-->
+
+<P>Include not_defined.html:
+<!--#include file="not_defined.html"-->
+
+<P>[<A HREF="ssi.html">Back</A>]
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/index.html b/lib/inets/test/httpd_test_data/server_root/htdocs/index.html
new file mode 100644
index 0000000000..cfdc9f9ab7
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/index.html
@@ -0,0 +1,25 @@
+<HTML>
+<HEAD>
+<TITLE>/index.html</TITLE>
+</HEAD>
+<BODY>
+<H1>/index.html</H1>
+
+<STRONG>Server-Side Include (SSI) commands:</STRONG><BR>
+<A HREF="config.shtml">config</A><BR>
+<A HREF="echo.shtml">echo</A><BR>
+<A HREF="exec.shtml">exec</A><BR>
+<A HREF="flastmod.shtml">flastmod</A><BR>
+<A HREF="fsize.shtml">fsize</A><BR>
+<A HREF="include.shtml">include</A><BR>
+
+<BR>
+<BR>
+
+<STRONG>ESI callback:</STRING><BR>
+<A HREF="cgi-bin/erl/httpd_example/get">cgi-bin/erl/httpd_example/get</A><BR>
+<A HREF="cgi-bin/erl/httpd_example/yahoo">cgi-bin/erl/httpd_example/yahoo</A><BR>
+<A HREF="cgi-bin/erl/httpd_example/test1">cgi-bin/erl/httpd_example/test1</A><BR>
+
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/last_modified.html b/lib/inets/test/httpd_test_data/server_root/htdocs/last_modified.html
new file mode 100644
index 0000000000..65c1790813
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/last_modified.html
@@ -0,0 +1,22 @@
+<HTML>
+<HEAD>
+<TITLE>/last_modified.html</TITLE>
+</HEAD>
+<BODY>
+<H1>/last_modified.html</H1>
+
+<P>This document is only used for test of illegal last-modified date.</P>
+
+
+</BODY>
+</HTML>
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/misc/friedrich.html b/lib/inets/test/httpd_test_data/server_root/htdocs/misc/friedrich.html
new file mode 100644
index 0000000000..d7953d5df4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/misc/friedrich.html
@@ -0,0 +1,7 @@
+<P><CITE>
+Talking much about oneself can also be a means to conceal oneself.<BR>
+-- Friedrich Nietzsche
+</CITE>
+
+<P>Nested Include:
+<!--#include file="misc/oech.html"-->
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/misc/oech.html b/lib/inets/test/httpd_test_data/server_root/htdocs/misc/oech.html
new file mode 100644
index 0000000000..506064bf04
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/misc/oech.html
@@ -0,0 +1,4 @@
+<P><CITE>
+What excuses stand in your way? How can you eliminate them?<BR>
+-- Roger von Oech
+</CITE>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/misc/welcome.html b/lib/inets/test/httpd_test_data/server_root/htdocs/misc/welcome.html
new file mode 100644
index 0000000000..8c17451f91
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/misc/welcome.html
@@ -0,0 +1 @@
+<HTML></HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_open/dummy.html b/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_open/dummy.html
new file mode 100644
index 0000000000..a6e8a35a04
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_open/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/open/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/open/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/dummy.html b/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/dummy.html
new file mode 100644
index 0000000000..016b04e540
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/secret/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/top_secret/index.html b/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/top_secret/index.html
new file mode 100644
index 0000000000..2d17e8b596
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/mnesia_secret/top_secret/index.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+<TITLE>/mnesia_secret/top_secret/index.html (04-Feb-1998)</TITLE>
+<!-- Created by: Mattias Nilsson, 04-Feb-1998 -->
+</HEAD>
+<BODY>
+<H1>/mnesia_secret/top_secret/index.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/open/dummy.html b/lib/inets/test/httpd_test_data/server_root/htdocs/open/dummy.html
new file mode 100644
index 0000000000..a6e8a35a04
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/open/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/open/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/open/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/secret/dummy.html b/lib/inets/test/httpd_test_data/server_root/htdocs/secret/dummy.html
new file mode 100644
index 0000000000..016b04e540
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/secret/dummy.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/dummy.html (17-Apr-1997)</TITLE>
+<!-- Created by: Joakim Greben�, 17-Apr-1997 -->
+<!-- Changed by: Joakim Greben�, 17-Apr-1997 -->
+</HEAD>
+<BODY>
+<H1>/secret/dummy.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/htdocs/secret/top_secret/index.html b/lib/inets/test/httpd_test_data/server_root/htdocs/secret/top_secret/index.html
new file mode 100644
index 0000000000..34db3d5d1a
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/htdocs/secret/top_secret/index.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+<TITLE>/secret/top_secret/index.html (04-Feb-1998)</TITLE>
+<!-- Created by: Mattias Nilsson, 04-Feb-1998 -->
+</HEAD>
+<BODY>
+<H1>/secret/top_secret/index.html</H1>
+</BODY>
+</HTML>
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/README b/lib/inets/test/httpd_test_data/server_root/icons/README
new file mode 100644
index 0000000000..a1fc5a5a9c
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/README
@@ -0,0 +1,161 @@
+Public Domain Icons
+
+ These icons were originally made for Mosaic for X and have been
+ included in the NCSA httpd and Apache server distributions in the
+ past. They are in the public domain and may be freely included in any
+ application. The originals were done by Kevin Hughes ([email protected]).
+
+ Many thanks to Andy Polyakov for tuning the icon colors and adding a
+ few new images. If you'd like to contribute additions or ideas to
+ this set, please let me know.
+
+ The distribution site for these icons is at:
+
+ http://www.eit.com/goodies/www.icons/
+
+ Kevin Hughes
+ September 11, 1995
+
+
+Suggested Uses
+
+The following are a few suggestions, to serve as a starting point for ideas.
+Please feel free to tweak and rename the icons as you like.
+
+ a.gif
+ This might be used to represent PostScript or text layout
+ languages.
+
+ alert.black.gif, alert.red.gif
+ These can be used to highlight any important items, such as a
+ README file in a directory.
+
+ back.gif, forward.gif
+ These can be used as links to go to previous and next areas.
+
+ ball.gray.gif, ball.red.gif
+ These might be used as bullets.
+
+ binary.gif
+ This can be used to represent binary files.
+
+ binhex.gif
+ This can represent BinHex-encoded data.
+
+ blank.gif
+ This can be used as a placeholder or a spacing element.
+
+ bomb.gif
+ This can be used to repreesnt core files.
+
+ box1.gif, box2.gif
+ These icons can be used to represent generic 3D applications and
+ related files.
+
+ broken.gif
+ This can represent corrupted data.
+
+ burst.gif
+ This can call attention to new and important items.
+
+ c.gif
+ This might represent C source code.
+
+ comp.blue.gif, comp.red.gif
+ These little computer icons can stand for telnet or FTP
+ sessions.
+
+ compressed.gif
+ This may represent compressed data.
+
+ continued.gif
+ This can be a link to a continued listing of a directory.
+
+ down.gif, up.gif, left.gif, right.gif
+ These can be used to scroll up, down, left and right in a
+ listing or may be used to denote items in an outline.
+
+ dvi.gif
+ This can represent DVI files.
+
+ f.gif
+ This might represent FORTRAN or Forth source code.
+
+ folder.gif, folder.open.gif, folder.sec.gif
+ The folder can represent directories. There is also a version
+ that can represent secure directories or directories that cannot
+ be viewed.
+
+ generic.gif, generic.sec.gif, generic.red.gif
+ These can represent generic files, secure files, and important
+ files, respectively.
+
+ hand.right.gif, hand.up.gif
+ These can point out important items (pun intended).
+
+ image1.gif, image2.gif, image3.gif
+ These can represent image formats of various types.
+
+ index.gif
+ This might represent a WAIS index or search facility.
+
+ layout.gif
+ This might represent files and formats that contain graphics as
+ well as text layout, such as HTML and PDF files.
+
+ link.gif
+ This might represent files that are symbolic links.
+
+ movie.gif
+ This can represent various movie formats.
+
+ p.gif
+ This may stand for Perl or Python source code.
+
+ pie0.gif ... pie8.gif
+ These icons can be used in applications where a list of
+ documents is returned from a search. The little pie chart images
+ can denote how relevant the documents may be to your search
+ query.
+
+ patch.gif
+ This may stand for patches and diff files.
+
+ portal.gif
+ This might be a link to an online service or a 3D world.
+
+ ps.gif, quill.gif
+ These may represent PostScript files.
+
+ screw1.gif, screw2.gif
+ These may represent CAD or engineering data and formats.
+
+ script.gif
+ This can represent any of various interpreted languages, such as
+ Perl, python, TCL, and shell scripts, as well as server
+ configuration files.
+
+ sound1.gif, sound2.gif
+ These can represent sound files.
+
+ sphere1.gif, sphere2.gif
+ These can represent 3D worlds or rendering applications and
+ formats.
+
+ tex.gif
+ This can represent TeX files.
+
+ text.gif
+ This can represent generic (plain) text files.
+
+ transfer.gif
+ This can represent FTP transfers or uploads/downloads.
+
+ unknown.gif
+ This may represent a file of an unknown type.
+
+ uuencoded.gif
+ This can stand for uuencoded data.
+
+ world1.gif, world2.gif
+ These can represent 3D worlds or other 3D formats.
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/a.gif b/lib/inets/test/httpd_test_data/server_root/icons/a.gif
new file mode 100644
index 0000000000..bb23d971f4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/a.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/alert.black.gif b/lib/inets/test/httpd_test_data/server_root/icons/alert.black.gif
new file mode 100644
index 0000000000..eaecd2172a
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/alert.black.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/alert.red.gif b/lib/inets/test/httpd_test_data/server_root/icons/alert.red.gif
new file mode 100644
index 0000000000..a423894043
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/alert.red.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/apache_pb.gif b/lib/inets/test/httpd_test_data/server_root/icons/apache_pb.gif
new file mode 100644
index 0000000000..3a1c139fc4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/apache_pb.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/back.gif b/lib/inets/test/httpd_test_data/server_root/icons/back.gif
new file mode 100644
index 0000000000..a694ae1ec3
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/back.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/ball.gray.gif b/lib/inets/test/httpd_test_data/server_root/icons/ball.gray.gif
new file mode 100644
index 0000000000..eb84268c4c
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/ball.gray.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/ball.red.gif b/lib/inets/test/httpd_test_data/server_root/icons/ball.red.gif
new file mode 100644
index 0000000000..a8425cb574
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/ball.red.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/binary.gif b/lib/inets/test/httpd_test_data/server_root/icons/binary.gif
new file mode 100644
index 0000000000..9a15cbae04
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/binary.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/binhex.gif b/lib/inets/test/httpd_test_data/server_root/icons/binhex.gif
new file mode 100644
index 0000000000..62d0363108
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/binhex.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/blank.gif b/lib/inets/test/httpd_test_data/server_root/icons/blank.gif
new file mode 100644
index 0000000000..0ccf01e198
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/blank.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/bomb.gif b/lib/inets/test/httpd_test_data/server_root/icons/bomb.gif
new file mode 100644
index 0000000000..270fdb1c06
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/bomb.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/box1.gif b/lib/inets/test/httpd_test_data/server_root/icons/box1.gif
new file mode 100644
index 0000000000..65dcd002ea
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/box1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/box2.gif b/lib/inets/test/httpd_test_data/server_root/icons/box2.gif
new file mode 100644
index 0000000000..c43bc4faec
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/box2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/broken.gif b/lib/inets/test/httpd_test_data/server_root/icons/broken.gif
new file mode 100644
index 0000000000..9f8cbe9f76
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/broken.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/burst.gif b/lib/inets/test/httpd_test_data/server_root/icons/burst.gif
new file mode 100644
index 0000000000..fbdcf575f7
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/burst.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button1.gif b/lib/inets/test/httpd_test_data/server_root/icons/button1.gif
new file mode 100644
index 0000000000..eb97cb7333
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button10.gif b/lib/inets/test/httpd_test_data/server_root/icons/button10.gif
new file mode 100644
index 0000000000..fe0c97998c
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button10.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button2.gif b/lib/inets/test/httpd_test_data/server_root/icons/button2.gif
new file mode 100644
index 0000000000..7698455bf9
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button3.gif b/lib/inets/test/httpd_test_data/server_root/icons/button3.gif
new file mode 100644
index 0000000000..a8b8319232
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button3.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button4.gif b/lib/inets/test/httpd_test_data/server_root/icons/button4.gif
new file mode 100644
index 0000000000..0fd15a0d7f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button4.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button5.gif b/lib/inets/test/httpd_test_data/server_root/icons/button5.gif
new file mode 100644
index 0000000000..64241e5c5d
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button5.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button6.gif b/lib/inets/test/httpd_test_data/server_root/icons/button6.gif
new file mode 100644
index 0000000000..867cfd1212
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button6.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button7.gif b/lib/inets/test/httpd_test_data/server_root/icons/button7.gif
new file mode 100644
index 0000000000..b3f5fb248f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button7.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button8.gif b/lib/inets/test/httpd_test_data/server_root/icons/button8.gif
new file mode 100644
index 0000000000..7a308be8f6
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button8.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/button9.gif b/lib/inets/test/httpd_test_data/server_root/icons/button9.gif
new file mode 100644
index 0000000000..9acba576c0
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/button9.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/buttonl.gif b/lib/inets/test/httpd_test_data/server_root/icons/buttonl.gif
new file mode 100644
index 0000000000..3883088e7a
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/buttonl.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/buttonr.gif b/lib/inets/test/httpd_test_data/server_root/icons/buttonr.gif
new file mode 100644
index 0000000000..c4dc3887db
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/buttonr.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/c.gif b/lib/inets/test/httpd_test_data/server_root/icons/c.gif
new file mode 100644
index 0000000000..7555b6c164
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/c.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/comp.blue.gif b/lib/inets/test/httpd_test_data/server_root/icons/comp.blue.gif
new file mode 100644
index 0000000000..f8d76a8c23
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/comp.blue.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/comp.gray.gif b/lib/inets/test/httpd_test_data/server_root/icons/comp.gray.gif
new file mode 100644
index 0000000000..7664cd0364
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/comp.gray.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/compressed.gif b/lib/inets/test/httpd_test_data/server_root/icons/compressed.gif
new file mode 100644
index 0000000000..39e732739f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/compressed.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/continued.gif b/lib/inets/test/httpd_test_data/server_root/icons/continued.gif
new file mode 100644
index 0000000000..b0ffb7e0cc
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/continued.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/dir.gif b/lib/inets/test/httpd_test_data/server_root/icons/dir.gif
new file mode 100644
index 0000000000..48264601ae
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/dir.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/down.gif b/lib/inets/test/httpd_test_data/server_root/icons/down.gif
new file mode 100644
index 0000000000..a354c871cd
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/down.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/dvi.gif b/lib/inets/test/httpd_test_data/server_root/icons/dvi.gif
new file mode 100644
index 0000000000..791be33105
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/dvi.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/f.gif b/lib/inets/test/httpd_test_data/server_root/icons/f.gif
new file mode 100644
index 0000000000..fbe353c282
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/f.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/folder.gif b/lib/inets/test/httpd_test_data/server_root/icons/folder.gif
new file mode 100644
index 0000000000..48264601ae
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/folder.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/folder.open.gif b/lib/inets/test/httpd_test_data/server_root/icons/folder.open.gif
new file mode 100644
index 0000000000..30979cb528
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/folder.open.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/folder.sec.gif b/lib/inets/test/httpd_test_data/server_root/icons/folder.sec.gif
new file mode 100644
index 0000000000..75332d9e59
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/folder.sec.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/forward.gif b/lib/inets/test/httpd_test_data/server_root/icons/forward.gif
new file mode 100644
index 0000000000..b2959b4c85
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/forward.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/generic.gif b/lib/inets/test/httpd_test_data/server_root/icons/generic.gif
new file mode 100644
index 0000000000..de60b2940f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/generic.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/generic.red.gif b/lib/inets/test/httpd_test_data/server_root/icons/generic.red.gif
new file mode 100644
index 0000000000..94743981d9
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/generic.red.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/generic.sec.gif b/lib/inets/test/httpd_test_data/server_root/icons/generic.sec.gif
new file mode 100644
index 0000000000..88d5240c3c
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/generic.sec.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/hand.right.gif b/lib/inets/test/httpd_test_data/server_root/icons/hand.right.gif
new file mode 100644
index 0000000000..5cdbc7206d
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/hand.right.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/hand.up.gif b/lib/inets/test/httpd_test_data/server_root/icons/hand.up.gif
new file mode 100644
index 0000000000..85a5d68317
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/hand.up.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/htdig.gif b/lib/inets/test/httpd_test_data/server_root/icons/htdig.gif
new file mode 100644
index 0000000000..35443fb63a
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/htdig.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/icon.sheet.gif b/lib/inets/test/httpd_test_data/server_root/icons/icon.sheet.gif
new file mode 100644
index 0000000000..ad1686e448
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/icon.sheet.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/image1.gif b/lib/inets/test/httpd_test_data/server_root/icons/image1.gif
new file mode 100644
index 0000000000..01e442bfa9
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/image1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/image2.gif b/lib/inets/test/httpd_test_data/server_root/icons/image2.gif
new file mode 100644
index 0000000000..751faeea36
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/image2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/image3.gif b/lib/inets/test/httpd_test_data/server_root/icons/image3.gif
new file mode 100644
index 0000000000..4f30484ff6
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/image3.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/index.gif b/lib/inets/test/httpd_test_data/server_root/icons/index.gif
new file mode 100644
index 0000000000..162478fb3a
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/index.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/layout.gif b/lib/inets/test/httpd_test_data/server_root/icons/layout.gif
new file mode 100644
index 0000000000..c96338a152
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/layout.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/left.gif b/lib/inets/test/httpd_test_data/server_root/icons/left.gif
new file mode 100644
index 0000000000..279e6710d4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/left.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/link.gif b/lib/inets/test/httpd_test_data/server_root/icons/link.gif
new file mode 100644
index 0000000000..c5b6889a76
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/link.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/movie.gif b/lib/inets/test/httpd_test_data/server_root/icons/movie.gif
new file mode 100644
index 0000000000..0035183774
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/movie.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/p.gif b/lib/inets/test/httpd_test_data/server_root/icons/p.gif
new file mode 100644
index 0000000000..7b917b4e91
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/p.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/patch.gif b/lib/inets/test/httpd_test_data/server_root/icons/patch.gif
new file mode 100644
index 0000000000..39bc90e795
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/patch.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pdf.gif b/lib/inets/test/httpd_test_data/server_root/icons/pdf.gif
new file mode 100644
index 0000000000..c88fd777c4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pdf.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie0.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie0.gif
new file mode 100644
index 0000000000..6f7a0ae7a7
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie0.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie1.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie1.gif
new file mode 100644
index 0000000000..03aa6be71e
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie2.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie2.gif
new file mode 100644
index 0000000000..b04c5e0908
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie3.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie3.gif
new file mode 100644
index 0000000000..4db9d023ed
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie3.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie4.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie4.gif
new file mode 100644
index 0000000000..93471fdd88
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie4.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie5.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie5.gif
new file mode 100644
index 0000000000..57aee93f07
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie5.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie6.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie6.gif
new file mode 100644
index 0000000000..0dc327b569
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie6.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie7.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie7.gif
new file mode 100644
index 0000000000..8661337f06
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie7.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/pie8.gif b/lib/inets/test/httpd_test_data/server_root/icons/pie8.gif
new file mode 100644
index 0000000000..59ddb34ce0
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/pie8.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/portal.gif b/lib/inets/test/httpd_test_data/server_root/icons/portal.gif
new file mode 100644
index 0000000000..0e6e506e00
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/portal.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/poweredby.gif b/lib/inets/test/httpd_test_data/server_root/icons/poweredby.gif
new file mode 100644
index 0000000000..d324ab80ea
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/poweredby.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/ps.gif b/lib/inets/test/httpd_test_data/server_root/icons/ps.gif
new file mode 100644
index 0000000000..0f565bc1db
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/ps.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/quill.gif b/lib/inets/test/httpd_test_data/server_root/icons/quill.gif
new file mode 100644
index 0000000000..818a5cdc7e
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/quill.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/right.gif b/lib/inets/test/httpd_test_data/server_root/icons/right.gif
new file mode 100644
index 0000000000..b256e5f75f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/right.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/screw1.gif b/lib/inets/test/httpd_test_data/server_root/icons/screw1.gif
new file mode 100644
index 0000000000..af6ba2b097
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/screw1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/screw2.gif b/lib/inets/test/httpd_test_data/server_root/icons/screw2.gif
new file mode 100644
index 0000000000..06dccb3e44
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/screw2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/script.gif b/lib/inets/test/httpd_test_data/server_root/icons/script.gif
new file mode 100644
index 0000000000..d8a853bc58
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/script.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/sound1.gif b/lib/inets/test/httpd_test_data/server_root/icons/sound1.gif
new file mode 100644
index 0000000000..8efb49f55d
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/sound1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/sound2.gif b/lib/inets/test/httpd_test_data/server_root/icons/sound2.gif
new file mode 100644
index 0000000000..48e6a7fb2f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/sound2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/sphere1.gif b/lib/inets/test/httpd_test_data/server_root/icons/sphere1.gif
new file mode 100644
index 0000000000..7067070da2
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/sphere1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/sphere2.gif b/lib/inets/test/httpd_test_data/server_root/icons/sphere2.gif
new file mode 100644
index 0000000000..a9e462a377
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/sphere2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/star.gif b/lib/inets/test/httpd_test_data/server_root/icons/star.gif
new file mode 100644
index 0000000000..4cfe0a5e0f
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/star.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/star_blank.gif b/lib/inets/test/httpd_test_data/server_root/icons/star_blank.gif
new file mode 100644
index 0000000000..a0c83cb85b
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/star_blank.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/tar.gif b/lib/inets/test/httpd_test_data/server_root/icons/tar.gif
new file mode 100644
index 0000000000..617e779efa
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/tar.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/tex.gif b/lib/inets/test/httpd_test_data/server_root/icons/tex.gif
new file mode 100644
index 0000000000..45e43233b8
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/tex.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/text.gif b/lib/inets/test/httpd_test_data/server_root/icons/text.gif
new file mode 100644
index 0000000000..4c623909fb
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/text.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/transfer.gif b/lib/inets/test/httpd_test_data/server_root/icons/transfer.gif
new file mode 100644
index 0000000000..33697dbb66
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/transfer.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/unknown.gif b/lib/inets/test/httpd_test_data/server_root/icons/unknown.gif
new file mode 100644
index 0000000000..32b1ea23fb
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/unknown.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/up.gif b/lib/inets/test/httpd_test_data/server_root/icons/up.gif
new file mode 100644
index 0000000000..6d6d6d1ebf
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/up.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/uu.gif b/lib/inets/test/httpd_test_data/server_root/icons/uu.gif
new file mode 100644
index 0000000000..4387d529f6
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/uu.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/uuencoded.gif b/lib/inets/test/httpd_test_data/server_root/icons/uuencoded.gif
new file mode 100644
index 0000000000..4387d529f6
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/uuencoded.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/world1.gif b/lib/inets/test/httpd_test_data/server_root/icons/world1.gif
new file mode 100644
index 0000000000..05b4ec2058
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/world1.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/icons/world2.gif b/lib/inets/test/httpd_test_data/server_root/icons/world2.gif
new file mode 100644
index 0000000000..e3203f7a88
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/icons/world2.gif
Binary files differ
diff --git a/lib/inets/test/httpd_test_data/server_root/logs/Dummy_File_Needed_By_WinZip b/lib/inets/test/httpd_test_data/server_root/logs/Dummy_File_Needed_By_WinZip
new file mode 100644
index 0000000000..8d1c8b69c3
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/logs/Dummy_File_Needed_By_WinZip
@@ -0,0 +1 @@
+
diff --git a/lib/inets/test/httpd_test_data/server_root/ssl/ssl_client.pem b/lib/inets/test/httpd_test_data/server_root/ssl/ssl_client.pem
new file mode 100644
index 0000000000..8221139eb4
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/ssl/ssl_client.pem
@@ -0,0 +1,22 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAL6Ym/bgUvhhnPkw08sggGg8Tnp759ThGMEjkmDzhuJ3w3PfnF65
+mgHcgunku4G6LxAQfEUougJWf9Phmjj3oRUCAwEAAQJBAKMjvVvzZxFzfAlP4flc
+OI0AEayFokp04dtvtzuFN09f+aBo2dP18xHmKLCZvxrBOaRAROoQYscALiIVpN07
+GAECIQDfi+sSfAFaDlT3vzpL3xE5UEH6IzY8jWpaZfM1QaToJQIhANpEF50H4wGO
+8Sbh7dUutNd+s+NYUjsMySW2DjLKMsoxAiEAzzb2ftrdsempD0F+O0gZwiPIFKLB
+Kp33YLYyHEKuJtUCIDGi+pvDh2R7VWw6RRQOIyI+tjolg83aAoSI+oGiahqBAiEA
+xzmNNajwoaokvWvlaz0na8rhxu45grOvDrflBT9XvSQ=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICDDCCAbYCAQAwDQYJKoZIhvcNAQEEBQAwgZAxCzAJBgNVBAYTAlNFMRIwEAYD
+VQQIEwlTdG9ja2hvbG0xDzANBgNVBAcTBkFsdnNqbzEMMAoGA1UEChMDRVRYMQ4w
+DAYDVQQLEwVETi9TUDEXMBUGA1UEAxMOSm9ha2ltIEdyZWJlbm8xJTAjBgkqhkiG
+9w0BCQEWFmpvY2tlQGVyaXguZXJpY3Nzb24uc2UwHhcNOTcwNzE1MTUzNDM2WhcN
+MDMwMjIyMTUzNDM2WjCBkDELMAkGA1UEBhMCU0UxEjAQBgNVBAgTCVN0b2NraG9s
+bTEPMA0GA1UEBxMGQWx2c2pvMQwwCgYDVQQKEwNFVFgxDjAMBgNVBAsTBUROL1NQ
+MRcwFQYDVQQDEw5Kb2FraW0gR3JlYmVubzElMCMGCSqGSIb3DQEJARYWam9ja2VA
+ZXJpeC5lcmljc3Nvbi5zZTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+mJv24FL4
+YZz5MNPLIIBoPE56e+fU4RjBI5Jg84bid8Nz35xeuZoB3ILp5LuBui8QEHxFKLoC
+Vn/T4Zo496EVAgMBAAEwDQYJKoZIhvcNAQEEBQADQQBYxQVfTydyZCE0UXvZd7Ei
+josNsAaWJk9fFIJaG9uyXCEfg2dVgoT2eBk3D9DI+7OB+78isM5CVlFbL7hilvP8
+-----END CERTIFICATE-----
diff --git a/lib/inets/test/httpd_test_data/server_root/ssl/ssl_server.pem b/lib/inets/test/httpd_test_data/server_root/ssl/ssl_server.pem
new file mode 100644
index 0000000000..fe739c15f7
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/ssl/ssl_server.pem
@@ -0,0 +1,22 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBAL9Bozj3BIjL5Cy8b3rjMT2kPZRychX4wz9bHoIIiKnKo1xXHYjw
+g3N9zWM1f1ZzMADwVry1uAInA8q09+7hL20CAwEAAQJACwu2ao7RozjrV64WXimK
+6X131P/7GMvCMwGHNIlbozqoOqmZcYrbKaF61l+XuwA2QvTo3ywW1Ivxcyr6TeAr
+PQIhAOX+WXT6yiqqwjt08kjBCJyMgfZtdAO6pc/6pKjNWiZfAiEA1OH1iPW/OQe5
+tlQXpiRVdLyneNsPygPRJc4Bdwu3hbMCIQDbI5pA56QxOzqOREOGJsb5wrciAfAE
+jZbnr72sSN2YqQIgAWFpvzagw9Tp/mWzNY+cwkIK7/yzsIKv04fveH8p9IMCIQCr
+td4IiukeUwXmPSvYM4uCE/+J89wEL9qU8Mlc3gDLXA==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIICDDCCAbYCAQAwDQYJKoZIhvcNAQEEBQAwgZAxCzAJBgNVBAYTAlNFMRIwEAYD
+VQQIEwlTdG9ja2hvbG0xDzANBgNVBAcTBkFsdnNqbzEMMAoGA1UEChMDRVRYMQ4w
+DAYDVQQLEwVETi9TUDEXMBUGA1UEAxMOSm9ha2ltIEdyZWJlbm8xJTAjBgkqhkiG
+9w0BCQEWFmpvY2tlQGVyaXguZXJpY3Nzb24uc2UwHhcNOTcwNzE1MTUzMzQxWhcN
+MDMwMjIyMTUzMzQxWjCBkDELMAkGA1UEBhMCU0UxEjAQBgNVBAgTCVN0b2NraG9s
+bTEPMA0GA1UEBxMGQWx2c2pvMQwwCgYDVQQKEwNFVFgxDjAMBgNVBAsTBUROL1NQ
+MRcwFQYDVQQDEw5Kb2FraW0gR3JlYmVubzElMCMGCSqGSIb3DQEJARYWam9ja2VA
+ZXJpeC5lcmljc3Nvbi5zZTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC/QaM49wSI
+y+QsvG964zE9pD2UcnIV+MM/Wx6CCIipyqNcVx2I8INzfc1jNX9WczAA8Fa8tbgC
+JwPKtPfu4S9tAgMBAAEwDQYJKoZIhvcNAQEEBQADQQAmXDY1CyJjzvQZX442kkHG
+ic9QFY1UuVfzokzNMwlHYl1Qx9zaodx0cJCrcH5GF9O9LJbhhV77LzoxT1Q5wZp5
+-----END CERTIFICATE-----
diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl
new file mode 100644
index 0000000000..6abee5be2c
--- /dev/null
+++ b/lib/inets/test/httpd_test_lib.erl
@@ -0,0 +1,332 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(httpd_test_lib).
+
+-include("inets_test_lib.hrl").
+
+%% Poll functions
+-export([verify_request/6, verify_request/7, is_expect/1]).
+
+-record(state, {request, % string()
+ socket, % socket()
+ status_line, % {Version, StatusCode, ReasonPharse}
+ headers, % #http_response_h{}
+ body, % binary()
+ mfa = {httpc_response, parse, [nolimit, false]},
+ canceled = [], % [RequestId]
+ max_header_size = nolimit, % nolimit | integer()
+ max_body_size = nolimit, % nolimit | integer()
+ print = false
+ }).
+
+%%% Part of http.hrl - Temporary solution %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Response headers
+-record(http_response_h,{
+%%% --- Standard "General" headers
+ 'cache-control',
+ connection,
+ date,
+ pragma,
+ trailer,
+ 'transfer-encoding',
+ upgrade,
+ via,
+ warning,
+%%% --- Standard "Response" headers
+ 'accept-ranges',
+ age,
+ etag,
+ location,
+ 'proxy-authenticate',
+ 'retry-after',
+ server,
+ vary,
+ 'www-authenticate',
+%%% --- Standard "Entity" headers
+ allow,
+ 'content-encoding',
+ 'content-language',
+ 'content-length' = "0",
+ 'content-location',
+ 'content-md5',
+ 'content-range',
+ 'content-type',
+ expires,
+ 'last-modified',
+ other=[] % list() - Key/Value list with other headers
+ }).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%--------------------------------------------------------------------
+%% API
+%%------------------------------------------------------------------
+verify_request(SocketType, Host, Port, Node, RequestStr, Options) ->
+ verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000).
+verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) ->
+ {ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port),
+ inets_test_lib:send(SocketType, Socket, RequestStr),
+
+ State = case inets_regexp:match(RequestStr, "printenv") of
+ nomatch ->
+ #state{};
+ _ ->
+ #state{print = true}
+ end,
+
+ case request(State#state{request = RequestStr, socket = Socket}, TimeOut) of
+ {error, Reson} ->
+ {error, Reson};
+ NewState ->
+ ValidateResult = validate(RequestStr, NewState, Options,
+ Node, Port),
+ inets_test_lib:close(SocketType, Socket),
+ ValidateResult
+ end.
+
+request(#state{mfa = {Module, Function, Args},
+ request = RequestStr, socket = Socket} = State, TimeOut) ->
+ HeadRequest = lists:sublist(RequestStr, 1, 4),
+ receive
+ {tcp, Socket, Data} ->
+ print(tcp, Data, State),
+ case Module:Function([Data | Args]) of
+ {ok, Parsed} ->
+ handle_http_msg(Parsed, State);
+ {_, whole_body, _} when HeadRequest == "HEAD" ->
+ State#state{body = <<>>};
+ NewMFA ->
+ request(State#state{mfa = NewMFA}, TimeOut)
+ end;
+ {tcp_closed, Socket} when Function == whole_body ->
+ print(tcp, "closed", State),
+ State#state{body = hd(Args)};
+ {tcp_closed, Socket} ->
+ test_server:fail(connection_closed);
+ {tcp_error, Socket, Reason} ->
+ test_server:fail({tcp_error, Reason});
+ {ssl, Socket, Data} ->
+ print(ssl, Data, State),
+ case Module:Function([Data | Args]) of
+ {ok, Parsed} ->
+ handle_http_msg(Parsed, State);
+ {_, whole_body, _} when HeadRequest == "HEAD" ->
+ State#state{body = <<>>};
+ NewMFA ->
+ request(State#state{mfa = NewMFA}, TimeOut)
+ end;
+ {ssl_closed, Socket} when Function == whole_body ->
+ print(ssl, "closed", State),
+ State#state{body = hd(Args)};
+ {ssl_closed, Socket} ->
+ test_server:fail(connection_closed);
+ {ssl_error, Socket, Reason} ->
+ test_server:fail({ssl_error, Reason})
+ after TimeOut ->
+ test_server:fail(connection_timed_out)
+ end.
+
+handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body},
+ State = #state{request = RequestStr}) ->
+ case is_expect(RequestStr) of
+ true ->
+ State#state{status_line = {Version,
+ StatusCode,
+ ReasonPharse},
+ headers = Headers};
+ false ->
+ handle_http_body(Body,
+ State#state{status_line = {Version,
+ StatusCode,
+ ReasonPharse},
+ headers = Headers})
+ end;
+
+handle_http_msg({ChunkedHeaders, Body},
+ State = #state{headers = Headers}) ->
+ NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
+ State#state{headers = NewHeaders, body = Body};
+
+handle_http_msg(Body, State) ->
+ State#state{body = Body}.
+
+handle_http_body(<<>>, State = #state{request = "HEAD" ++ _}) ->
+ State#state{body = <<>>};
+
+handle_http_body(Body, State = #state{headers = Headers,
+ max_body_size = MaxBodySize}) ->
+ case Headers#http_response_h.'transfer-encoding' of
+ "chunked" ->
+ case http_chunk:decode(Body, State#state.max_body_size,
+ State#state.max_header_size) of
+ {Module, Function, Args} ->
+ request(State#state{mfa = {Module, Function, Args}},
+ 30000);
+ {ok, {ChunkedHeaders, NewBody}} ->
+ NewHeaders = http_chunk:handle_headers(Headers,
+ ChunkedHeaders),
+ State#state{headers = NewHeaders, body = NewBody}
+ end;
+ _ ->
+ Length =
+ list_to_integer(Headers#http_response_h.'content-length'),
+ case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
+ true ->
+ case httpc_response:whole_body(Body, Length) of
+ {ok, NewBody} ->
+ State#state{body = NewBody};
+ MFA ->
+ request(State#state{mfa = MFA}, 5000)
+ end;
+ false ->
+ test_server:fail(body_too_big)
+ end
+ end.
+
+validate(RequestStr, #state{status_line = {Version, StatusCode, _},
+ headers = Headers,
+ body = Body}, Options, N, P) ->
+
+ %io:format("Status~p: H:~p B:~p~n", [StatusCode, Headers, Body]),
+ check_version(Version, Options),
+ case lists:keysearch(statuscode, 1, Options) of
+ {value, _} ->
+ check_status_code(StatusCode, Options, Options);
+ _ ->
+ ok
+ end,
+ do_validate(http_response:header_list(Headers), Options, N, P),
+ check_body(RequestStr, StatusCode,
+ Headers#http_response_h.'content-type',
+ list_to_integer(Headers#http_response_h.'content-length'),
+ Body).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%------------------------------------------------------------------
+check_version(Version, Options) ->
+ case lists:keysearch(version, 1, Options) of
+ {value, {version, Version}} ->
+ ok;
+ {value, {version, Ver}} ->
+ test_server:fail({wrong_version, [{got, Version},
+ {expected, Ver}]});
+ _ ->
+ case Version of
+ "HTTP/1.1" ->
+ ok;
+ _ ->
+ test_server:fail({wrong_version, [{got, Version},
+ {expected, "HTTP/1.1"}]})
+ end
+ end.
+
+check_status_code(StatusCode, [], Options) ->
+ test_server:fail({wrong_status_code, [{got, StatusCode},
+ {expected, Options}]});
+check_status_code(StatusCode, Current = [_ | Rest], Options) ->
+ case lists:keysearch(statuscode, 1, Current) of
+ {value, {statuscode, StatusCode}} ->
+ ok;
+ {value, {statuscode, _OtherStatus}} ->
+ check_status_code(StatusCode, Rest, Options);
+ false ->
+ test_server:fail({wrong_status_code, [{got, StatusCode},
+ {expected, Options}]})
+ end.
+
+do_validate(_, [], _, _) ->
+ ok;
+do_validate(Header, [{statuscode, _Code} | Rest], N, P) ->
+ do_validate(Header, Rest, N, P);
+do_validate(Header, [{header, HeaderField}|Rest], N, P) ->
+ LowerHeaderField = http_util:to_lower(HeaderField),
+ case lists:keysearch(LowerHeaderField, 1, Header) of
+ {value, {LowerHeaderField, _Value}} ->
+ ok;
+ false ->
+ test_server:fail({missing_header_field, LowerHeaderField, Header});
+ _ ->
+ test_server:fail({missing_header_field, LowerHeaderField, Header})
+ end,
+ do_validate(Header, Rest, N, P);
+do_validate(Header, [{header, HeaderField, Value}|Rest],N,P) ->
+ LowerHeaderField = http_util:to_lower(HeaderField),
+ case lists:keysearch(LowerHeaderField, 1, Header) of
+ {value, {LowerHeaderField, Value}} ->
+ ok;
+ false ->
+ test_server:fail({wrong_header_field_value, LowerHeaderField,
+ Header});
+ _ ->
+ test_server:fail({wrong_header_field_value, LowerHeaderField,
+ Header})
+ end,
+ do_validate(Header, Rest, N, P);
+do_validate(Header,[{no_last_modified,HeaderField}|Rest],N,P) ->
+% io:format("Header: ~p~nHeaderField: ~p~n",[Header,HeaderField]),
+ case lists:keysearch(HeaderField,1,Header) of
+ {value,_} ->
+ test_server:fail({wrong_header_field_value, HeaderField,
+ Header});
+ _ ->
+ ok
+ end,
+ do_validate(Header, Rest, N, P);
+do_validate(Header, [_Unknown | Rest], N, P) ->
+ do_validate(Header, Rest, N, P).
+
+is_expect(RequestStr) ->
+
+ case inets_regexp:match(RequestStr, "xpect:100-continue") of
+ {match, _, _}->
+ true;
+ _ ->
+ false
+ end.
+
+%% OTP-5775, content-length
+check_body("GET /cgi-bin/erl/httpd_example:get_bin HTTP/1.0\r\n\r\n", 200, "text/html", Length, _Body) when Length /= 274->
+ test_server:fail(content_length_error);
+check_body("GET /cgi-bin/cgi_echo HTTP/1.0\r\n\r\n", 200, "text/plain",
+ _, Body) ->
+ case size(Body) of
+ 100 ->
+ ok;
+ _ ->
+ test_server:fail(content_length_error)
+ end;
+
+check_body(RequestStr, 200, "text/html", _, Body) ->
+ HeadRequest = lists:sublist(RequestStr, 1, 3),
+ case HeadRequest of
+ "GET" ->
+ inets_test_lib:check_body(binary_to_list(Body));
+ _ ->
+ ok
+ end;
+
+check_body(_, _, _, _,_) ->
+ ok.
+
+print(Proto, Data, #state{print = true}) ->
+ test_server:format("Received ~p: ~p~n", [Proto, Data]);
+print(_, _, #state{print = false}) ->
+ ok.
+
diff --git a/lib/inets/test/httpd_time_test.erl b/lib/inets/test/httpd_time_test.erl
new file mode 100644
index 0000000000..7d6aa08542
--- /dev/null
+++ b/lib/inets/test/httpd_time_test.erl
@@ -0,0 +1,500 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(httpd_time_test).
+
+-export([t/3, t1/2, t2/2]).
+
+-export([do/1, do/2, do/3, do/4, do/5]).
+
+-export([main/5, poller_main/4, poller_loop/4]).
+
+-include("inets_test_lib.hrl").
+
+-record(stat, {pid, time = undefined, count = undefined, res}).
+
+
+%%% -----------------------------------------------------------------
+%%% Test suite interface
+%%%
+
+t1(Host, Port) ->
+ t(ip_comm, Host, Port).
+
+
+t2(Host, Port) ->
+ t(ssl, Host, Port).
+
+
+t(SocketType, Host, Port) ->
+ %% put(dbg,true),
+ main(1, SocketType, Host, Port, 60000).
+
+
+
+%%% -----------------------------------------------------------------
+%%% Public interface when running the time test manually...
+%%%
+
+do(Port) ->
+ do(ip_comm, hostname(), Port).
+
+do(Port, Time) when is_integer(Port) andalso is_integer(Time) ->
+ do(ip_comm, hostname(), Port, Time);
+
+do(Host, Port) ->
+ do(ip_comm, Host, Port).
+
+do(Host, Port, Time) when is_integer(Port) andalso is_integer(Time) ->
+ do(1, ip_comm, Host, Port, Time);
+
+do(SocketType, Host, Port) when is_integer(Port) ->
+ do(1, SocketType, Host, Port, 60000).
+
+do(N, SocketType, Host, Port) when is_integer(N) andalso is_integer(Port) ->
+ do(N, SocketType, Host, Port, 60000);
+
+do(SocketType, Host, Port, Time)
+ when is_integer(Port) andalso is_integer(Time) ->
+ do(1, SocketType, Host, Port, Time).
+
+do(N, SocketType, Host, Port, Time)
+ when is_integer(N) andalso is_integer(Port) andalso is_integer(Time) ->
+ do_it(N, SocketType, Host, Port, Time).
+
+do_it(N, SocketType, Host, Port, Time) ->
+ d("do_it -> entry with"
+ "~n N: ~p"
+ "~n SocketType: ~p"
+ "~n Host: ~p"
+ "~n Port: ~p"
+ "~n Time: ~p", [N, SocketType, Host, Port, Time]),
+ proc_lib:spawn(?MODULE, main, [N, SocketType, Host, Port, Time]).
+
+
+
+%%% -----------------------------------------------------------------
+%%% Controller (main) process
+%%%
+
+main(N, SocketType, Host, Port, Time)
+ when is_integer(N) andalso
+ is_atom(SocketType) andalso
+ is_integer(Port) andalso
+ is_integer(Time) ->
+ process_flag(trap_exit,true),
+ put(sname,ctrl),
+ %% put(dbg,true),
+ d("main -> entry"),
+ Pollers = start_pollers(N, [self(), SocketType, Host, Port]),
+ d("main -> Pollers: ~p", [Pollers]),
+ loop(Pollers, Time).
+
+loop(Pollers, Timeout) ->
+ d("loop -> entry when"
+ "~n Timeout: ~p", [Timeout]),
+ Start = t(),
+ receive
+ {'EXIT', Pid, {poller_stat_failure, Time, Reason}} ->
+ case is_poller(Pid, Pollers) of
+ true ->
+ error_msg("received unexpected exit from poller ~p~n"
+ "befor completion of test "
+ "(after ~p micro sec):~n"
+ "~p~n", [Pid,Time,Reason]),
+ exit({fail, {poller_exit, Pid, Reason}});
+ false ->
+ error_msg("received unexpected ~p from ~p"
+ "befor completion of test", [Reason, Pid]),
+ loop(Pollers, to(Timeout, Start))
+ end;
+
+ {poller_stat_failure, Pid, {Time, Reason}} ->
+ error_msg("received stat failure ~p from poller ~p after ~p "
+ "befor completion of test", [Reason, Pid, Time]),
+ exit({fail, {poller_failure, Pid, Reason}});
+
+ {poller_stat_failure, Pid, Reason} ->
+ error_msg("received stat failure ~p from poller ~p "
+ "befor completion of test", [Reason, Pid]),
+ exit({fail, {poller_failure, Pid, Reason}});
+
+ Any ->
+ error_msg("received unexpected message befor completion of test: "
+ "~n ~p", [Any]),
+ exit({fail, Any})
+
+ after Timeout ->
+ d("loop -> timeout: stop pollers"),
+ stop_pollers(Pollers),
+ d("loop -> collect poller statistics"),
+ Stats = collect_poller_stat(Pollers, []),
+ d("loop -> Stats: ~p", [Stats]),
+ display_poller_stat(Stats, Timeout),
+ ok
+ end.
+
+collect_poller_stat([], PollersStat) ->
+ PollersStat;
+collect_poller_stat(Pollers, PollersStat) ->
+ d("collect_poller_stat -> entry with"
+ "~n Pollers: ~p"
+ "~n PollersStat: ~p", [Pollers, PollersStat]),
+ receive
+ {poller_statistics, Poller, {Time, Count}} ->
+ d("collect_poller_stat -> got statistics from ~p", [Poller]),
+ case lists:keysearch(Poller, 2, Pollers) of
+ {value, PollerStat} ->
+ d("collect_poller_stat -> current statistic record: ~p",
+ [PollerStat]),
+ P = lists:keydelete(Poller, 2, Pollers),
+ d("collect_poller_stat -> P: ~p", [P]),
+ S = PollerStat#stat{time = Time, count = Count, res = ok},
+ d("collect_poller_stat -> S: ~p", [S]),
+ collect_poller_stat(P, [S | PollersStat]);
+ false ->
+ error_msg("statistics already received for ~p", [Poller]),
+ collect_poller_stat(Pollers, PollersStat)
+ end;
+ {poller_stat_failure, Poller, Else} ->
+ error_msg("poller statistics failure for ~p with ~p",
+ [Poller, Else]),
+ case lists:keysearch(Poller, 2, Pollers) of
+ {value, PollerStat} ->
+ P = lists:keydelete(Poller, 2, Pollers),
+ S = PollerStat#stat{res = {error, Else}},
+ collect_poller_stat(P, [S | PollerStat]);
+ false ->
+ error_msg("statistics already received for ~p", [Poller]),
+ collect_poller_stat(Pollers, PollersStat)
+ end
+ end.
+
+
+display_poller_stat(Stats, T) ->
+ display_poller_stat(Stats, 1, T, 0).
+
+display_poller_stat([], _, TestTime, AccCount) ->
+ io:format("Total statistics:~n"
+ " Accumulated count: ~w~n"
+ " Average access time: ~w milli sec~n",
+ [AccCount, (TestTime/AccCount)]);
+display_poller_stat([#stat{res = ok} = Stat | Stats], N, TestTime, AccCount) ->
+ #stat{pid = Pid, time = Time, count = Count} = Stat,
+ io:format("Statistics for poller ~p (~p):~n"
+ " time: ~w seconds~n"
+ " count: ~w~n"
+ " Average access time: ~w milli sec~n",
+ [Pid, N, Time/(1000*1000), Count, (TestTime/Count)]),
+ display_poller_stat(Stats, N + 1, TestTime, AccCount+Count);
+display_poller_stat([Stat | Stats], N, TestTime, AccCount) ->
+ #stat{pid = Pid, res = Error} = Stat,
+ io:format("Statistics failed for poller ~p (~p):~n"
+ " ~p~n", [Pid, N, Error]),
+ display_poller_stat(Stats, N + 1, TestTime, AccCount).
+
+
+
+%%% -----------------------------------------------------------------
+%%% Poller process
+%%%
+
+start_pollers(N, Args) ->
+ start_pollers(N, Args, []).
+
+start_pollers(0, _Args, Pollers) ->
+ Pollers;
+start_pollers(N, Args, Pollers) ->
+ Pid = proc_lib:spawn_link(?MODULE, poller_main, Args),
+ start_pollers(N-1, Args, [#stat{pid = Pid} | Pollers]).
+
+stop_pollers(Pollers) ->
+ [Pid ! stop || #stat{pid = Pid} <- Pollers],
+ await_stop_pollers(Pollers).
+
+await_stop_pollers([]) ->
+ ok;
+await_stop_pollers(Pollers0) ->
+ receive
+ {'EXIT', Pid, _Reason} ->
+ Pollers = lists:keydelete(Pid, 2, Pollers0),
+ await_stop_pollers(Pollers)
+ after 5000 ->
+ [Pid ! shutdown || #stat{pid = Pid} <- Pollers0]
+ end.
+
+
+is_poller(_, []) ->
+ false;
+is_poller(Pid, [#stat{pid = Pid}|_]) ->
+ true;
+is_poller(Pid, [_|Rest]) ->
+ is_poller(Pid, Rest).
+
+
+poller_main(Parent, SocketType, Host, Port) ->
+ process_flag(trap_exit,true),
+ put(sname,poller),
+ case timer:tc(?MODULE, poller_loop, [SocketType, Host, Port, uris()]) of
+ {Time, Count} when is_integer(Time) andalso is_integer(Count) ->
+ Parent ! {poller_statistics, self(), {Time, Count}};
+ {Time, {'EXIT', Reason}} when is_integer(Time) ->
+ exit({poller_stat_failure, Time, Reason});
+ {Time, Other} when is_integer(Time) ->
+ Parent ! {poller_stat_failure, self(), {Time, Other}};
+ Else ->
+ Parent ! {poller_stat_failure, self(), Else}
+ end.
+
+
+uris() ->
+ uris(get(uris)).
+
+uris(L) when is_list(L) ->
+ L;
+uris(_) ->
+ ["/",
+ "/index.html"].
+
+
+poller_loop(SocketType, Host, Port, URIs) ->
+ poller_loop(SocketType, Host, Port, URIs, 0).
+
+poller_loop(SocketType, Host, Port, URIs, Count) ->
+ receive
+ stop ->
+ Count
+ after 0 ->
+ case poller_loop1(SocketType, Host, Port, URIs) of
+ done ->
+ poller_loop(SocketType, Host, Port, URIs,
+ Count + length(URIs));
+ {error, Reason, FailURI, FailURIs} ->
+ SuccessCount =
+ Count + (length(URIs) - (length(FailURIs) + 1)),
+ exit({Reason, FailURI, SuccessCount})
+ end
+ end.
+
+
+poller_loop1(_SocketType, _Host, _Port, []) ->
+ done;
+poller_loop1(SocketType, Host, Port, [URI | URIs]) ->
+ Res = inets_test_lib:connect_byte(SocketType, Host, Port),
+ case (catch poll(Res, SocketType, URI, "200")) of
+ ok ->
+ poller_loop1(SocketType, Host, Port, URIs);
+ {'EXIT', Reason} ->
+ {error, Reason, URI, URIs}
+ end.
+
+poll({ok, Socket}, SocketType, URI, ExpRes) ->
+ Req = "GET " ++ URI ++ " HTTP/1.0\r\n\r\n",
+ Res = inets_test_lib:send(SocketType, Socket, Req),
+ await_poll_response(Res, SocketType, Socket, ExpRes);
+poll({error, Reason}, _SocketType, _URI, _ExpRes) ->
+ exit({failed_creating_socket, Reason});
+poll(Error, _SocketType, _URI, _ExpRes) ->
+ exit({failed_creating_socket, Error}).
+
+await_poll_response(ok, SocketType, Socket, ExpStatusCode) ->
+ receive
+ %% SSL receives
+ {ssl, Socket, Data} ->
+ validate(ExpStatusCode, SocketType, Socket, Data);
+ {ssl_closed, Socket} ->
+ exit(connection_closed);
+ {ssl_error, Socket, Error} ->
+ exit({connection_error, Error});
+
+ %% TCP receives
+ {tcp, Socket, Response} ->
+ validate(ExpStatusCode, SocketType, Socket, Response);
+ {tcp_closed, Socket} ->
+ exit(connection_closed);
+ {tcp_error, Socket, Error} ->
+ exit({connection_error, Error})
+
+ after 10000 ->
+ exit(response_timed_out)
+ end;
+await_poll_response(Error, _SocketType, _Socket, _ExpStatusCode) ->
+ exit(Error).
+
+
+validate(ExpStatusCode, SocketType, Socket, Response) ->
+ Sz = sz(Response),
+ trash_the_rest(Socket, Sz),
+ inets_test_lib:close(SocketType, Socket),
+ case inets_regexp:split(Response," ") of
+ {ok,["HTTP/1.0", ExpStatusCode|_]} ->
+ ok;
+ {ok,["HTTP/1.0", StatusCode|_]} ->
+ error_msg("Unexpected status code: ~p (~s). "
+ "Expected status code: ~p (~s)",
+ [StatusCode, status_to_message(StatusCode),
+ ExpStatusCode, status_to_message(ExpStatusCode)]),
+ exit({unexpected_response_code, StatusCode, ExpStatusCode});
+ {ok,["HTTP/1.1", ExpStatusCode|_]} ->
+ ok;
+ {ok,["HTTP/1.1", StatusCode|_]} ->
+ error_msg("Unexpected status code: ~p (~s). "
+ "Expected status code: ~p (~s)",
+ [StatusCode, status_to_message(StatusCode),
+ ExpStatusCode, status_to_message(ExpStatusCode)]),
+ exit({unexpected_response_code, StatusCode, ExpStatusCode})
+ end.
+
+
+trash_the_rest(Socket, N) ->
+ receive
+ {ssl, Socket, Trash} ->
+ trash_the_rest(Socket, add(N,sz(Trash)));
+ {ssl_closed, Socket} ->
+ N;
+ {ssl_error, Socket, Error} ->
+ exit({connection_error, Error});
+
+ {tcp, Socket, Trash} ->
+ trash_the_rest(Socket, add(N,sz(Trash)));
+ {tcp_closed, Socket} ->
+ N;
+ {tcp_error, Socket, Error} ->
+ exit({connection_error, Error})
+
+ after 10000 ->
+ exit({connection_timed_out, N})
+ end.
+
+
+add(N1,N2) when is_integer(N1) andalso is_integer(N2) ->
+ N1 + N2;
+add(N1,_) when is_integer(N1) ->
+ N1;
+add(_,N2) when is_integer(N2) ->
+ N2.
+
+
+sz(L) when is_list(L) ->
+ length(lists:flatten(L));
+sz(B) when is_binary(B) ->
+ size(B);
+sz(O) ->
+ {unknown_size,O}.
+
+
+%% --------------------------------------------------------------
+%%
+%% Status code to printable string
+%%
+
+status_to_message(L) when is_list(L) ->
+ case (catch list_to_integer(L)) of
+ I when is_integer(I) ->
+ status_to_message(I);
+ _ ->
+ io_lib:format("UNKNOWN STATUS CODE: '~p'",[L])
+ end;
+status_to_message(100) -> "Section 10.1.1: Continue";
+status_to_message(101) -> "Section 10.1.2: Switching Protocols";
+status_to_message(200) -> "Section 10.2.1: OK";
+status_to_message(201) -> "Section 10.2.2: Created";
+status_to_message(202) -> "Section 10.2.3: Accepted";
+status_to_message(203) -> "Section 10.2.4: Non-Authoritative Information";
+status_to_message(204) -> "Section 10.2.5: No Content";
+status_to_message(205) -> "Section 10.2.6: Reset Content";
+status_to_message(206) -> "Section 10.2.7: Partial Content";
+status_to_message(300) -> "Section 10.3.1: Multiple Choices";
+status_to_message(301) -> "Section 10.3.2: Moved Permanently";
+status_to_message(302) -> "Section 10.3.3: Found";
+status_to_message(303) -> "Section 10.3.4: See Other";
+status_to_message(304) -> "Section 10.3.5: Not Modified";
+status_to_message(305) -> "Section 10.3.6: Use Proxy";
+status_to_message(307) -> "Section 10.3.8: Temporary Redirect";
+status_to_message(400) -> "Section 10.4.1: Bad Request";
+status_to_message(401) -> "Section 10.4.2: Unauthorized";
+status_to_message(402) -> "Section 10.4.3: Peyment Required";
+status_to_message(403) -> "Section 10.4.4: Forbidden";
+status_to_message(404) -> "Section 10.4.5: Not Found";
+status_to_message(405) -> "Section 10.4.6: Method Not Allowed";
+status_to_message(406) -> "Section 10.4.7: Not Acceptable";
+status_to_message(407) -> "Section 10.4.8: Proxy Authentication Required";
+status_to_message(408) -> "Section 10.4.9: Request Time-Out";
+status_to_message(409) -> "Section 10.4.10: Conflict";
+status_to_message(410) -> "Section 10.4.11: Gone";
+status_to_message(411) -> "Section 10.4.12: Length Required";
+status_to_message(412) -> "Section 10.4.13: Precondition Failed";
+status_to_message(413) -> "Section 10.4.14: Request Entity Too Large";
+status_to_message(414) -> "Section 10.4.15: Request-URI Too Large";
+status_to_message(415) -> "Section 10.4.16: Unsupported Media Type";
+status_to_message(416) -> "Section 10.4.17: Requested range not satisfiable";
+status_to_message(417) -> "Section 10.4.18: Expectation Failed";
+status_to_message(500) -> "Section 10.5.1: Internal Server Error";
+status_to_message(501) -> "Section 10.5.2: Not Implemented";
+status_to_message(502) -> "Section 10.5.3: Bad Gatteway";
+status_to_message(503) -> "Section 10.5.4: Service Unavailable";
+status_to_message(504) -> "Section 10.5.5: Gateway Time-out";
+status_to_message(505) -> "Section 10.5.6: HTTP Version not supported";
+status_to_message(Code) -> io_lib:format("Unknown status code: ~p",[Code]).
+
+%% ----------------------------------------------------------------
+
+to(To, Start) ->
+ To - (t() - Start).
+
+%% Time in milli seconds
+t() ->
+ {A,B,C} = erlang:now(),
+ A*1000000000+B*1000+(C div 1000).
+
+
+%% ----------------------------------------------------------------
+
+
+
+% close(Socket) ->
+% gen_tcp:close(Socket).
+
+% send(Socket, Data) ->
+% gen_tcp:send(Socket, Data).
+
+
+hostname() ->
+ {ok, Hostname} = inet:gethostname(),
+ hostname(Hostname).
+
+hostname(Hostname) when is_list(Hostname) ->
+ list_to_atom(Hostname);
+hostname(Hostname) ->
+ Hostname.
+
+%% ----------------------------------------------------------------
+
+error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A).
+
+d(F) ->
+ d(get(dbg),F,[]).
+
+d(F,A) ->
+ d(get(dbg),F,A).
+
+d(true, F, A) ->
+ io:format("DBG ~p ~p " ++ F ++ "~n", [self(),get(sname)]++A);
+d(_,_,_) ->
+ ok.
diff --git a/lib/inets/test/inets.config b/lib/inets/test/inets.config
new file mode 100644
index 0000000000..6c9077594d
--- /dev/null
+++ b/lib/inets/test/inets.config
@@ -0,0 +1 @@
+[{inets,[{services,[{httpd,"/ldisk/tests/bmk/inets/priv_dir/8099.conf"}]}]}].
diff --git a/lib/inets/test/inets.spec b/lib/inets/test/inets.spec
new file mode 100644
index 0000000000..a9b4524295
--- /dev/null
+++ b/lib/inets/test/inets.spec
@@ -0,0 +1,2 @@
+{topcase, {dir, "../inets_test"}}.
+{hosts, ["tuor"]}.
diff --git a/lib/inets/test/inets.spec.vxworks b/lib/inets/test/inets.spec.vxworks
new file mode 100644
index 0000000000..6886299226
--- /dev/null
+++ b/lib/inets/test/inets.spec.vxworks
@@ -0,0 +1,5 @@
+{topcase, {dir, "../inets_test"}}.
+{skip, {inets_SUITE, ip_mod_cgi, "Requires processes"}}.
+{skip, {inets_SUITE, ip_mod_all_modules, "Requires processes"}}.
+{skip, {inets_SUITE, ssl, "Requires SSL"}}.
+
diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl
new file mode 100644
index 0000000000..56983caace
--- /dev/null
+++ b/lib/inets/test/inets_SUITE.erl
@@ -0,0 +1,583 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(inets_SUITE).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("inets_test_lib.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(NUM_DEFAULT_SERVICES, 1).
+
+all(doc) ->
+ ["Test suites for the inets application."];
+
+all(suite) ->
+ [
+ app_test,
+ appup_test,
+ services_test,
+ httpd_reload
+ ].
+
+services_test(suite) ->
+ [
+ start_inets,
+ start_httpc,
+ start_httpd,
+ start_ftpc,
+ start_tftpd
+ ].
+
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_Case, Config) ->
+ inets:stop(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_, Config) ->
+ Config.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+app_test(suite) ->
+ [{inets_app_test, all}].
+
+appup_test(suite) ->
+ [{inets_appup_test, all}].
+
+
+%%-------------------------------------------------------------------------
+
+start_inets(doc) ->
+ ["Test inets API functions"];
+start_inets(suite) ->
+ [];
+start_inets(Config) when is_list(Config) ->
+ [_|_] = inets:service_names(),
+
+ {error,inets_not_started} = inets:services(),
+ {error,inets_not_started} = inets:services_info(),
+
+ ok = inets:start(),
+
+ %% httpc default profile always started
+ [_|_] = inets:services(),
+ [_|_] = inets:services_info(),
+
+ {error,{already_started,inets}} = inets:start(),
+
+ ok = inets:stop(),
+ {error,{not_started,inets}} = inets:stop(),
+
+ ok = inets:start(transient),
+ ok = inets:stop(),
+
+ ok = inets:start(permanent),
+ ok = inets:stop().
+
+
+%%-------------------------------------------------------------------------
+
+start_httpc(doc) ->
+ ["Start/stop of httpc service"];
+start_httpc(suite) ->
+ [];
+start_httpc(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ tsp("start_httpc -> entry with"
+ "~n Config: ~p", [Config]),
+
+ PrivDir = ?config(priv_dir, Config),
+
+ tsp("start_httpc -> start (empty) inets"),
+ ok = inets:start(),
+
+ tsp("start_httpc -> start httpc (as inets service) with profile foo"),
+ {ok, Pid0} = inets:start(httpc, [{profile, foo}]),
+
+ tsp("start_httpc -> check running services"),
+ Pids0 = [ServicePid || {_, ServicePid} <- inets:services()],
+ true = lists:member(Pid0, Pids0),
+ [_|_] = inets:services_info(),
+
+ tsp("start_httpc -> stop httpc"),
+ inets:stop(httpc, Pid0),
+
+ tsp("start_httpc -> sleep some"),
+ test_server:sleep(100),
+
+ tsp("start_httpc -> check running services"),
+ Pids1 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid0, Pids1),
+
+ tsp("start_httpc -> start httpc (stand-alone) with profile bar"),
+ {ok, Pid1} = inets:start(httpc, [{profile, bar}], stand_alone),
+
+ tsp("start_httpc -> check running services"),
+ Pids2 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid1, Pids2),
+
+ tsp("start_httpc -> stop httpc"),
+ ok = inets:stop(stand_alone, Pid1),
+ receive
+ {'EXIT', Pid1, shutdown} ->
+ ok
+ after 100 ->
+ tsf(stand_alone_not_shutdown)
+ end,
+
+ tsp("start_httpc -> stop inets"),
+ ok = inets:stop(),
+
+ tsp("start_httpc -> unload inets"),
+ application:load(inets),
+
+ tsp("start_httpc -> set inets environment (httpc profile foo)"),
+ application:set_env(inets, services, [{httpc,[{profile, foo},
+ {data_dir, PrivDir}]}]),
+
+ tsp("start_httpc -> start inets"),
+ ok = inets:start(),
+
+ tsp("start_httpc -> check running services"),
+ (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
+
+ tsp("start_httpc -> unset inets env"),
+ application:unset_env(inets, services),
+
+ tsp("start_httpc -> stop inets"),
+ ok = inets:stop(),
+
+ tsp("start_httpc -> start (empty) inets"),
+ ok = inets:start(),
+
+ tsp("start_httpc -> start inets httpc service with profile foo"),
+ {ok, Pid3} = inets:start(httpc, [{profile, foo}]),
+
+ tsp("start_httpc -> stop inets service httpc with profile foo"),
+ ok = inets:stop(httpc, foo),
+
+ tsp("start_httpc -> check running services"),
+ Pids3 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid3, Pids3),
+
+ tsp("start_httpc -> stop inets"),
+ ok = inets:stop(),
+
+ tsp("start_httpc -> done"),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+start_httpd(doc) ->
+ ["Start/stop of httpd service"];
+start_httpd(suite) ->
+ [];
+start_httpd(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ i("start_httpd -> entry with"
+ "~n Config: ~p", [Config]),
+ PrivDir = ?config(priv_dir, Config),
+ HttpdConf = [{server_name, "httpd_test"}, {server_root, PrivDir},
+ {document_root, PrivDir}, {bind_address, "localhost"}],
+
+ i("start_httpd -> start inets"),
+ ok = inets:start(),
+
+ i("start_httpd -> start httpd service"),
+ {ok, Pid0} = inets:start(httpd, [{port, 0}, {ipfamily, inet} | HttpdConf]),
+ Pids0 = [ServicePid || {_, ServicePid} <- inets:services()],
+ true = lists:member(Pid0, Pids0),
+ [_|_] = inets:services_info(),
+
+ i("start_httpd -> stop httpd service"),
+ inets:stop(httpd, Pid0),
+ test_server:sleep(500),
+ Pids1 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid0, Pids1),
+ i("start_httpd -> start (stand-alone) httpd service"),
+ {ok, Pid1} =
+ inets:start(httpd, [{port, 0}, {ipfamily, inet} | HttpdConf],
+ stand_alone),
+ Pids2 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid1, Pids2),
+ i("start_httpd -> stop (stand-alone) httpd service"),
+ ok = inets:stop(stand_alone, Pid1),
+ receive
+ {'EXIT', Pid1, shutdown} ->
+ ok
+ after 100 ->
+ test_server:fail(stand_alone_not_shutdown)
+ end,
+ i("start_httpd -> stop inets"),
+ ok = inets:stop(),
+ File0 = filename:join(PrivDir, "httpd.conf"),
+ {ok, Fd0} = file:open(File0, [write]),
+ Str = io_lib:format("~p.~n", [[{port, 0}, {ipfamily, inet} | HttpdConf]]),
+ ok = file:write(Fd0, Str),
+ file:close(Fd0),
+
+ i("start_httpd -> [application] load inets"),
+ application:load(inets),
+ i("start_httpd -> [application] set httpd services env with proplist-file"),
+ application:set_env(inets,
+ services, [{httpd, [{proplist_file, File0}]}]),
+ i("start_httpd -> start inets"),
+ ok = inets:start(),
+ (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
+ i("start_httpd -> [application] unset services env"),
+ application:unset_env(inets, services),
+ i("start_httpd -> stop inets"),
+ ok = inets:stop(),
+
+ File1 = filename:join(PrivDir, "httpd_apache.conf"),
+
+ {ok, Fd1} = file:open(File1, [write]),
+ file:write(Fd1, "ServerName httpd_test\r\n"),
+ file:write(Fd1, "ServerRoot " ++ PrivDir ++ "\r\n"),
+ file:write(Fd1, "DocumentRoot " ++ PrivDir ++" \r\n"),
+ file:write(Fd1, "BindAddress *|inet\r\n"),
+ file:write(Fd1, "Port 0\r\n"),
+ file:close(Fd1),
+
+ i("start_httpd -> [application] load inets"),
+ application:load(inets),
+ i("start_httpd -> [application] set httpd services env with file"),
+ application:set_env(inets,
+ services, [{httpd, [{file, File1}]}]),
+ i("start_httpd -> start inets"),
+ ok = inets:start(),
+ (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
+ i("start_httpd -> [application] unset services env"),
+ application:unset_env(inets, services),
+ i("start_httpd -> stop inets"),
+ ok = inets:stop(),
+
+ %% OLD format
+ i("start_httpd -> [application] load inets"),
+ application:load(inets),
+ i("start_httpd -> [application] set httpd services OLD env"),
+ application:set_env(inets,
+ services, [{httpd, File1}]),
+ i("start_httpd -> start inets"),
+ ok = inets:start(),
+ (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
+ i("start_httpd -> [application] unset services enc"),
+ application:unset_env(inets, services),
+ i("start_httpd -> stop inets"),
+ ok = inets:stop(),
+
+ i("start_httpd -> start inets"),
+ ok = inets:start(),
+ i("start_httpd -> try (and fail) start httpd service - server_name"),
+ {error, {missing_property, server_name}} =
+ inets:start(httpd, [{port, 0},
+ {server_root, PrivDir},
+ {document_root, PrivDir},
+ {bind_address, "localhost"}]),
+ i("start_httpd -> try (and fail) start httpd service - missing document_root"),
+ {error, {missing_property, document_root}} =
+ inets:start(httpd, [{port, 0},
+ {server_name, "httpd_test"},
+ {server_root, PrivDir},
+ {bind_address, "localhost"}]),
+ i("start_httpd -> try (and fail) start httpd service - missing server_root"),
+ {error, {missing_property, server_root}} =
+ inets:start(httpd, [{port, 0},
+ {server_name, "httpd_test"},
+ {document_root, PrivDir},
+ {bind_address, "localhost"}]),
+ i("start_httpd -> try (and fail) start httpd service - missing port"),
+ {error, {missing_property, port}} =
+ inets:start(httpd, HttpdConf),
+ i("start_httpd -> stop inets"),
+ ok = inets:stop(),
+ i("start_httpd -> done"),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+
+start_ftpc(doc) ->
+ ["Start/stop of ftpc service"];
+start_ftpc(suite) ->
+ [];
+start_ftpc(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ inets:disable_trace(),
+ inets:enable_trace(max, io, ftpc),
+ ok = inets:start(),
+ try
+ begin
+ {_Tag, FtpdHost} = ftp_suite_lib:dirty_select_ftpd_host(Config),
+ case inets:start(ftpc, [{host, FtpdHost}]) of
+ {ok, Pid0} ->
+ Pids0 = [ServicePid || {_, ServicePid} <-
+ inets:services()],
+ true = lists:member(Pid0, Pids0),
+ [_|_] = inets:services_info(),
+ inets:stop(ftpc, Pid0),
+ test_server:sleep(100),
+ Pids1 = [ServicePid || {_, ServicePid} <-
+ inets:services()],
+ false = lists:member(Pid0, Pids1),
+ {ok, Pid1} =
+ inets:start(ftpc, [{host, FtpdHost}], stand_alone),
+ Pids2 = [ServicePid || {_, ServicePid} <-
+ inets:services()],
+ false = lists:member(Pid1, Pids2),
+ ok = inets:stop(stand_alone, Pid1),
+ receive
+ {'EXIT', Pid1, shutdown} ->
+ ok
+ after 100 ->
+ tsf(stand_alone_not_shutdown)
+ end,
+ ok = inets:stop(),
+ inets:disable_trace(),
+ ok;
+ _ ->
+ inets:disable_trace(),
+ {skip, "Unable to reach selected FTP server " ++ FtpdHost}
+ end
+ end
+ catch
+ throw:{error, not_found} ->
+ inets:disable_trace(),
+ {skip, "No available FTP servers"}
+ end.
+
+
+
+%%-------------------------------------------------------------------------
+
+start_tftpd(doc) ->
+ ["Start/stop of tfpd service"];
+start_tftpd(suite) ->
+ [];
+start_tftpd(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ok = inets:start(),
+ {ok, Pid0} = inets:start(tftpd, [{host, "localhost"}, {port, 0}]),
+ Pids0 = [ServicePid || {_, ServicePid} <- inets:services()],
+ true = lists:member(Pid0, Pids0),
+ [_|_] = inets:services_info(),
+ inets:stop(tftpd, Pid0),
+ test_server:sleep(100),
+ Pids1 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid0, Pids1),
+ {ok, Pid1} =
+ inets:start(tftpd, [{host, "localhost"}, {port, 0}], stand_alone),
+ Pids2 = [ServicePid || {_, ServicePid} <- inets:services()],
+ false = lists:member(Pid1, Pids2),
+ ok = inets:stop(stand_alone, Pid1),
+ receive
+ {'EXIT', Pid1, shutdown} ->
+ ok
+ after 100 ->
+ test_server:fail(stand_alone_not_shutdown)
+ end,
+ ok = inets:stop(),
+ application:load(inets),
+ application:set_env(inets, services, [{tftpd,[{host, "localhost"},
+ {port, 0}]}]),
+ ok = inets:start(),
+ (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()),
+ application:unset_env(inets, services),
+ ok = inets:stop().
+
+
+%%-------------------------------------------------------------------------
+
+httpd_reload(doc) ->
+ ["Reload httpd configuration without restarting service"];
+httpd_reload(suite) ->
+ [];
+httpd_reload(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ i("httpd_reload -> starting"),
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ HttpdConf = [{server_name, "httpd_test"},
+ {server_root, PrivDir},
+ {document_root, PrivDir},
+ {bind_address, "localhost"}],
+
+ inets:enable_trace(max, io),
+
+ i("httpd_reload -> start inets"),
+
+ ok = inets:start(),
+ test_server:sleep(5000),
+ i("httpd_reload -> inets started - start httpd service"),
+
+ {ok, Pid0} = inets:start(httpd, [{port, 0}, {ipfamily, inet} | HttpdConf]),
+ test_server:sleep(5000),
+ i("httpd_reload -> httpd service started (~p) - get port", [Pid0]),
+
+ [{port, Port0}] = httpd:info(Pid0, [port]),
+ test_server:sleep(5000),
+ i("httpd_reload -> Port: ~p - get document root", [Port0]),
+
+ [{document_root, PrivDir}] = httpd:info(Pid0, [document_root]),
+ test_server:sleep(5000),
+ i("httpd_reload -> document root: ~p - reload config", [PrivDir]),
+
+ ok = httpd:reload_config([{port, Port0}, {ipfamily, inet},
+ {server_name, "httpd_test"},
+ {server_root, PrivDir},
+ {document_root, DataDir},
+ {bind_address, "localhost"}], non_disturbing),
+ test_server:sleep(5000),
+ io:format("~w:~w:httpd_reload - reloaded - get document root~n", [?MODULE, ?LINE]),
+
+ [{document_root, DataDir}] = httpd:info(Pid0, [document_root]),
+ test_server:sleep(5000),
+ i("httpd_reload -> document root: ~p - reload config", [DataDir]),
+
+ ok = httpd:reload_config([{port, Port0}, {ipfamily, inet},
+ {server_name, "httpd_test"},
+ {server_root, PrivDir},
+ {document_root, PrivDir},
+ {bind_address, "localhost"}], disturbing),
+
+ [{document_root, PrivDir}] = httpd:info(Pid0, [document_root]),
+ ok = inets:stop(httpd, Pid0),
+ ok = inets:stop(),
+
+ File = filename:join(PrivDir, "httpd_apache.conf"),
+
+ {ok, Fd0} = file:open(File, [write]),
+ file:write(Fd0, "ServerName httpd_test\r\n"),
+ file:write(Fd0, "ServerRoot " ++ PrivDir ++ "\r\n"),
+ file:write(Fd0, "DocumentRoot " ++ PrivDir ++" \r\n"),
+ file:write(Fd0, "BindAddress *\r\n"),
+ file:write(Fd0, "Port 0\r\n"),
+ file:close(Fd0),
+
+ application:load(inets),
+ application:set_env(inets,
+ services, [{httpd, [{file, File}]}]),
+
+ ok = inets:start(),
+ [Pid1] = [HttpdPid || {httpd, HttpdPid} <- inets:services()],
+ [{server_name, "httpd_test"}] = httpd:info(Pid1, [server_name]),
+ [{port, Port1}] = httpd:info(Pid1, [port]),
+ {ok, Fd1} = file:open(File, [write]),
+ file:write(Fd1, "ServerName httpd_test2\r\n"),
+ file:write(Fd1, "ServerRoot " ++ PrivDir ++ "\r\n"),
+ file:write(Fd1, "DocumentRoot " ++ PrivDir ++" \r\n"),
+ file:write(Fd1, "BindAddress *\r\n"),
+ file:write(Fd1, "Port " ++ integer_to_list(Port1) ++ "\r\n"),
+ file:close(Fd1),
+
+ ok = httpd:reload_config(File, non_disturbing),
+ [{server_name, "httpd_test2"}] = httpd:info(Pid1, [server_name]),
+
+ {ok, Fd2} = file:open(File, [write]),
+ file:write(Fd2, "ServerName httpd_test\r\n"),
+ file:write(Fd2, "ServerRoot " ++ PrivDir ++ "\r\n"),
+ file:write(Fd2, "DocumentRoot " ++ PrivDir ++" \r\n"),
+ file:write(Fd2, "BindAddress *\r\n"),
+ file:write(Fd2, "Port " ++ integer_to_list(Port1) ++ "\r\n"),
+ file:close(Fd2),
+ ok = httpd:reload_config(File, disturbing),
+ [{server_name, "httpd_test"}] = httpd:info(Pid1, [server_name]),
+
+ ok = inets:stop(httpd, Pid1),
+ application:unset_env(inets, services),
+ ok = inets:stop(),
+ i("httpd_reload -> starting"),
+ ok.
+
+
+tsf(Reason) ->
+ test_server:fail(Reason).
+
+tsp(F) ->
+ tsp(F, []).
+tsp(F, A) ->
+ Timestamp = formated_timestamp(),
+ test_server:format("** ~s ** ~p ~p:" ++ F ++ "~n", [Timestamp, self(), ?MODULE | A]).
+
+i(F) ->
+ i(F, []).
+
+i(F, A) ->
+ Timestamp = formated_timestamp(),
+ io:format("*** ~s ~w:" ++ F ++ "~n", [Timestamp, ?MODULE | A]).
+
+formated_timestamp() ->
+ format_timestamp( os:timestamp() ).
+
+format_timestamp({_N1, _N2, N3} = Now) ->
+ {Date, Time} = calendar:now_to_datetime(Now),
+ {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ FormatDate =
+ io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
+ [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
+ lists:flatten(FormatDate).
+
diff --git a/lib/inets/test/inets_SUITE_data/.gitignore b/lib/inets/test/inets_SUITE_data/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/inets/test/inets_SUITE_data/.gitignore
diff --git a/lib/inets/test/inets_app_test.erl b/lib/inets/test/inets_app_test.erl
new file mode 100644
index 0000000000..6bdb9bb308
--- /dev/null
+++ b/lib/inets/test/inets_app_test.erl
@@ -0,0 +1,296 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Verify the application specifics of the inets application
+%%----------------------------------------------------------------------
+-module(inets_app_test).
+
+-compile(export_all).
+
+-include("inets_test_lib.hrl").
+
+
+% t() -> megaco_test_lib:t(?MODULE).
+% t(Case) -> megaco_test_lib:t({?MODULE, Case}).
+
+
+%% Test server callbacks
+init_per_testcase(undef_funcs, Config) ->
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ Dog = test_server:timetrap(inets_test_lib:minutes(10)),
+ [{watchdog, Dog}| NewConfig];
+init_per_testcase(_, Config) ->
+ Config.
+
+fin_per_testcase(_Case, Config) ->
+ Config.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(suite) ->
+ Cases =
+ [
+ fields,
+ modules,
+ exportall,
+ app_depend,
+ undef_funcs
+ ],
+ {req, [], {conf, app_init, Cases, app_fin}}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+app_init(suite) -> [];
+app_init(doc) -> [];
+app_init(Config) when is_list(Config) ->
+ case is_app(inets) of
+ {ok, AppFile} ->
+ io:format("AppFile: ~n~p~n", [AppFile]),
+ inets:print_version_info(),
+ [{app_file, AppFile}|Config];
+ {error, Reason} ->
+ fail(Reason)
+ end.
+
+is_app(App) ->
+ LibDir = code:lib_dir(App),
+ File = filename:join([LibDir, "ebin", atom_to_list(App) ++ ".app"]),
+ case file:consult(File) of
+ {ok, [{application, App, AppFile}]} ->
+ {ok, AppFile};
+ Error ->
+ {error, {invalid_format, Error}}
+ end.
+
+
+app_fin(suite) -> [];
+app_fin(doc) -> [];
+app_fin(Config) when is_list(Config) ->
+ Config.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+fields(suite) ->
+ [];
+fields(doc) ->
+ [];
+fields(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Fields = [vsn, description, modules, registered, applications],
+ case check_fields(Fields, AppFile, []) of
+ [] ->
+ ok;
+ Missing ->
+ fail({missing_fields, Missing})
+ end.
+
+check_fields([], _AppFile, Missing) ->
+ Missing;
+check_fields([Field|Fields], AppFile, Missing) ->
+ check_fields(Fields, AppFile, check_field(Field, AppFile, Missing)).
+
+check_field(Name, AppFile, Missing) ->
+ io:format("checking field: ~p~n", [Name]),
+ case lists:keymember(Name, 1, AppFile) of
+ true ->
+ Missing;
+ false ->
+ [Name|Missing]
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+modules(suite) ->
+ [];
+modules(doc) ->
+ [];
+modules(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Mods = key1search(modules, AppFile),
+ EbinList = get_ebin_mods(inets),
+ case missing_modules(Mods, EbinList, []) of
+ [] ->
+ ok;
+ Missing ->
+ throw({error, {missing_modules, Missing}})
+ end,
+ case extra_modules(Mods, EbinList, []) of
+ [] ->
+ ok;
+ Extra ->
+ throw({error, {extra_modules, Extra}})
+ end,
+ {ok, Mods}.
+
+get_ebin_mods(App) ->
+ LibDir = code:lib_dir(App),
+ EbinDir = filename:join([LibDir,"ebin"]),
+ {ok, Files0} = file:list_dir(EbinDir),
+ Files1 = [lists:reverse(File) || File <- Files0],
+ [list_to_atom(lists:reverse(Name)) || [$m,$a,$e,$b,$.|Name] <- Files1].
+
+
+missing_modules([], _Ebins, Missing) ->
+ Missing;
+missing_modules([Mod|Mods], Ebins, Missing) ->
+ case lists:member(Mod, Ebins) of
+ true ->
+ missing_modules(Mods, Ebins, Missing);
+ false ->
+ io:format("missing module: ~p~n", [Mod]),
+ missing_modules(Mods, Ebins, [Mod|Missing])
+ end.
+
+
+extra_modules(_Mods, [], Extra) ->
+ Extra;
+extra_modules(Mods, [Mod|Ebins], Extra) ->
+ case lists:member(Mod, Mods) of
+ true ->
+ extra_modules(Mods, Ebins, Extra);
+ false ->
+ io:format("supefluous module: ~p~n", [Mod]),
+ extra_modules(Mods, Ebins, [Mod|Extra])
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+exportall(suite) ->
+ [];
+exportall(doc) ->
+ [];
+exportall(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Mods = key1search(modules, AppFile),
+ check_export_all(Mods).
+
+
+check_export_all([]) ->
+ ok;
+check_export_all([Mod|Mods]) ->
+ case (catch apply(Mod, module_info, [compile])) of
+ {'EXIT', {undef, _}} ->
+ check_export_all(Mods);
+ O ->
+ case lists:keysearch(options, 1, O) of
+ false ->
+ check_export_all(Mods);
+ {value, {options, List}} ->
+ case lists:member(export_all, List) of
+ true ->
+ throw({error, {export_all, Mod}});
+ false ->
+ check_export_all(Mods)
+ end
+ end
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+app_depend(suite) ->
+ [];
+app_depend(doc) ->
+ [];
+app_depend(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Apps = key1search(applications, AppFile),
+ check_apps(Apps).
+
+
+check_apps([]) ->
+ ok;
+check_apps([App|Apps]) ->
+ case is_app(App) of
+ {ok, _} ->
+ check_apps(Apps);
+ Error ->
+ throw({error, {missing_app, {App, Error}}})
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+undef_funcs(suite) ->
+ [];
+undef_funcs(doc) ->
+ [];
+undef_funcs(Config) when is_list(Config) ->
+ App = inets,
+ AppFile = key1search(app_file, Config),
+ Mods = key1search(modules, AppFile),
+ Root = code:root_dir(),
+ LibDir = code:lib_dir(App),
+ EbinDir = filename:join([LibDir,"ebin"]),
+ XRefTestName = undef_funcs_make_name(App, xref_test_name),
+ {ok, XRef} = xref:start(XRefTestName),
+ ok = xref:set_default(XRef,
+ [{verbose,false},{warnings,false}]),
+ XRefName = undef_funcs_make_name(App, xref_name),
+ {ok, XRefName} = xref:add_release(XRef, Root, {name,XRefName}),
+ {ok, App} = xref:replace_application(XRef, App, EbinDir),
+ {ok, Undefs} = xref:analyze(XRef, undefined_function_calls),
+ xref:stop(XRef),
+ analyze_undefined_function_calls(Undefs, Mods, []).
+
+analyze_undefined_function_calls([], _, []) ->
+ ok;
+analyze_undefined_function_calls([], _, AppUndefs) ->
+ exit({suite_failed, {undefined_function_calls, AppUndefs}});
+analyze_undefined_function_calls([{{Mod, _F, _A}, _C} = AppUndef|Undefs],
+ AppModules, AppUndefs) ->
+ %% Check that this module is our's
+ case lists:member(Mod,AppModules) of
+ true ->
+ {Calling,Called} = AppUndef,
+ {Mod1,Func1,Ar1} = Calling,
+ {Mod2,Func2,Ar2} = Called,
+ io:format("undefined function call: "
+ "~n ~w:~w/~w calls ~w:~w/~w~n",
+ [Mod1,Func1,Ar1,Mod2,Func2,Ar2]),
+ analyze_undefined_function_calls(Undefs, AppModules,
+ [AppUndef|AppUndefs]);
+ false ->
+ io:format("dropping ~p~n", [Mod]),
+ analyze_undefined_function_calls(Undefs, AppModules, AppUndefs)
+ end.
+
+%% This function is used simply to avoid cut-and-paste errors later...
+undef_funcs_make_name(App, PostFix) ->
+ list_to_atom(atom_to_list(App) ++ "_" ++ atom_to_list(PostFix)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+fail(Reason) ->
+ exit({suite_failed, Reason}).
+
+key1search(Key, L) ->
+ case lists:keysearch(Key, 1, L) of
+ undefined ->
+ fail({not_found, Key, L});
+ {value, {Key, Value}} ->
+ Value
+ end.
diff --git a/lib/inets/test/inets_appup_test.erl b/lib/inets/test/inets_appup_test.erl
new file mode 100644
index 0000000000..d580c6c4c5
--- /dev/null
+++ b/lib/inets/test/inets_appup_test.erl
@@ -0,0 +1,336 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Verify the application specifics of the Megaco application
+%%----------------------------------------------------------------------
+-module(inets_appup_test).
+
+-compile(export_all).
+
+-include("inets_test_lib.hrl").
+
+
+% t() -> megaco_test_lib:t(?MODULE).
+% t(Case) -> megaco_test_lib:t({?MODULE, Case}).
+
+
+%% Test server callbacks
+init_per_testcase(_Case, Config) ->
+ Config.
+
+fin_per_testcase(_Case, Config) ->
+ Config.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(suite) ->
+ Cases =
+ [
+ appup
+ ],
+ {req, [], {conf, appup_init, Cases, appup_fin}}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+appup_init(suite) -> [];
+appup_init(doc) -> [];
+appup_init(Config) when is_list(Config) ->
+ AppFile = file_name(inets, ".app"),
+ AppupFile = file_name(inets, ".appup"),
+ [{app_file, AppFile}, {appup_file, AppupFile}|Config].
+
+
+file_name(App, Ext) ->
+ LibDir = code:lib_dir(App),
+ filename:join([LibDir, "ebin", atom_to_list(App) ++ Ext]).
+
+
+appup_fin(suite) -> [];
+appup_fin(doc) -> [];
+appup_fin(Config) when is_list(Config) ->
+ Config.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+appup(suite) ->
+ [];
+appup(doc) ->
+ "perform a simple check of the appup file";
+appup(Config) when is_list(Config) ->
+ AppupFile = key1search(appup_file, Config),
+ AppFile = key1search(app_file, Config),
+ Modules = modules(AppFile),
+ check_appup(AppupFile, Modules).
+
+modules(File) ->
+ case file:consult(File) of
+ {ok, [{application,inets,Info}]} ->
+ case lists:keysearch(modules,1,Info) of
+ {value, {modules, Modules}} ->
+ Modules;
+ false ->
+ fail({bad_appinfo, Info})
+ end;
+ Error ->
+ fail({bad_appfile, Error})
+ end.
+
+
+check_appup(AppupFile, Modules) ->
+ case file:consult(AppupFile) of
+ {ok, [{V, UpFrom, DownTo}]} ->
+% io:format("~p => "
+% "~n ~p"
+% "~n ~p"
+% "~n", [V, UpFrom, DownTo]),
+ check_appup(V, UpFrom, DownTo, Modules);
+ Else ->
+ fail({bad_appupfile, Else})
+ end.
+
+
+check_appup(V, UpFrom, DownTo, Modules) ->
+ check_version(V),
+ check_depends(up, UpFrom, Modules),
+ check_depends(down, DownTo, Modules),
+ ok.
+
+
+check_depends(_, [], _) ->
+ ok;
+check_depends(UpDown, [Dep|Deps], Modules) ->
+ check_depend(UpDown, Dep, Modules),
+ check_depends(UpDown, Deps, Modules).
+
+
+check_depend(UpDown, {V, Instructions}, Modules) ->
+ check_version(V),
+ case check_instructions(UpDown,
+ Instructions, Instructions, [], [], Modules) of
+ {_Good, []} ->
+ ok;
+ {_, Bad} ->
+ fail({bad_instructions, Bad, UpDown})
+ end.
+
+
+check_instructions(_, [], _, Good, Bad, _) ->
+ {lists:reverse(Good), lists:reverse(Bad)};
+check_instructions(UpDown, [Instr|Instrs], AllInstr, Good, Bad, Modules) ->
+ case (catch check_instruction(UpDown, Instr, AllInstr, Modules)) of
+ ok ->
+ check_instructions(UpDown, Instrs, AllInstr,
+ [Instr|Good], Bad, Modules);
+ {error, Reason} ->
+ check_instructions(UpDown, Instrs, AllInstr, Good,
+ [{Instr, Reason}|Bad], Modules)
+ end;
+check_instructions(UpDown, Instructions, _, _, _, _) ->
+ fail({bad_instructions, {UpDown, Instructions}}).
+
+%% A new module is added
+check_instruction(up, {add_module, Module}, _, Modules)
+ when is_atom(Module) ->
+ check_module(Module, Modules);
+
+%% An old module is re-added
+check_instruction(down, {add_module, Module}, _, Modules)
+ when is_atom(Module) ->
+ case (catch check_module(Module, Modules)) of
+ {error, {unknown_module, Module, Modules}} ->
+ ok;
+ ok ->
+ error({existing_readded_module, Module})
+ end;
+
+%% Removing a module on upgrade:
+%% - the module has been removed from the app-file.
+%% - check that no module depends on this (removed) module
+check_instruction(up, {remove, {Module, Pre, Post}}, _, Modules)
+ when is_atom(Module), is_atom(Pre), is_atom(Post) ->
+ case (catch check_module(Module, Modules)) of
+ {error, {unknown_module, Module, Modules}} ->
+ check_purge(Pre),
+ check_purge(Post);
+ ok ->
+ error({existing_removed_module, Module})
+ end;
+
+%% Removing a module on downgrade: the module exist
+%% in the app-file.
+check_instruction(down, {remove, {Module, Pre, Post}}, AllInstr, Modules)
+ when is_atom(Module), is_atom(Pre), is_atom(Post) ->
+ case (catch check_module(Module, Modules)) of
+ ok ->
+ check_purge(Pre),
+ check_purge(Post),
+ check_no_remove_depends(Module, AllInstr);
+ {error, {unknown_module, Module, Modules}} ->
+ error({nonexisting_removed_module, Module})
+ end;
+
+check_instruction(up, {load_module, Module, Pre, Post, Depend}, _, Modules)
+ when is_atom(Module), is_atom(Pre), is_atom(Post), is_list(Depend) ->
+ check_module(Module, Modules),
+ check_module_depend(Module, Depend, Modules),
+ check_purge(Pre),
+ check_purge(Post);
+
+check_instruction(down, {load_module, Module, Pre, Post, Depend}, _, Modules)
+ when is_atom(Module), is_atom(Pre), is_atom(Post), is_list(Depend) ->
+ check_module(Module, Modules),
+ % Can not be sure that the the dependent module exists in the new appfile
+ %%check_module_depend(Module, Depend, Modules),
+ check_purge(Pre),
+ check_purge(Post);
+
+
+
+check_instruction(up, {delete_module, Module}, _, Modules)
+ when is_atom(Module) ->
+ case (catch check_module(Module, Modules)) of
+ {error, {unknown_module, Module, Modules}} ->
+ ok;
+ ok ->
+ error({existing_module_deleted, Module})
+ end;
+
+check_instruction(down, {delete_module, Module}, _, Modules)
+ when is_atom(Module) ->
+ check_module(Module, Modules);
+
+
+check_instruction(_, {apply, {Module, Function, Args}}, _, _) when is_atom(Module), is_atom(Function), is_list(Args) ->
+ ok;
+
+check_instruction(_, {update, Module, supervisor}, _, Modules) when is_atom(Module) ->
+ check_module(Module, Modules);
+
+check_instruction(_, {update, Module, {advanced, _}, DepMods}, _, Modules) when is_atom(Module), is_list(DepMods) ->
+ check_module(Module, Modules),
+ check_module_depend(Module, DepMods, Modules);
+
+check_instruction(_, {update, Module, Change, Pre, Post, Depend}, _, Modules)
+ when is_atom(Module), is_atom(Pre), is_atom(Post), is_list(Depend) ->
+ check_module(Module, Modules),
+ check_module_depend(Module, Depend, Modules),
+ check_change(Change),
+ check_purge(Pre),
+ check_purge(Post);
+
+check_instruction(_, {restart_application, inets}, _AllInstr, _Modules) ->
+ ok;
+
+check_instruction(_, {update, Module, {advanced, _}}, _, Modules) ->
+ check_module(Module, Modules);
+
+check_instruction(_, Instr, _AllInstr, _Modules) ->
+ error({error, {unknown_instruction, Instr}}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+check_version(V) when is_list(V) ->
+ ok;
+check_version(V) ->
+ error({bad_version, V}).
+
+
+check_module(M, Modules) when is_atom(M) ->
+ case lists:member(M,Modules) of
+ true ->
+ ok;
+ false ->
+ error({unknown_module, M, Modules})
+ end;
+check_module(M, _) ->
+ error({bad_module, M}).
+
+
+check_module_depend(M, [], _) when is_atom(M) ->
+ ok;
+check_module_depend(M, Deps, Modules) when is_atom(M), is_list(Deps) ->
+ case [Dep || Dep <- Deps, lists:member(Dep, Modules) == false] of
+ [] ->
+ ok;
+ Unknown ->
+ error({unknown_depend_modules, Unknown})
+ end;
+check_module_depend(_M, D, _Modules) ->
+ error({bad_depend, D}).
+
+
+check_no_remove_depends(_Module, []) ->
+ ok;
+check_no_remove_depends(Module, [Instr|Instrs]) ->
+ check_no_remove_depend(Module, Instr),
+ check_no_remove_depends(Module, Instrs).
+
+check_no_remove_depend(Module, {load_module, Mod, _Pre, _Post, Depend}) ->
+ case lists:member(Module, Depend) of
+ true ->
+ error({removed_module_in_depend, load_module, Mod, Module});
+ false ->
+ ok
+ end;
+check_no_remove_depend(Module, {update, Mod, _Change, _Pre, _Post, Depend}) ->
+ case lists:member(Module, Depend) of
+ true ->
+ error({removed_module_in_depend, update, Mod, Module});
+ false ->
+ ok
+ end;
+check_no_remove_depend(_, _) ->
+ ok.
+
+
+check_change(soft) ->
+ ok;
+check_change({advanced, _Something}) ->
+ ok;
+check_change(Change) ->
+ error({bad_change, Change}).
+
+
+check_purge(soft_purge) ->
+ ok;
+check_purge(brutal_purge) ->
+ ok;
+check_purge(Purge) ->
+ error({bad_purge, Purge}).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+error(Reason) ->
+ throw({error, Reason}).
+
+fail(Reason) ->
+ exit({suite_failed, Reason}).
+
+key1search(Key, L) ->
+ case lists:keysearch(Key, 1, L) of
+ undefined ->
+ fail({not_found, Key, L});
+ {value, {Key, Value}} ->
+ Value
+ end.
diff --git a/lib/inets/test/inets_internal.hrl b/lib/inets/test/inets_internal.hrl
new file mode 120000
index 0000000000..3228d7ef6a
--- /dev/null
+++ b/lib/inets/test/inets_internal.hrl
@@ -0,0 +1 @@
+../src/inets_app/inets_internal.hrl \ No newline at end of file
diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl
new file mode 100644
index 0000000000..ba41e0960c
--- /dev/null
+++ b/lib/inets/test/inets_sup_SUITE.erl
@@ -0,0 +1,414 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(inets_sup_SUITE).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+all(doc) ->
+ ["Test that the inets supervisorstructur is the expected one."];
+all(suite) ->
+ [
+ default_tree,
+ ftpc_worker,
+ tftpd_worker,
+ httpd_subtree,
+ httpc_subtree
+ ].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_) ->
+ inets:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(httpd_subtree, Config) ->
+ io:format("init_per_testcase(httpd_subtree) -> entry with"
+ "~n Config: ~p"
+ "~n", [Config]),
+ Dog = test_server:timetrap(?t:minutes(1)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ ServerROOT = filename:join(PrivDir, "server_root"),
+ DocROOT = filename:join(PrivDir, "htdocs"),
+ ConfDir = filename:join(ServerROOT, "conf"),
+
+ io:format("init_per_testcase(httpd_subtree) -> create dir(s)"
+ "~n", []),
+ file:make_dir(ServerROOT), %% until http_test is cleaned up!
+ ok = file:make_dir(DocROOT),
+ ok = file:make_dir(ConfDir),
+
+ io:format("init_per_testcase(httpd_subtree) -> copy file(s)"
+ "~n", []),
+ {ok, _} = inets_test_lib:copy_file("simple.conf", DataDir, PrivDir),
+ {ok, _} = inets_test_lib:copy_file("mime.types", DataDir, ConfDir),
+
+ io:format("init_per_testcase(httpd_subtree) -> write file(s)"
+ "~n", []),
+ ConfFile = filename:join(PrivDir, "simple.conf"),
+ {ok, Fd} = file:open(ConfFile, [append]),
+ ok = file:write(Fd, "ServerRoot " ++ ServerROOT ++ "\n"),
+ ok = file:write(Fd, "DocumentRoot " ++ DocROOT ++ "\n"),
+ ok = file:close(Fd),
+
+ %% To make sure application:set_env is not overwritten by any
+ %% app-file settings.
+ io:format("init_per_testcase(httpd_subtree) -> load inets app"
+ "~n", []),
+ application:load(inets),
+ io:format("init_per_testcase(httpd_subtree) -> update inets env"
+ "~n", []),
+ ok = application:set_env(inets, services, [{httpd, ConfFile}]),
+
+ try
+ io:format("init_per_testcase(httpd_subtree) -> start inets app"
+ "~n", []),
+ ok = inets:start(),
+ io:format("init_per_testcase(httpd_subtree) -> done"
+ "~n", []),
+ [{watchdog, Dog}, {server_root, ServerROOT}, {doc_root, DocROOT},
+ {conf_dir, ConfDir}| NewConfig]
+ catch
+ _:Reason ->
+ io:format("init_per_testcase(httpd_subtree) -> "
+ "failed starting inets - cleanup"
+ "~n Reason: ~p"
+ "~n", [Reason]),
+ application:unset_env(inets, services),
+ application:unload(inets),
+ exit({failed_starting_inets, Reason})
+ end;
+
+
+init_per_testcase(Case, Config) ->
+ io:format("init_per_testcase(~p) -> entry with"
+ "~n Config: ~p"
+ "~n", [Case, Config]),
+ Dog = test_server:timetrap(?t:minutes(5)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ Stop = inets:stop(),
+ io:format("init_per_testcase(~p) -> Stop: ~p"
+ "~n", [Case, Stop]),
+ ok = inets:start(),
+ [{watchdog, Dog} | NewConfig].
+
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(httpd_subtree, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ PrivDir = ?config(priv_dir, Config),
+ inets_test_lib:del_dirs(PrivDir),
+ ok;
+
+end_per_testcase(_, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ inets:stop(),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+
+
+%%-------------------------------------------------------------------------
+%% default_tree
+%%-------------------------------------------------------------------------
+default_tree(doc) ->
+ ["Makes sure the correct processes are started and linked,"
+ "in the default case."];
+default_tree(suite) ->
+ [];
+default_tree(Config) when is_list(Config) ->
+ TopSupChildren = supervisor:which_children(inets_sup),
+ 4 = length(TopSupChildren),
+ {value, {httpd_sup, _, supervisor,[httpd_sup]}} =
+ lists:keysearch(httpd_sup, 1, TopSupChildren),
+ {value, {httpc_sup, _,supervisor,[httpc_sup]}} =
+ lists:keysearch(httpc_sup, 1, TopSupChildren),
+ {value, {ftp_sup,_,supervisor,[ftp_sup]}} =
+ lists:keysearch(ftp_sup, 1, TopSupChildren),
+ {value, {tftp_sup,_,supervisor,[tftp_sup]}} =
+ lists:keysearch(tftp_sup, 1, TopSupChildren),
+
+ HttpcSupChildren = supervisor:which_children(httpc_sup),
+ {value, {httpc_profile_sup,_, supervisor, [httpc_profile_sup]}} =
+ lists:keysearch(httpc_profile_sup, 1, HttpcSupChildren),
+ {value, {httpc_handler_sup,_, supervisor, [httpc_handler_sup]}} =
+ lists:keysearch(httpc_handler_sup, 1, HttpcSupChildren),
+
+ [] = supervisor:which_children(ftp_sup),
+
+ [] = supervisor:which_children(httpd_sup),
+
+ %% Default profile
+ [{httpc_manager, _, worker,[httpc_manager]}]
+ = supervisor:which_children(httpc_profile_sup),
+
+ [] = supervisor:which_children(httpc_handler_sup),
+
+ [] = supervisor:which_children(tftp_sup),
+
+ ok.
+
+
+%%-------------------------------------------------------------------------
+%% ftpc_worker
+%%-------------------------------------------------------------------------
+ftpc_worker(doc) ->
+ ["Makes sure the ftp worker processes are added and removed "
+ "appropriatly to/from the supervison tree."];
+ftpc_worker(suite) ->
+ [];
+ftpc_worker(Config) when is_list(Config) ->
+ inets:disable_trace(),
+ inets:enable_trace(max, io, ftpc),
+ [] = supervisor:which_children(ftp_sup),
+ try
+ begin
+ {_Tag, FtpdHost} = ftp_suite_lib:dirty_select_ftpd_host(Config),
+ case inets:start(ftpc, [{host, FtpdHost}]) of
+ {ok, Pid} ->
+ case supervisor:which_children(ftp_sup) of
+ [{_,_, worker, [ftp]}] ->
+ inets:stop(ftpc, Pid),
+ test_server:sleep(5000),
+ [] = supervisor:which_children(ftp_sup),
+ inets:disable_trace(),
+ ok;
+ Children ->
+ inets:disable_trace(),
+ exit({unexpected_children, Children})
+ end;
+ _ ->
+ inets:disable_trace(),
+ {skip, "Unable to reach test FTP server"}
+ end
+ end
+ catch
+ throw:{error, not_found} ->
+ inets:disable_trace(),
+ {skip, "No available FTP servers"}
+ end.
+
+
+%%-------------------------------------------------------------------------
+%% tftpd_worker
+%%-------------------------------------------------------------------------
+tftpd_worker(doc) ->
+ ["Makes sure the tftp sub tree is correct."];
+tftpd_worker(suite) ->
+ [];
+tftpd_worker(Config) when is_list(Config) ->
+ [] = supervisor:which_children(tftp_sup),
+ {ok, Pid0} = inets:start(tftpd, [{host, "localhost"},
+ {port, inet_port()}]),
+ {ok, _Pid1} = inets:start(tftpd, [{host, "localhost"},
+ {port, inet_port()}], stand_alone),
+
+ [{_,Pid0, worker, _}] = supervisor:which_children(tftp_sup),
+ inets:stop(tftpd, Pid0),
+ test_server:sleep(5000),
+ [] = supervisor:which_children(tftp_sup),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+%% httpd_subtree
+%%-------------------------------------------------------------------------
+httpd_subtree(doc) ->
+ ["Makes sure the httpd sub tree is correct."];
+httpd_subtree(suite) ->
+ [];
+httpd_subtree(Config) when is_list(Config) ->
+ io:format("httpd_subtree -> entry with"
+ "~n Config: ~p"
+ "~n", [Config]),
+
+ %% Check that we have the httpd top supervisor
+ io:format("httpd_subtree -> verify inets~n", []),
+ {ok, _} = verify_child(inets_sup, httpd_sup, supervisor),
+
+ %% Check that we have the httpd instance supervisor
+ io:format("httpd_subtree -> verify httpd~n", []),
+ {ok, Id} = verify_child(httpd_sup, httpd_instance_sup, supervisor),
+ {httpd_instance_sup, Addr, Port} = Id,
+ Instance = httpd_util:make_name("httpd_instance_sup", Addr, Port),
+
+ %% Check that we have the expected httpd instance children
+ io:format("httpd_subtree -> verify httpd instance children "
+ "(acceptor, misc and manager)~n", []),
+ {ok, _} = verify_child(Instance, httpd_acceptor_sup, supervisor),
+ {ok, _} = verify_child(Instance, httpd_misc_sup, supervisor),
+ {ok, _} = verify_child(Instance, httpd_manager, worker),
+
+ %% Check that the httpd instance acc supervisor has children
+ io:format("httpd_subtree -> verify acc~n", []),
+ InstanceAcc = httpd_util:make_name("httpd_acc_sup", Addr, Port),
+ case supervisor:which_children(InstanceAcc) of
+ [_ | _] ->
+ ok;
+ InstanceAccUnexpectedChildren ->
+ exit({unexpected_children,
+ InstanceAcc, InstanceAccUnexpectedChildren})
+ end,
+
+ %% Check that the httpd instance misc supervisor has no children
+ io:format("httpd_subtree -> verify misc~n", []),
+ InstanceMisc = httpd_util:make_name("httpd_misc_sup", Addr, Port),
+ case supervisor:which_children(InstanceMisc) of
+ [] ->
+ ok;
+ InstanceMiscUnexpectedChildren ->
+ exit({unexpected_children,
+ InstanceMisc, InstanceMiscUnexpectedChildren})
+ end,
+ io:format("httpd_subtree -> done~n", []),
+ ok.
+
+
+verify_child(Parent, Child, Type) ->
+%% io:format("verify_child -> entry with"
+%% "~n Parent: ~p"
+%% "~n Child: ~p"
+%% "~n Type: ~p"
+%% "~n", [Parent, Child, Type]),
+ Children = supervisor:which_children(Parent),
+%% io:format("verify_child -> which children"
+%% "~n Children: ~p"
+%% "~n", [Children]),
+ verify_child(Children, Parent, Child, Type).
+
+verify_child([], Parent, Child, _Type) ->
+ {error, {child_not_found, Child, Parent}};
+verify_child([{Id, _Pid, Type2, Mods}|Children], Parent, Child, Type) ->
+ case lists:member(Child, Mods) of
+ true when (Type2 =:= Type) ->
+%% io:format("verify_child -> found with expected type"
+%% "~n Id: ~p"
+%% "~n", [Id]),
+ {ok, Id};
+ true when (Type2 =/= Type) ->
+%% io:format("verify_child -> found with unexpected type"
+%% "~n Type2: ~p"
+%% "~n Id: ~p"
+%% "~n", [Type2, Id]),
+ {error, {wrong_type, Type2, Child, Parent}};
+ false ->
+ verify_child(Children, Parent, Child, Type)
+ end.
+
+
+
+%%-------------------------------------------------------------------------
+%% httpc_subtree
+%%-------------------------------------------------------------------------
+httpc_subtree(doc) ->
+ ["Makes sure the httpc sub tree is correct."];
+httpc_subtree(suite) ->
+ [];
+httpc_subtree(Config) when is_list(Config) ->
+ tsp("httpc_subtree -> entry with"
+ "~n Config: ~p", [Config]),
+
+ tsp("httpc_subtree -> start inets service httpc with profile foo"),
+ {ok, Foo} = inets:start(httpc, [{profile, foo}]),
+
+ tsp("httpc_subtree -> "
+ "start stand-alone inets service httpc with profile bar"),
+ {ok, Bar} = inets:start(httpc, [{profile, bar}], stand_alone),
+
+ tsp("httpc_subtree -> retreive list of httpc instances"),
+ HttpcChildren = supervisor:which_children(httpc_profile_sup),
+ tsp("httpc_subtree -> HttpcChildren: ~n~p", [HttpcChildren]),
+
+ tsp("httpc_subtree -> verify httpc stand-alone instances"),
+ {value, {httpc_manager, _, worker, [httpc_manager]}} =
+ lists:keysearch(httpc_manager, 1, HttpcChildren),
+
+ tsp("httpc_subtree -> verify httpc (named) instances"),
+ {value,{{httpc,foo}, Pid, worker, [httpc_manager]}} =
+ lists:keysearch({httpc, foo}, 1, HttpcChildren),
+ false = lists:keysearch({httpc, bar}, 1, HttpcChildren),
+
+ tsp("httpc_subtree -> stop inets"),
+ inets:stop(httpc, Pid),
+
+ tsp("httpc_subtree -> done"),
+ ok.
+
+inet_port() ->
+ {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
+ {ok, Port} = inet:port(Socket),
+ gen_tcp:close(Socket),
+ Port.
+
+
+tsp(F) ->
+ tsp(F, []).
+tsp(F, A) ->
+ test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
+tsf(Reason) ->
+ test_server:fail(Reason).
+
diff --git a/lib/inets/test/inets_sup_SUITE_data/mime.types b/lib/inets/test/inets_sup_SUITE_data/mime.types
new file mode 100644
index 0000000000..e52d345ff7
--- /dev/null
+++ b/lib/inets/test/inets_sup_SUITE_data/mime.types
@@ -0,0 +1,3 @@
+# MIME type Extension
+text/html html htm
+text/plain asc txt
diff --git a/lib/inets/test/inets_sup_SUITE_data/simple.conf b/lib/inets/test/inets_sup_SUITE_data/simple.conf
new file mode 100644
index 0000000000..e1429b4a28
--- /dev/null
+++ b/lib/inets/test/inets_sup_SUITE_data/simple.conf
@@ -0,0 +1,6 @@
+Port 8888
+ServerName www.test
+SocketType ip_comm
+Modules mod_get
+ServerAdmin [email protected]
+
diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl
new file mode 100644
index 0000000000..6af2ad32f7
--- /dev/null
+++ b/lib/inets/test/inets_test_lib.erl
@@ -0,0 +1,302 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(inets_test_lib).
+
+-include("inets_test_lib.hrl").
+
+%% Various small utility functions
+-export([start_http_server/1, start_http_server_ssl/1]).
+-export([hostname/0]).
+-export([connect_bin/3, connect_byte/3, send/3, close/2]).
+-export([copy_file/3, copy_files/2, copy_dirs/2, del_dirs/1]).
+-export([info/4, log/4, debug/4, print/4]).
+-export([check_body/1]).
+-export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]).
+-export([non_pc_tc_maybe_skip/4, os_based_skip/1]).
+
+start_http_server(Conf) ->
+ application:load(inets),
+ ok = application:set_env(inets, services, [{httpd, Conf}]),
+ ok = application:start(inets).
+
+start_http_server_ssl(FileName) ->
+ application:start(ssl),
+ catch start_http_server(FileName).
+
+%% ----------------------------------------------------------------------
+%% print functions
+%%
+
+info(F, A, Mod, Line) ->
+ print("INF ", F, A, Mod, Line).
+
+log(F, A, Mod, Line) ->
+ print("LOG ", F, A, Mod, Line).
+
+debug(F, A, Mod, Line) ->
+ print("DBG ", F, A, Mod, Line).
+
+print(P, F, A, Mod, Line) ->
+ io:format("~s[~p:~p:~p] : " ++ F ++ "~n", [P, self(), Mod, Line| A]).
+
+print(F, A, Mod, Line) ->
+ print("", F, A, Mod, Line).
+
+hostname() ->
+ from($@, atom_to_list(node())).
+from(H, [H | T]) -> T;
+from(H, [_ | T]) -> from(H, T);
+from(_, []) -> [].
+
+
+copy_file(File, From, To) ->
+ file:copy(filename:join(From, File), filename:join(To, File)).
+
+copy_files(FromDir, ToDir) ->
+ {ok, Files} = file:list_dir(FromDir),
+ lists:foreach(fun(File) ->
+ FullPath = filename:join(FromDir, File),
+ case filelib:is_file(FullPath) of
+ true ->
+ file:copy(FullPath,
+ filename:join(ToDir, File));
+ false ->
+ ok
+ end
+ end, Files).
+
+
+copy_dirs(FromDirRoot, ToDirRoot) ->
+%% io:format("~w:copy_dirs -> entry with"
+%% "~n FromDirRoot: ~p"
+%% "~n ToDirRoot: ~p"
+%% "~n", [?MODULE, FromDirRoot, ToDirRoot]),
+ {ok, Files} = file:list_dir(FromDirRoot),
+ lists:foreach(
+ fun(FileOrDir) ->
+ %% Check if it's a directory or a file
+%% io:format("~w:copy_dirs -> check ~p"
+%% "~n", [?MODULE, FileOrDir]),
+ case filelib:is_dir(filename:join(FromDirRoot, FileOrDir)) of
+ true ->
+%% io:format("~w:copy_dirs -> ~p is a directory"
+%% "~n", [?MODULE, FileOrDir]),
+ FromDir = filename:join([FromDirRoot, FileOrDir]),
+ ToDir = filename:join([ToDirRoot, FileOrDir]),
+ ok = file:make_dir(ToDir),
+ copy_dirs(FromDir, ToDir);
+ false ->
+%% io:format("~w:copy_dirs -> ~p is a file"
+%% "~n", [?MODULE, FileOrDir]),
+ copy_file(FileOrDir, FromDirRoot, ToDirRoot)
+ end
+ end, Files).
+
+del_dirs(Dir) ->
+ case file:list_dir(Dir) of
+ {ok, []} ->
+ file:del_dir(Dir);
+ {ok, Files} ->
+ lists:foreach(fun(File) ->
+ FullPath = filename:join(Dir,File),
+ case filelib:is_dir(FullPath) of
+ true ->
+ del_dirs(FullPath),
+ file:del_dir(FullPath);
+ false ->
+ file:delete(FullPath)
+ end
+ end, Files);
+ _ ->
+ ok
+ end.
+
+check_body(Body) ->
+ case string:rstr(Body, "</html>") of
+ 0 ->
+ case string:rstr(Body, "</HTML>") of
+ 0 ->
+ test_server:format("Body ~p~n", [Body]),
+ test_server:fail(did_not_receive_whole_body);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+%% ----------------------------------------------------------------
+%% Conditional skip of testcases
+%%
+
+non_pc_tc_maybe_skip(Config, Condition, File, Line)
+ when is_list(Config) andalso is_function(Condition) ->
+ %% Check if we shall skip the skip
+ case os:getenv("TS_OS_BASED_SKIP") of
+ "false" ->
+ ok;
+ _ ->
+ case lists:keysearch(ts, 1, Config) of
+ {value, {ts, inets}} ->
+ %% Always run the testcase if we are using our own
+ %% test-server...
+ ok;
+ _ ->
+ case (catch Condition()) of
+ true ->
+ skip(non_pc_testcase, File, Line);
+ _ ->
+ ok
+ end
+ end
+ end.
+
+
+os_based_skip(any) ->
+ true;
+os_based_skip(Skippable) when is_list(Skippable) ->
+ {OsFam, OsName} =
+ case os:type() of
+ {_Fam, _Name} = FamAndName ->
+ FamAndName;
+ Fam ->
+ {Fam, undefined}
+ end,
+ case lists:member(OsFam, Skippable) of
+ true ->
+ true;
+ false ->
+ case lists:keysearch(OsFam, 1, Skippable) of
+ {value, {OsFam, OsName}} ->
+ true;
+ {value, {OsFam, OsNames}} when is_list(OsNames) ->
+ lists:member(OsName, OsNames);
+ _ ->
+ false
+ end
+ end;
+os_based_skip(_) ->
+ false.
+
+
+%% ----------------------------------------------------------------------
+%% Socket functions:
+%% open(SocketType, Host, Port) -> {ok, Socket} | {error, Reason}
+%% SocketType -> ssl | ip_comm
+%% Host -> atom() | string() | {A, B, C, D}
+%% Port -> integer()
+
+connect_bin(ssl, Host, Port) ->
+ ssl:start(),
+ %% Does not support ipv6 in old ssl
+ case ssl:connect(Host, Port, [binary, {packet,0}]) of
+ {ok, Socket} ->
+ {ok, Socket};
+ {error, Reason} ->
+ {error, Reason};
+ Error ->
+ Error
+ end;
+connect_bin(ip_comm, Host, Port) ->
+ Opts = [inet6, binary, {packet,0}],
+ connect(ip_comm, Host, Port, Opts).
+
+
+connect(ip_comm, Host, Port, Opts) ->
+ test_server:format("gen_tcp:connect(~p, ~p, ~p) ~n", [Host, Port, Opts]),
+ case gen_tcp:connect(Host,Port, Opts) of
+ {ok, Socket} ->
+ test_server:format("connect success~n", []),
+ {ok, Socket};
+ {error, nxdomain} ->
+ test_server:format("nxdomain opts: ~p~n", [Opts]),
+ connect(ip_comm, Host, Port, lists:delete(inet6, Opts));
+ {error, eafnosupport} ->
+ test_server:format("eafnosupport opts: ~p~n", [Opts]),
+ connect(ip_comm, Host, Port, lists:delete(inet6, Opts));
+ {error, {enfile,_}} ->
+ test_server:format("Error enfile~n", []),
+ {error, enfile};
+ Error ->
+ test_server:format("Unexpected error: "
+ "~n Error: ~p"
+ "~nwhen"
+ "~n Host: ~p"
+ "~n Port: ~p"
+ "~n Opts: ~p"
+ "~n", [Error, Host, Port, Opts]),
+ Error
+ end.
+
+connect_byte(ip_comm, Host, Port) ->
+ Opts = [inet6, {packet,0}],
+ connect(ip_comm, Host, Port, Opts);
+
+connect_byte(ssl, Host, Port) ->
+ ssl:start(),
+ %% Does not support ipv6 in old ssl
+ case ssl:connect(Host,Port,[{packet,0}]) of
+ {ok,Socket} ->
+ {ok,Socket};
+ {error,{enfile,_}} ->
+ {error, enfile};
+ Error ->
+ Error
+ end.
+
+send(ssl, Socket, Data) ->
+ ssl:send(Socket, Data);
+send(ip_comm,Socket,Data) ->
+ gen_tcp:send(Socket,Data).
+
+
+close(ssl,Socket) ->
+ catch ssl:close(Socket);
+close(ip_comm,Socket) ->
+ catch gen_tcp:close(Socket).
+
+millis() ->
+ erlang:now().
+
+millis_diff(A,B) ->
+ T1 = (element(1,A)*1000000) + element(2,A) + (element(3,A)/1000000),
+ T2 = (element(1,B)*1000000) + element(2,B) + (element(3,B)/1000000),
+ T1 - T2.
+
+hours(N) -> trunc(N * 1000 * 60 * 60).
+minutes(N) -> trunc(N * 1000 * 60).
+seconds(N) -> trunc(N * 1000).
+
+
+sleep(infinity) ->
+ receive
+ after infinity ->
+ ok
+ end;
+sleep(MSecs) ->
+ receive
+ after trunc(MSecs) ->
+ ok
+ end,
+ ok.
+
+
+skip(Reason, File, Line) ->
+ exit({skipped, {Reason, File, Line}}).
diff --git a/lib/inets/test/inets_test_lib.hrl b/lib/inets/test/inets_test_lib.hrl
new file mode 100644
index 0000000000..12a43fa136
--- /dev/null
+++ b/lib/inets/test/inets_test_lib.hrl
@@ -0,0 +1,104 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Define common macros for testing
+%%----------------------------------------------------------------------
+
+%% - Print macros -
+
+-ifdef(inets_debug).
+-define(DEBUG(F,A), inets_test_lib:debug(F, A, ?MODULE, ?LINE)).
+-else.
+-define(DEBUG(F,A),ok).
+-endif.
+
+-ifdef(inets_log).
+-define(LOG(F,A), inets_test_lib:log(F, A, ?MODULE, ?LINE)).
+-else.
+-define(LOG(F,A),ok).
+-endif.
+
+-define(INFO(F,A), inets_test_lib:info(F, A, ?MODULE, ?LINE)).
+-define(PRINT(F,A), inets_test_lib:print(F, A, ?MODULE, ?LINE)).
+
+
+%% - Macros stolen from the test server -
+
+-ifndef(line).
+-define(line,put(test_server_loc,{?MODULE,?LINE}),).
+-endif.
+
+
+%% - Test case macros -
+
+-define(EXPANDABLE(I, C, F), inets_test_lib:expandable(I, C, F)).
+-define(OS_BASED_SKIP(Skippable),
+ inets_test_lib:os_based_skip(Skippable)).
+
+-define(NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ inets_test_lib:non_pc_tc_maybe_skip(Config, Condition, ?MODULE, ?LINE)).
+
+
+
+%% - Misc macros -
+
+-define(UPDATE(K,V,C), inets_test_lib:update_config(K,V,C)).
+-define(CONFIG(K,C), inets_test_lib:get_config(K,C)).
+-define(HOSTNAME(), inets_test_lib:hostname()).
+-define(SZ(X), inets_test_lib:sz(X)).
+
+
+%% - Test case macros -
+
+-define(SKIP(Reason), inets_test_lib:skip(Reason)).
+-define(FAIL(Reason), inets_test_lib:fail(Reason, ?MODULE, ?LINE)).
+
+
+%% - Socket macros -
+
+-define(CONNECT(M,H,P), inets_test_lib:connect(M,H,P)).
+-define(SEND(M,S,D), inets_test_lib:send(M,S,D)).
+-define(CSEND(M,S,D,C,T), inets_test_lib:csend(M,S,D,C,T)).
+-define(CLOSE(M,S), inets_test_lib:close(M,S)).
+
+
+%% - Time macros -
+
+-define(HOURS(N), inets_test_lib:hours(N)).
+-define(MINS(N), inets_test_lib:minutes(N)).
+-define(SECS(N), inets_test_lib:seconds(N)).
+
+-define(WD_START(T), inets_test_lib:watchdog_start(T)).
+-define(WD_STOP(P), inets_test_lib:watchdog_stop(P)).
+
+-define(SLEEP(MSEC), inets_test_lib:sleep(MSEC)).
+-define(M(), inets_test_lib:millis()).
+-define(MDIFF(A,B), inets_test_lib:millis_diff(A,B)).
+
+
+%% - Process utility macros -
+
+-define(FLUSH(), inets_test_lib:flush_mqueue()).
+-define(ETRAP_GET(), inets_test_lib:trap_exit()).
+-define(ETRAP_SET(O), inets_test_lib:trap_exit(O)).
+
+
+
+
diff --git a/lib/inets/test/rules.mk b/lib/inets/test/rules.mk
new file mode 100644
index 0000000000..047c03b267
--- /dev/null
+++ b/lib/inets/test/rules.mk
@@ -0,0 +1,59 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+# ----------------------------------------------------
+# Make include file for otp
+#
+# Copyright (C) 1996, Ericsson Telecommunications
+# Author: Lars Thorsen
+# ----------------------------------------------------
+.SUFFIXES: .hrl .erl .jam .beam
+
+
+# ----------------------------------------------------
+# Common macros
+# ----------------------------------------------------
+DEFAULT_TARGETS = opt debug instr release release_docs clean docs
+
+# ----------------------------------------------------
+# Erlang language section
+# ----------------------------------------------------
+EMULATOR = beam
+ifeq ($(findstring vxworks,$(TARGET)),vxworks)
+# VxWorks object files should be compressed.
+# Other object files should have debug_info.
+ERL_COMPILE_FLAGS += +compressed
+else
+ifdef BOOTSTRAP
+ERL_COMPILE_FLAGS += +slim
+else
+ERL_COMPILE_FLAGS += +debug_info
+endif
+endif
+ERLC_WFLAGS = -W
+ERLC = erlc $(ERLC_WFLAGS) $(ERLC_FLAGS)
+ERL.beam = erl.beam -boot start_clean
+ERL.jam = erl -boot start_clean
+ERL = $(ERL.$(EMULATOR))
+
+ifeq ($(EBIN),)
+EBIN = .
+endif
+
+ESRC = .
+
+
+$(EBIN)/%.jam: $(ESRC)/%.erl
+ $(ERLC) -bjam $(ERL_COMPILE_FLAGS) -o$(EBIN) $<
+
+$(EBIN)/%.beam: $(ESRC)/%.erl
+ $(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(EBIN) $<
+
+.erl.jam:
+ $(ERLC) -bjam $(ERL_COMPILE_FLAGS) -o$(dir $@) $<
+
+.erl.beam:
+ $(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(dir $@) $<
+
+
+
+
+
diff --git a/lib/inets/test/tftp_SUITE.erl b/lib/inets/test/tftp_SUITE.erl
new file mode 100644
index 0000000000..5768fff88b
--- /dev/null
+++ b/lib/inets/test/tftp_SUITE.erl
@@ -0,0 +1,903 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(tftp_SUITE).
+
+-compile(export_all).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Includes and defines
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-include("tftp_test_lib.hrl").
+
+-define(START_DAEMON(PortX, OptionsX),
+ fun(Port, Options) ->
+ {ok, Pid} = ?VERIFY({ok, _Pid}, tftp:start([{port, Port} | Options])),
+ if
+ Port == 0 ->
+ {ok, ActualOptions} = ?IGNORE(tftp:info(Pid)),
+ {value, {port, ActualPort}} =
+ lists:keysearch(port, 1, ActualOptions),
+ {ActualPort, Pid};
+ true ->
+ {Port, Pid}
+ end
+ end(PortX, OptionsX)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% API
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+t() ->
+ tftp_test_lib:t([{?MODULE, all}]).
+
+t(Cases) ->
+ tftp_test_lib:t(Cases, default_config()).
+
+t(Cases, Config) ->
+ tftp_test_lib:t(Cases, Config).
+
+default_config() ->
+ [].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Test server callbacks
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init_per_testcase(Case, Config) ->
+ tftp_test_lib:init_per_testcase(Case, Config).
+
+fin_per_testcase(Case, Config) when is_list(Config) ->
+ tftp_test_lib:fin_per_testcase(Case, Config).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Top test case
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(doc) ->
+ ["Test suites for TFTP."];
+
+all(suite) ->
+ [
+ simple,
+ extra,
+ reuse_connection,
+ resend_client,
+ resend_server
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Simple
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+simple(doc) ->
+ ["Start the daemon and perform simple a read and write."];
+simple(suite) ->
+ [];
+simple(Config) when is_list(Config) ->
+ ?VERIFY(ok, application:start(inets)),
+
+ {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])),
+
+ %% Read fail
+ RemoteFilename = "tftp_temporary_remote_test_file.txt",
+ LocalFilename = "tftp_temporary_local_test_file.txt",
+ Blob = list_to_binary(lists:duplicate(2000, $1)),
+ %% Blob = <<"Some file contents\n">>,
+ Size = size(Blob),
+ ?IGNORE(file:delete(RemoteFilename)),
+ ?VERIFY({error, {client_open, enoent, _}},
+ tftp:read_file(RemoteFilename, binary, [{port, Port}])),
+
+ %% Write and read
+ ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, Blob, [{port, Port}])),
+ ?VERIFY({ok, Blob}, tftp:read_file(RemoteFilename, binary, [{port, Port}])),
+ ?IGNORE(file:delete(LocalFilename)),
+ ?VERIFY({ok, Size}, tftp:read_file(RemoteFilename, LocalFilename, [{port, Port}])),
+
+ %% Cleanup
+ unlink(DaemonPid),
+ exit(DaemonPid, kill),
+ ?VERIFY(ok, file:delete(LocalFilename)),
+ ?VERIFY(ok, file:delete(RemoteFilename)),
+ ?VERIFY(ok, application:stop(inets)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Extra
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+extra(doc) ->
+ ["Verify new stuff for IS 1.2."];
+extra(suite) ->
+ [];
+extra(Config) when is_list(Config) ->
+ ?VERIFY({'EXIT', {badarg,{fake_key, fake_flag}}},
+ tftp:start([{port, 0}, {fake_key, fake_flag}])),
+
+ {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])),
+
+ RemoteFilename = "tftp_extra_temporary_remote_test_file.txt",
+ LocalFilename = "tftp_extra_temporary_local_test_file.txt",
+ Blob = <<"Some file contents\n">>,
+ Size = size(Blob),
+ Host = "127.0.0.1",
+ Peer = {inet, Host, Port},
+ Generic =
+ [
+ {state, []},
+ {prepare, fun extra_prepare/6},
+ {open, fun extra_open/6},
+ {read, fun extra_read/1},
+ {write, fun extra_write/2},
+ {abort, fun extra_abort/3 }
+ ],
+ Options = [{host, Host},
+ {port, Port},
+ %%{ debug,all},
+ {callback, {".*", tftp_test_lib, Generic}}],
+ ?VERIFY(ok, file:write_file(LocalFilename, Blob)),
+ ?VERIFY({ok, [{count, Size}, Peer]},
+ tftp:write_file(RemoteFilename, LocalFilename, Options)),
+ ?VERIFY(ok, file:delete(LocalFilename)),
+
+ ?VERIFY({ok,[{bin, Blob}, Peer]},
+ tftp:read_file(RemoteFilename, LocalFilename, Options)),
+
+ %% Cleanup
+ unlink(DaemonPid),
+ exit(DaemonPid, kill),
+ ?VERIFY(ok, file:delete(LocalFilename)),
+ ?VERIFY(ok, file:delete(RemoteFilename)),
+ ok.
+
+-record(extra_state, {file, blksize, count, acc, peer}).
+
+%%-------------------------------------------------------------------
+%% Prepare
+%%-------------------------------------------------------------------
+
+extra_prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) ->
+ %% Client side
+ BlkSize = list_to_integer(tftp_test_lib:lookup_option("blksize", "512", SuggestedOptions)),
+ State = #extra_state{blksize = BlkSize, peer = Peer},
+ extra_open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State),
+ {ok, SuggestedOptions, State};
+extra_prepare(_Peer, _Access, _Bin, _Mode, _SuggestedOptions, _Initial) ->
+ {error, {undef, "Illegal callback options."}}.
+
+%%-------------------------------------------------------------------
+%% Open
+%%-------------------------------------------------------------------
+
+extra_open(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) ->
+ %% Server side
+ case extra_prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) of
+ {ok, AcceptedOptions, []} ->
+ BlkSize = list_to_integer(tftp_test_lib:lookup_option("blksize", "512", AcceptedOptions)),
+ State = #extra_state{blksize = BlkSize, peer = Peer},
+ extra_open(Peer, Access, LocalFilename, Mode, AcceptedOptions, State);
+ {error, {Code, Text}} ->
+ {error, {Code, Text}}
+ end;
+extra_open(_Peer, Access, LocalFilename, _Mode, NegotiatedOptions, #extra_state{} = State) ->
+ {File, Acc} =
+ case Access of
+ read ->
+ if
+ is_binary(LocalFilename) ->
+ {undefined, LocalFilename};
+ is_list(LocalFilename) ->
+ {ok, Bin} = file:read_file(LocalFilename),
+ {LocalFilename, Bin}
+ end;
+ write ->
+ {LocalFilename, []}
+ end,
+ %% Both sides
+ State2 = State#extra_state{file = File, acc = Acc, count = 0},
+ {ok, NegotiatedOptions, State2}.
+
+%%-------------------------------------------------------------------
+%% Read
+%%-------------------------------------------------------------------
+
+extra_read(#extra_state{acc = Bin} = State) when is_binary(Bin) ->
+ BlkSize = State#extra_state.blksize,
+ Count = State#extra_state.count + size(Bin),
+ if
+ size(Bin) >= BlkSize ->
+ <<Block:BlkSize/binary, Bin2/binary>> = Bin,
+ State2 = State#extra_state{acc = Bin2, count = Count},
+ {more, Block, State2};
+ size(Bin) < BlkSize ->
+ Res = [{count, Count}, State#extra_state.peer],
+ {last, Bin, Res}
+ end.
+
+%%-------------------------------------------------------------------
+%% Write
+%%-------------------------------------------------------------------
+
+extra_write(Bin, #extra_state{acc = List} = State) when is_binary(Bin), is_list(List) ->
+ Size = size(Bin),
+ BlkSize = State#extra_state.blksize,
+ if
+ Size == BlkSize ->
+ {more, State#extra_state{acc = [Bin | List]}};
+ Size < BlkSize ->
+ Bin2 = list_to_binary(lists:reverse([Bin | List])),
+ Res = [{bin, Bin2}, State#extra_state.peer],
+ file:write_file(State#extra_state.file, Bin2),
+ {last, Res}
+ end.
+
+%%-------------------------------------------------------------------
+%% Abort
+%%-------------------------------------------------------------------
+
+extra_abort(_Code, _Text, #extra_state{}) ->
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Re-send client
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+resend_client(doc) ->
+ ["Verify that the server behaves correctly when the client re-sends packets."];
+resend_client(suite) ->
+ [];
+resend_client(Config) when is_list(Config) ->
+ Host = {127, 0, 0, 1},
+ {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, all}])),
+
+ ?VERIFY(ok, resend_read_client(Host, Port, 10)),
+ ?VERIFY(ok, resend_read_client(Host, Port, 512)),
+ ?VERIFY(ok, resend_read_client(Host, Port, 1025)),
+
+ ?VERIFY(ok, resend_write_client(Host, Port, 10)),
+ ?VERIFY(ok, resend_write_client(Host, Port, 512)),
+ ?VERIFY(ok, resend_write_client(Host, Port, 1025)),
+
+ %% Cleanup
+ unlink(DaemonPid),
+ exit(DaemonPid, kill),
+ ok.
+
+resend_read_client(Host, Port, BlkSize) ->
+ RemoteFilename = "tftp_resend_read_client.tmp",
+ Block1 = lists:duplicate(BlkSize, $1),
+ Block2 = lists:duplicate(BlkSize, $2),
+ Block3 = lists:duplicate(BlkSize, $3),
+ Block4 = lists:duplicate(BlkSize, $4),
+ Block5 = lists:duplicate(BlkSize, $5),
+ Blocks = [Block1, Block2, Block3, Block4, Block5],
+ Blob = list_to_binary(Blocks),
+ ?VERIFY(ok, file:write_file(RemoteFilename, Blob)),
+
+ Timeout = timer:seconds(3),
+ ?VERIFY(timeout, recv(0)),
+
+ %% Open socket
+ {ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+
+ ReadList = [0, 1, RemoteFilename, 0, "octet", 0],
+ Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
+ NewPort =
+ if
+ BlkSize =:= 512 ->
+ %% Send READ
+ ReadBin = list_to_binary(ReadList),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
+
+ %% Sleep a while in order to provoke the server to re-send the packet
+ timer:sleep(Timeout + timer:seconds(1)),
+
+ %% Recv DATA #1 (the packet that the server think that we have lost)
+ {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, Data1Bin}, recv(Timeout)),
+ NewPort0;
+ true ->
+ %% Send READ
+ BlkSizeList = integer_to_list(BlkSize),
+ Options = ["blksize", 0, BlkSizeList, 0],
+ ReadBin = list_to_binary([ReadList | Options]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
+
+ %% Recv OACK
+ OptionAckBin = list_to_binary([0, 6 | Options]),
+ {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
+
+ %% Send ACK #0
+ Ack0Bin = <<0, 4, 0, 0>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort0, Ack0Bin)),
+
+ %% Send ACK #0 AGAIN (pretend that we timed out)
+ timer:sleep(timer:seconds(1)),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort0, Ack0Bin)),
+
+ %% Recv DATA #1 (the packet that the server think that we have lost)
+ ?VERIFY({udp, Socket, Host, NewPort0, Data1Bin}, recv(Timeout)),
+ NewPort0
+ end,
+
+ %% Recv DATA #1 AGAIN (the re-sent package)
+ ?VERIFY({udp, Socket, Host, NewPort, Data1Bin}, recv(Timeout)),
+
+ %% Send ACK #1
+ Ack1Bin = <<0, 4, 0, 1>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack1Bin)),
+
+ %% Recv DATA #2
+ Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
+ ?VERIFY({udp, Socket, Host, NewPort, Data2Bin}, recv(Timeout)),
+
+ %% Send ACK #2
+ Ack2Bin = <<0, 4, 0, 2>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)),
+
+ %% Recv DATA #3
+ Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
+ ?VERIFY({udp, Socket, Host, NewPort, Data3Bin}, recv(Timeout)),
+
+ %% Send ACK #3
+ Ack3Bin = <<0, 4, 0, 3>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack3Bin)),
+
+ %% Send ACK #3 AGAIN (pretend that we timed out)
+ timer:sleep(timer:seconds(1)),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack3Bin)),
+
+ %% Recv DATA #4 (the packet that the server think that we have lost)
+ Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
+ ?VERIFY({udp, Socket, Host, NewPort, Data4Bin}, recv(Timeout)),
+
+ %% Recv DATA #4 AGAIN (the re-sent package)
+ ?VERIFY({udp, Socket, Host, NewPort, Data4Bin}, recv(Timeout)),
+
+ %% Send ACK #2 which is out of range
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)),
+
+ %% Send ACK #4
+ Ack4Bin = <<0, 4, 0, 4>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack4Bin)),
+
+ %% Recv DATA #5
+ Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
+ ?VERIFY({udp, Socket, Host, NewPort, Data5Bin}, recv(Timeout)),
+
+ %% Send ACK #5
+ Ack5Bin = <<0, 4, 0, 5>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack5Bin)),
+
+ %% Close socket
+ ?VERIFY(ok, gen_udp:close(Socket)),
+
+ ?VERIFY(timeout, recv(Timeout)),
+ ?VERIFY(ok, file:delete(RemoteFilename)),
+ ok.
+
+resend_write_client(Host, Port, BlkSize) ->
+ RemoteFilename = "tftp_resend_write_client.tmp",
+ Block1 = lists:duplicate(BlkSize, $1),
+ Block2 = lists:duplicate(BlkSize, $2),
+ Block3 = lists:duplicate(BlkSize, $3),
+ Block4 = lists:duplicate(BlkSize, $4),
+ Block5 = lists:duplicate(BlkSize, $5),
+ Blocks = [Block1, Block2, Block3, Block4, Block5],
+ Blob = list_to_binary(Blocks),
+ ?IGNORE(file:delete(RemoteFilename)),
+ ?VERIFY({error, enoent}, file:read_file(RemoteFilename)),
+
+ Timeout = timer:seconds(3),
+ ?VERIFY(timeout, recv(0)),
+
+ %% Open socket
+ {ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+
+ WriteList = [0, 2, RemoteFilename, 0, "octet", 0],
+ NewPort =
+ if
+ BlkSize =:= 512 ->
+ %% Send WRITE
+ WriteBin = list_to_binary(WriteList),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, Port, WriteBin)),
+
+ %% Sleep a while in order to provoke the server to re-send the packet
+ timer:sleep(Timeout + timer:seconds(1)),
+
+ %% Recv ACK #0 (the packet that the server think that we have lost)
+ Ack0Bin = <<0, 4, 0, 0>>,
+ ?VERIFY({udp, Socket, Host, _, Ack0Bin}, recv(Timeout)),
+
+ %% Recv ACK #0 AGAIN (the re-sent package)
+ {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, Ack0Bin}, recv(Timeout)),
+ NewPort0;
+ true ->
+ %% Send WRITE
+ BlkSizeList = integer_to_list(BlkSize),
+ WriteBin = list_to_binary([WriteList, "blksize", 0, BlkSizeList, 0]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, Port, WriteBin)),
+
+ %% Sleep a while in order to provoke the server to re-send the packet
+ timer:sleep(timer:seconds(1)),
+
+ %% Recv OACK (the packet that the server think that we have lost)
+ OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]),
+ ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
+
+ %% Recv OACK AGAIN (the re-sent package)
+ {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
+ NewPort0
+ end,
+
+ %% Send DATA #1
+ Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data1Bin)),
+
+ %% Recv ACK #1
+ Ack1Bin = <<0, 4, 0, 1>>,
+ ?VERIFY({udp, Socket, Host, NewPort, Ack1Bin}, recv(Timeout)),
+
+ %% Send DATA #2
+ Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data2Bin)),
+
+ %% Recv ACK #2
+ Ack2Bin = <<0, 4, 0, 2>>,
+ ?VERIFY({udp, Socket, Host, NewPort, Ack2Bin}, recv(Timeout)),
+
+ %% Send DATA #3
+ Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data3Bin)),
+
+ %% Recv ACK #3
+ Ack3Bin = <<0, 4, 0, 3>>,
+ ?VERIFY({udp, Socket, Host, NewPort, Ack3Bin}, recv(Timeout)),
+
+ %% Send DATA #3 AGAIN (pretend that we timed out)
+ timer:sleep(timer:seconds(1)),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data3Bin)),
+
+ %% Recv ACK #3 AGAIN (the packet that the server think that we have lost)
+ ?VERIFY({udp, Socket, Host, NewPort, Ack3Bin}, recv(Timeout)),
+
+ %% Send DATA #2 which is out of range
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data2Bin)),
+
+ %% Send DATA #4
+ Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data4Bin)),
+
+ %% Recv ACK #4
+ Ack4Bin = <<0, 4, 0, 4>>,
+ ?VERIFY({udp, Socket, Host, NewPort, Ack4Bin}, recv(Timeout)),
+
+ %% Send DATA #5
+ Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data5Bin)),
+
+ %% Recv ACK #5
+ Ack5Bin = <<0, 4, 0, 5>>,
+ ?VERIFY({udp, Socket, Host, NewPort, Ack5Bin}, recv(Timeout)),
+
+ %% Close socket
+ ?VERIFY(ok, gen_udp:close(Socket)),
+
+ ?VERIFY(timeout, recv(Timeout)),
+ ?VERIFY({ok, Blob}, file:read_file(RemoteFilename)),
+ ?VERIFY(ok, file:delete(RemoteFilename)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Re-send server
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+resend_server(doc) ->
+ ["Verify that the server behaves correctly when the server re-sends packets."];
+resend_server(suite) ->
+ [];
+resend_server(Config) when is_list(Config) ->
+ Host = {127, 0, 0, 1},
+
+ ?VERIFY(ok, resend_read_server(Host, 10)),
+ ?VERIFY(ok, resend_read_server(Host, 512)),
+ ?VERIFY(ok, resend_read_server(Host, 1025)),
+
+ ?VERIFY(ok, resend_write_server(Host, 10)),
+ ?VERIFY(ok, resend_write_server(Host, 512)),
+ ?VERIFY(ok, resend_write_server(Host, 1025)),
+ ok.
+
+resend_read_server(Host, BlkSize) ->
+ RemoteFilename = "tftp_resend_read_server.tmp",
+ Block1 = lists:duplicate(BlkSize, $1),
+ Block2 = lists:duplicate(BlkSize, $2),
+ Block3 = lists:duplicate(BlkSize, $3),
+ Block4 = lists:duplicate(BlkSize, $4),
+ Block5 = lists:duplicate(BlkSize, $5),
+ Block6 = [],
+ Blocks = [Block1, Block2, Block3, Block4, Block5, Block6],
+ Blob = list_to_binary(Blocks),
+
+ Timeout = timer:seconds(3),
+ ?VERIFY(timeout, recv(0)),
+
+ %% Open daemon socket
+ {ok, DaemonSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+ {ok, DaemonPort} = ?IGNORE(inet:port(DaemonSocket)),
+
+ %% Open server socket
+ {ok, ServerSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+ ?IGNORE(inet:port(ServerSocket)),
+
+ %% Prepare client process
+ ReplyTo = self(),
+ ClientFun =
+ fun(Extra) ->
+ Options = [{port, DaemonPort}, {debug, brief}] ++ Extra,
+ Res = ?VERIFY({ok, Blob}, tftp:read_file(RemoteFilename, binary, Options)),
+ ReplyTo ! {self(), {tftp_client_reply, Res}},
+ exit(normal)
+ end,
+
+ ReadList = [0, 1, RemoteFilename, 0, "octet", 0],
+ Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
+ Ack1Bin = <<0, 4, 0, 1>>,
+ {ClientPort, ClientPid} =
+ if
+ BlkSize =:= 512 ->
+ %% Start client process
+ ClientPid0 = spawn_link(fun() -> ClientFun([]) end),
+
+ %% Recv READ
+ ReadBin = list_to_binary(ReadList),
+ {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, ReadBin}, recv(Timeout)),
+
+ %% Send DATA #1
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Data1Bin)),
+
+ %% Sleep a while in order to provoke the client to re-send the packet
+ timer:sleep(Timeout + timer:seconds(1)),
+
+ %% Recv ACK #1 (the packet that the server think that we have lost)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack1Bin}, recv(Timeout)),
+
+ %% Recv ACK #1 AGAIN (the re-sent package)
+ ?VERIFY({udp, ServerSocket, Host, _, Ack1Bin}, recv(Timeout)),
+ {ClientPort0, ClientPid0};
+ true ->
+ %% Start client process
+ BlkSizeList = integer_to_list(BlkSize),
+ ClientPid0 = spawn_link(fun() -> ClientFun([{"blksize", BlkSizeList}]) end),
+
+ %% Recv READ
+ Options = ["blksize", 0, BlkSizeList, 0],
+ ReadBin = list_to_binary([ReadList | Options]),
+ {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, ReadBin}, recv(Timeout)),
+
+ %% Send OACK
+ BlkSizeList = integer_to_list(BlkSize),
+ OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, OptionAckBin)),
+
+ %% Sleep a while in order to provoke the client to re-send the packet
+ timer:sleep(Timeout + timer:seconds(1)),
+
+ %% Recv ACK #0 (the packet that the server think that we have lost)
+ Ack0Bin = <<0, 4, 0, 0>>,
+ ?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack0Bin}, recv(Timeout)),
+
+ %% Recv ACK #0 AGAIN (the re-sent package)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack0Bin}, recv(Timeout)),
+
+ %% Send DATA #1
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Data1Bin)),
+
+ %% Recv ACK #1
+ ?VERIFY({udp, ServerSocket, Host, _, Ack1Bin}, recv(Timeout)),
+ {ClientPort0, ClientPid0}
+ end,
+
+ %% Send DATA #2
+ Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data2Bin)),
+
+ %% Recv ACK #2
+ Ack2Bin = <<0, 4, 0, 2>>,
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack2Bin}, recv(Timeout)),
+
+ %% Send DATA #3
+ Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)),
+
+ %% Recv ACK #3
+ Ack3Bin = <<0, 4, 0, 3>>,
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack3Bin}, recv(Timeout)),
+
+ %% Send DATA #3 AGAIN (pretend that we timed out)
+ timer:sleep(timer:seconds(1)),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)),
+
+ %% Recv ACK #3 AGAIN (the packet that the server think that we have lost)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack3Bin}, recv(Timeout)),
+
+ %% Send DATA #4
+ Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data4Bin)),
+
+ %% Recv ACK #4
+ Ack4Bin = <<0, 4, 0, 4>>,
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack4Bin}, recv(Timeout)),
+
+ %% Send DATA #3 which is out of range
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)),
+
+ %% Send DATA #5
+ Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data5Bin)),
+
+ %% Recv ACK #5
+ Ack5Bin = <<0, 4, 0, 5>>,
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack5Bin}, recv(Timeout)),
+
+ %% Send DATA #6
+ Data6Bin = list_to_binary([0, 3, 0, 6 | Block6]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data6Bin)),
+
+ %% Close daemon and server sockets
+ ?VERIFY(ok, gen_udp:close(ServerSocket)),
+ ?VERIFY(ok, gen_udp:close(DaemonSocket)),
+
+ ?VERIFY({ClientPid, {tftp_client_reply, {ok, Blob}}}, recv(Timeout)),
+
+ ?VERIFY(timeout, recv(Timeout)),
+ ok.
+
+resend_write_server(Host, BlkSize) ->
+ RemoteFilename = "tftp_resend_write_server.tmp",
+ Block1 = lists:duplicate(BlkSize, $1),
+ Block2 = lists:duplicate(BlkSize, $2),
+ Block3 = lists:duplicate(BlkSize, $3),
+ Block4 = lists:duplicate(BlkSize, $4),
+ Block5 = lists:duplicate(BlkSize, $5),
+ Block6 = [],
+ Blocks = [Block1, Block2, Block3, Block4, Block5, Block6],
+ Blob = list_to_binary(Blocks),
+ Size = size(Blob),
+
+ Timeout = timer:seconds(3),
+ ?VERIFY(timeout, recv(0)),
+
+ %% Open daemon socket
+ {ok, DaemonSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+ {ok, DaemonPort} = ?IGNORE(inet:port(DaemonSocket)),
+
+ %% Open server socket
+ {ok, ServerSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+ ?IGNORE(inet:port(ServerSocket)),
+
+ %% Prepare client process
+ ReplyTo = self(),
+ ClientFun =
+ fun(Extra) ->
+ Options = [{port, DaemonPort}, {debug, brief}] ++ Extra,
+ Res = ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, Blob, Options)),
+ ReplyTo ! {self(), {tftp_client_reply, Res}},
+ exit(normal)
+ end,
+
+ WriteList = [0, 2, RemoteFilename, 0, "octet", 0],
+ Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
+ {ClientPort, ClientPid} =
+ if
+ BlkSize =:= 512 ->
+ %% Start client process
+ ClientPid0 = spawn_link(fun() -> ClientFun([]) end),
+
+ %% Recv WRITE
+ WriteBin = list_to_binary(WriteList),
+ io:format("WriteBin ~p\n", [WriteBin]),
+ {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, WriteBin}, recv(Timeout)),
+
+ %% Send ACK #1
+ Ack0Bin = <<0, 4, 0, 0>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Ack0Bin)),
+
+ %% Sleep a while in order to provoke the client to re-send the packet
+ timer:sleep(Timeout + timer:seconds(1)),
+
+ %% Recv DATA #1 (the packet that the server think that we have lost)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)),
+
+ %% Recv DATA #1 AGAIN (the re-sent package)
+ ?VERIFY({udp, ServerSocket, Host, _, Data1Bin}, recv(Timeout)),
+ {ClientPort0, ClientPid0};
+ true ->
+ %% Start client process
+ BlkSizeList = integer_to_list(BlkSize),
+ ClientPid0 = spawn_link(fun() -> ClientFun([{"blksize", BlkSizeList}]) end),
+
+ %% Recv WRITE
+ Options = ["blksize", 0, BlkSizeList, 0],
+ WriteBin = list_to_binary([WriteList | Options]),
+ {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, WriteBin}, recv(Timeout)),
+
+ %% Send OACK
+ BlkSizeList = integer_to_list(BlkSize),
+ OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, OptionAckBin)),
+
+ %% Sleep a while in order to provoke the client to re-send the packet
+ timer:sleep(Timeout + timer:seconds(1)),
+
+ %% Recv DATA #1 (the packet that the server think that we have lost)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)),
+
+ %% Recv DATA #1 AGAIN (the re-sent package)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)),
+ {ClientPort0, ClientPid0}
+ end,
+
+ %% Send ACK #1
+ Ack1Bin = <<0, 4, 0, 1>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack1Bin)),
+
+ %% Recv DATA #2
+ Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Data2Bin}, recv(Timeout)),
+
+ %% Send ACK #2
+ Ack2Bin = <<0, 4, 0, 2>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack2Bin)),
+
+ %% Recv DATA #3
+ Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Data3Bin}, recv(Timeout)),
+
+ %% Send ACK #3
+ Ack3Bin = <<0, 4, 0, 3>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)),
+
+ %% Send ACK #3 AGAIN (pretend that we timed out)
+ timer:sleep(timer:seconds(1)),
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)),
+
+ %% Recv DATA #4 (the packet that the server think that we have lost)
+ Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Data4Bin}, recv(Timeout)),
+
+ %% Recv DATA #4 AGAIN (the re-sent package)
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Data4Bin}, recv(Timeout)),
+
+ %% Send ACK #4
+ Ack4Bin = <<0, 4, 0, 4>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack4Bin)),
+
+ %% Recv DATA #5
+ Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Data5Bin}, recv(Timeout)),
+
+ %% Send ACK #3 which is out of range
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)),
+
+ %% Send ACK #5
+ Ack5Bin = <<0, 4, 0, 5>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack5Bin)),
+
+ %% Recv DATA #6
+ Data6Bin = list_to_binary([0, 3, 0, 6 | Block6]),
+ ?VERIFY({udp, ServerSocket, Host, ClientPort, Data6Bin}, recv(Timeout)),
+
+ %% Send ACK #6
+ Ack6Bin = <<0, 4, 0, 6>>,
+ ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack6Bin)),
+
+ %% Close daemon and server sockets
+ ?VERIFY(ok, gen_udp:close(ServerSocket)),
+ ?VERIFY(ok, gen_udp:close(DaemonSocket)),
+
+ ?VERIFY({ClientPid, {tftp_client_reply, {ok, Size}}}, recv(Timeout)),
+
+ ?VERIFY(timeout, recv(Timeout)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+reuse_connection(doc) ->
+ ["Verify that the server can reuse an ongiong connection when same client resends request."];
+reuse_connection(suite) ->
+ [];
+reuse_connection(Config) when is_list(Config) ->
+ Host = {127, 0, 0, 1},
+ {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, all}])),
+
+ RemoteFilename = "reuse_connection.tmp",
+ BlkSize = 512,
+ Block1 = lists:duplicate(BlkSize, $1),
+ Block2 = lists:duplicate(BlkSize div 2, $2),
+ Blocks = [Block1, Block2],
+ Blob = list_to_binary(Blocks),
+ ?VERIFY(ok, file:write_file(RemoteFilename, Blob)),
+
+ Seconds = 3,
+ Timeout = timer:seconds(Seconds),
+ ?VERIFY(timeout, recv(0)),
+
+ %% Open socket
+ {ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
+
+ ReadList = [0, 1, RemoteFilename, 0, "octet", 0],
+ Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
+
+ %% Send READ
+ TimeoutList = integer_to_list(Seconds),
+ Options = ["timeout", 0, TimeoutList, 0],
+ ReadBin = list_to_binary([ReadList | Options]),
+ ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
+
+ %% Send yet another READ for same file
+ ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
+
+ %% Recv OACK
+ OptionAckBin = list_to_binary([0, 6 | Options]),
+ {udp, _, _, NewPort, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
+
+ %% Send ACK #0
+ Ack0Bin = <<0, 4, 0, 0>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack0Bin)),
+
+ %% Recv DATA #1
+ ?VERIFY({udp, Socket, Host, NewPort, Data1Bin}, recv(Timeout)),
+
+ %% Send ACK #1
+ Ack1Bin = <<0, 4, 0, 1>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack1Bin)),
+
+ %% Recv DATA #2
+ Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
+ ?VERIFY({udp, Socket, Host, NewPort, Data2Bin}, recv(Timeout)),
+
+ %% Send ACK #2
+ Ack2Bin = <<0, 4, 0, 2>>,
+ ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)),
+
+ %% Close socket
+ ?VERIFY(ok, gen_udp:close(Socket)),
+
+ ?VERIFY(timeout, recv(Timeout)),
+ ?VERIFY(ok, file:delete(RemoteFilename)),
+
+ %% Cleanup
+ unlink(DaemonPid),
+ exit(DaemonPid, kill),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Goodies
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+recv(Timeout) ->
+ receive
+ Msg ->
+ Msg
+ after Timeout ->
+ timeout
+ end.
diff --git a/lib/inets/test/tftp_test_lib.erl b/lib/inets/test/tftp_test_lib.erl
new file mode 100644
index 0000000000..3729309b0e
--- /dev/null
+++ b/lib/inets/test/tftp_test_lib.erl
@@ -0,0 +1,307 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(tftp_test_lib).
+
+-compile(export_all).
+
+-include("tftp_test_lib.hrl").
+
+%%
+%% -----
+%%
+
+init_per_testcase(_Case, Config) when is_list(Config) ->
+ io:format("\n ", []),
+ ?IGNORE(application:stop(inets)),
+ Config.
+
+fin_per_testcase(_Case, Config) when is_list(Config) ->
+ ?IGNORE(application:stop(inets)),
+ Config.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Infrastructure for test suite
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+error(Actual, Mod, Line) ->
+ (catch global:send(tftp_global_logger, {failed, Mod, Line})),
+ log("<ERROR> Bad result: ~p\n", [Actual], Mod, Line),
+ Label = lists:concat([Mod, "(", Line, ") unexpected result"]),
+ et:report_event(60, Mod, Mod, Label,
+ [{line, Mod, Line}, {error, Actual}]),
+ case global:whereis_name(tftp_test_case_sup) of
+ undefined ->
+ ignore;
+ Pid ->
+ Fail = #'REASON'{mod = Mod, line = Line, desc = Actual},
+ Pid ! {fail, self(), Fail}
+ end,
+ Actual.
+
+log(Format, Args, Mod, Line) ->
+ case global:whereis_name(tftp_global_logger) of
+ undefined ->
+ io:format(user, "~p(~p): " ++ Format,
+ [Mod, Line] ++ Args);
+ Pid ->
+ io:format(Pid, "~p(~p): " ++ Format,
+ [Mod, Line] ++ Args)
+ end.
+
+default_config() ->
+ [].
+
+t() ->
+ t([{?MODULE, all}]).
+
+t(Cases) ->
+ t(Cases, default_config()).
+
+t(Cases, Config) ->
+ process_flag(trap_exit, true),
+ Res = lists:flatten(do_test(Cases, Config)),
+ io:format("Res: ~p\n", [Res]),
+ display_result(Res),
+ Res.
+
+do_test({Mod, Fun}, Config) when is_atom(Mod), is_atom(Fun) ->
+ case catch apply(Mod, Fun, [suite]) of
+ [] ->
+ io:format("Eval: ~p:", [{Mod, Fun}]),
+ Res = eval(Mod, Fun, Config),
+ {R, _, _} = Res,
+ io:format(" ~p\n", [R]),
+ Res;
+
+ Cases when is_list(Cases) ->
+ io:format("Expand: ~p ...\n", [{Mod, Fun}]),
+ Map = fun(Case) when is_atom(Case)-> {Mod, Case};
+ (Case) -> Case
+ end,
+ do_test(lists:map(Map, Cases), Config);
+
+ {req, _, {conf, Init, Cases, Finish}} ->
+ case (catch apply(Mod, Init, [Config])) of
+ Conf when is_list(Conf) ->
+ io:format("Expand: ~p ...\n", [{Mod, Fun}]),
+ Map = fun(Case) when is_atom(Case)-> {Mod, Case};
+ (Case) -> Case
+ end,
+ Res = do_test(lists:map(Map, Cases), Conf),
+ (catch apply(Mod, Finish, [Conf])),
+ Res;
+
+ {'EXIT', {skipped, Reason}} ->
+ io:format(" => skipping: ~p\n", [Reason]),
+ [{skipped, {Mod, Fun}, Reason}];
+
+ Error ->
+ io:format(" => failed: ~p\n", [Error]),
+ [{failed, {Mod, Fun}, Error}]
+ end;
+
+ {'EXIT', {undef, _}} ->
+ io:format("Undefined: ~p\n", [{Mod, Fun}]),
+ [{nyi, {Mod, Fun}, ok}];
+
+ Error ->
+ io:format("Ignoring: ~p: ~p\n", [{Mod, Fun}, Error]),
+ [{failed, {Mod, Fun}, Error}]
+ end;
+do_test(Mod, Config) when is_atom(Mod) ->
+ Res = do_test({Mod, all}, Config),
+ Res;
+do_test(Cases, Config) when is_list(Cases) ->
+ [do_test(Case, Config) || Case <- Cases];
+do_test(Bad, _Config) ->
+ [{badarg, Bad, ok}].
+
+eval(Mod, Fun, Config) ->
+ TestCase = {?MODULE, Mod, Fun},
+ Label = lists:concat(["TEST CASE: ", Fun]),
+ et:report_event(40, ?MODULE, Mod, Label ++ " started",
+ [TestCase, Config]),
+ global:register_name(tftp_test_case_sup, self()),
+ Flag = process_flag(trap_exit, true),
+ Config2 = Mod:init_per_testcase(Fun, Config),
+ Pid = spawn_link(?MODULE, do_eval, [self(), Mod, Fun, Config2]),
+ R = wait_for_evaluator(Pid, Mod, Fun, Config2, []),
+ Mod:fin_per_testcase(Fun, Config2),
+ global:unregister_name(tftp_test_case_sup),
+ process_flag(trap_exit, Flag),
+ R.
+
+wait_for_evaluator(Pid, Mod, Fun, Config, Errors) ->
+ TestCase = {?MODULE, Mod, Fun},
+ Label = lists:concat(["TEST CASE: ", Fun]),
+ receive
+ {done, Pid, ok} when Errors == [] ->
+ et:report_event(40, Mod, ?MODULE, Label ++ " ok",
+ [TestCase, Config]),
+ {ok, {Mod, Fun}, Errors};
+ {done, Pid, {ok, _}} when Errors == [] ->
+ et:report_event(40, Mod, ?MODULE, Label ++ " ok",
+ [TestCase, Config]),
+ {ok, {Mod, Fun}, Errors};
+ {done, Pid, Fail} ->
+ et:report_event(20, Mod, ?MODULE, Label ++ " failed",
+ [TestCase, Config, {return, Fail}, Errors]),
+ {failed, {Mod,Fun}, Fail};
+ {'EXIT', Pid, {skipped, Reason}} ->
+ et:report_event(20, Mod, ?MODULE, Label ++ " skipped",
+ [TestCase, Config, {skipped, Reason}]),
+ {skipped, {Mod, Fun}, Errors};
+ {'EXIT', Pid, Reason} ->
+ et:report_event(20, Mod, ?MODULE, Label ++ " crashed",
+ [TestCase, Config, {'EXIT', Reason}]),
+ {crashed, {Mod, Fun}, [{'EXIT', Reason} | Errors]};
+ {fail, Pid, Reason} ->
+ wait_for_evaluator(Pid, Mod, Fun, Config, Errors ++ [Reason])
+ end.
+
+do_eval(ReplyTo, Mod, Fun, Config) ->
+ case (catch apply(Mod, Fun, [Config])) of
+ {'EXIT', {skipped, Reason}} ->
+ ReplyTo ! {'EXIT', self(), {skipped, Reason}};
+ Other ->
+ ReplyTo ! {done, self(), Other}
+ end,
+ unlink(ReplyTo),
+ exit(shutdown).
+
+display_result([]) ->
+ io:format("OK\n", []);
+display_result(Res) when is_list(Res) ->
+ Ok = [MF || {ok, MF, _} <- Res],
+ Nyi = [MF || {nyi, MF, _} <- Res],
+ Skipped = [{MF, Reason} || {skipped, MF, Reason} <- Res],
+ Failed = [{MF, Reason} || {failed, MF, Reason} <- Res],
+ Crashed = [{MF, Reason} || {crashed, MF, Reason} <- Res],
+ display_summary(Ok, Nyi, Skipped, Failed, Crashed),
+ display_skipped(Skipped),
+ display_failed(Failed),
+ display_crashed(Crashed).
+
+display_summary(Ok, Nyi, Skipped, Failed, Crashed) ->
+ io:format("\nTest case summary:\n", []),
+ display_summary(Ok, "successful"),
+ display_summary(Nyi, "not yet implemented"),
+ display_summary(Skipped, "skipped"),
+ display_summary(Failed, "failed"),
+ display_summary(Crashed, "crashed"),
+ io:format("\n", []).
+
+display_summary(Res, Info) ->
+ io:format(" ~w test cases ~s\n", [length(Res), Info]).
+
+display_skipped([]) ->
+ ok;
+display_skipped(Skipped) ->
+ io:format("Skipped test cases:\n", []),
+ F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end,
+ lists:foreach(F, Skipped),
+ io:format("\n", []).
+
+
+display_failed([]) ->
+ ok;
+display_failed(Failed) ->
+ io:format("Failed test cases:\n", []),
+ F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end,
+ lists:foreach(F, Failed),
+ io:format("\n", []).
+
+display_crashed([]) ->
+ ok;
+display_crashed(Crashed) ->
+ io:format("Crashed test cases:\n", []),
+ F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end,
+ lists:foreach(F, Crashed),
+ io:format("\n", []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% generic callback
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(generic_state, {state, prepare, open, read, write, abort}).
+
+prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
+ State = lookup_option(state, mandatory, Initial),
+ Prepare = lookup_option(prepare, mandatory, Initial),
+ Open = lookup_option(open, mandatory, Initial),
+ Read = lookup_option(read, mandatory, Initial),
+ Write = lookup_option(write, mandatory, Initial),
+ Abort = lookup_option(abort, mandatory, Initial),
+ case Prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of
+ {ok, AcceptedOptions, NewState} ->
+ {ok,
+ AcceptedOptions,
+ #generic_state{state = NewState,
+ prepare = Prepare,
+ open = Open,
+ read = Read,
+ write = Write,
+ abort = Abort}};
+ Other ->
+ Other
+ end.
+
+open(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
+ case prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) of
+ {ok, SuggestedOptions2, GenericState} ->
+ open(Peer, Access, LocalFilename, Mode, SuggestedOptions2, GenericState);
+ Other ->
+ Other
+ end;
+open(Peer, Access, LocalFilename, Mode, SuggestedOptions, #generic_state{state = State, open = Open} = GenericState) ->
+ case Open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of
+ {ok, SuggestedOptions2, NewState} ->
+ {ok, SuggestedOptions2, GenericState#generic_state{state = NewState}};
+ Other ->
+ Other
+ end.
+
+read(#generic_state{state = State, read = Read} = GenericState) ->
+ case Read(State) of
+ {more, DataBlock, NewState} ->
+ {more, DataBlock, GenericState#generic_state{state = NewState}};
+ Other ->
+ Other
+ end.
+
+write(DataBlock, #generic_state{state = State, write = Write} = GenericState) ->
+ case Write(DataBlock, State) of
+ {more, NewState} ->
+ {more, GenericState#generic_state{state = NewState}};
+ Other ->
+ Other
+ end.
+
+abort(Code, Text, #generic_state{state = State, abort = Abort}) ->
+ Abort(Code, Text, State).
+
+lookup_option(Key, Default, Options) ->
+ case lists:keysearch(Key, 1, Options) of
+ {value, {_, Val}} ->
+ Val;
+ false ->
+ Default
+ end.
+
diff --git a/lib/inets/test/tftp_test_lib.hrl b/lib/inets/test/tftp_test_lib.hrl
new file mode 100644
index 0000000000..da4b065976
--- /dev/null
+++ b/lib/inets/test/tftp_test_lib.hrl
@@ -0,0 +1,43 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-record('REASON', {mod, line, desc}).
+
+-define(LOG(Format, Args),
+ tftp_test_lib:log(Format, Args, ?MODULE, ?LINE)).
+
+-define(ERROR(Reason),
+ tftp_test_lib:error(Reason, ?MODULE, ?LINE)).
+
+-define(VERIFY(Expected, Expr),
+ fun() ->
+ AcTuAlReS = (catch (Expr)),
+ case AcTuAlReS of
+ Expected -> ?LOG("Ok, ~p\n", [AcTuAlReS]);
+ _ -> ?ERROR(AcTuAlReS)
+ end,
+ AcTuAlReS
+ end()).
+
+-define(IGNORE(Expr),
+ fun() ->
+ AcTuAlReS = (catch (Expr)),
+ ?LOG("Ok, ~p\n", [AcTuAlReS]),
+ AcTuAlReS
+ end()).
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 8ed4f0c192..34c18c4d13 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -1,27 +1,41 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1997-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
-INETS_VSN = 5.2.0.1
-PRE_VSN =
-APP_VSN = "inets-$(INETS_VSN)$(PRE_VSN)"
+APPLICATION = inets
+INETS_VSN = 5.3
+PRE_VSN =-p10
+APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
+
+TICKETS = \
+ OTP-8016 \
+ OTP-8056 \
+ OTP-8103 \
+ OTP-8106 \
+ OTP-8312 \
+ OTP-8315 \
+ OTP-8327 \
+ OTP-8349 \
+ OTP-8351 \
+ OTP-8359 \
+ OTP-8371
-TICKETS = OTP-8204 OTP-8206 OTP-8247 OTP-8248 OTP-8249 OTP-8258 OTP-8280
+TICKETS_5_2 = OTP-8204 OTP-8206 OTP-8247 OTP-8248 OTP-8249 OTP-8258 OTP-8280
TICKETS_5_1_3 = OTP-8154
diff --git a/lib/orber/doc/src/notes.xml b/lib/orber/doc/src/notes.xml
index 1185b7658f..816ec77d61 100644
--- a/lib/orber/doc/src/notes.xml
+++ b/lib/orber/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2009</year>
+ <year>1997</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>Orber Release Notes</title>
@@ -36,6 +36,18 @@
<title>Orber 3.6.15</title>
<section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>
+ Removed obsolete SSL dependency.</p>
+ <p>
+ Own Id: OTP-8374 Aux Id:</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
<title>Fixed Bugs and Malfunctions</title>
<list type="bulleted">
<item>
diff --git a/lib/orber/vsn.mk b/lib/orber/vsn.mk
index ccba631fb5..8bfa13eb03 100644
--- a/lib/orber/vsn.mk
+++ b/lib/orber/vsn.mk
@@ -2,7 +2,8 @@
ORBER_VSN = 3.6.15
TICKETS = OTP-8353 \
- OTP-8354
+ OTP-8354 \
+ OTP-8374
TICKETS_3.6.14 = OTP-8201
diff --git a/system/doc/reference_manual/modules.xml b/system/doc/reference_manual/modules.xml
index f4885be480..0dbc0ab56b 100644
--- a/system/doc/reference_manual/modules.xml
+++ b/system/doc/reference_manual/modules.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>Modules</title>
@@ -170,17 +170,21 @@ fact(0) -> % |
<section>
<title>Types and function specifications</title>
- <p>The current release implements types and function specifications
- as described in
+ <p>A similar syntax as for module attributes is used for
+ specifying types and function specifications.
+ </p>
+ <pre>
+-type my_type() :: atom() | integer().
+-spec my_function(integer()) -> integer().
+ </pre>
+ <p>Read more in <seealso marker="typespec">Types and Function specifications</seealso>.
+ </p>
+ <p>
+ The desciption is based on
<url href="http://www.erlang.org/eeps/eep-0008.html">EEP8 -
-Types and function specifications</url>.
-
- <note>
- <p>The implementation and EEP8 may not exactly correspond to
- each other. In a future release, type and function specifications
- will be described in this reference manual.</p>
- </note>
- </p>
+ Types and function specifications</url>
+ which will not be further updated.
+ </p>
</section>
</section>
diff --git a/system/doc/reference_manual/part.xml b/system/doc/reference_manual/part.xml
index aebeaf335a..8151f4c4e1 100644
--- a/system/doc/reference_manual/part.xml
+++ b/system/doc/reference_manual/part.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
-
+
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
-
+
</legalnotice>
<title>Erlang Reference Manual</title>
@@ -32,6 +32,7 @@
<xi:include href="patterns.xml"/>
<xi:include href="modules.xml"/>
<xi:include href="functions.xml"/>
+ <xi:include href="typespec.xml"/>
<xi:include href="expressions.xml"/>
<xi:include href="macros.xml"/>
<xi:include href="records.xml"/>
diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml
new file mode 100755
index 0000000000..a3660713e4
--- /dev/null
+++ b/system/doc/reference_manual/typespec.xml
@@ -0,0 +1,464 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2003</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Types and Function Specifications</title>
+ <prepared>Kostis Sagonas, Tobias Lindahl, Kenneth Lundin</prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>typespec.xml</file>
+ </header>
+
+ <section>
+ <title>Introduction of Types</title>
+ <p>
+ Although Erlang is a dynamically typed language this section describes
+ an extension to the Erlang language for declaring sets of Erlang terms
+ to form a particular type, effectively forming a specific sub-type of the
+ set of all Erlang terms.
+ </p>
+ <p>
+ Subsequently, these types can be used to specify types of record fields
+ and the argument and return types of functions.
+ </p>
+ <p>
+ Type information can be used to document function interfaces,
+ provide more information for bug detection tools such as <c>Dialyzer</c>,
+ and can be exploited by documentation tools such as <c>Edoc</c> for
+ generating program documentation of various forms.
+ It is expected that the type language described in this document will
+ supersede and replace the purely comment-based <c>@type</c> and
+ <c>@spec</c> declarations used by <c>Edoc</c>.
+ </p>
+ <warning>
+ The syntax and semantics described here is still preliminary and might be
+ slightly changed and extended before it becomes officially supported.
+ The plan is that this will happen in R14B.
+ </warning>
+ </section>
+ <section>
+ <marker id="syntax"></marker>
+ <title>Types and their Syntax</title>
+ <p>
+ Types describe sets of Erlang terms.
+ Types consist and are built from a set of predefined types (e.g. <c>integer()</c>,
+ <c>atom()</c>, <c>pid()</c>, ...) described below.
+ Predefined types represent a typically infinite set of Erlang terms which
+ belong to this type.
+ For example, the type <c>atom()</c> stands for the set of all Erlang atoms.
+ </p>
+ <p>
+ For integers and atoms, we allow for singleton types (e.g. the integers <c>-1</c>
+ and <c>42</c> or the atoms <c>'foo'</c> and <c>'bar'</c>).
+
+ All other types are built using unions of either predefined types or singleton
+ types. In a type union between a type and one of its sub-types the sub-type is
+ absorbed by the super-type and the union is subsequently treated as if the
+ sub-type was not a constituent of the union. For example, the type union:
+ </p>
+ <pre>
+ atom() | 'bar' | integer() | 42</pre>
+ <p>
+ describes the same set of terms as the type union:
+ </p>
+ <pre>
+atom() | integer()</pre>
+ <p>
+ Because of sub-type relations that exist between types, types form a lattice
+ where the topmost element, any(), denotes the set of all Erlang terms and
+ the bottom-most element, none(), denotes the empty set of terms.
+ </p>
+ <p>
+ The set of predefined types and the syntax for types is given below:
+ </p>
+ <pre><![CDATA[
+Type :: any() %% The top type, the set of all Erlang terms.
+ | none() %% The bottom type, contains no terms.
+ | pid()
+ | port()
+ | ref()
+ | [] %% nil
+ | Atom
+ | Binary
+ | float()
+ | Fun
+ | Integer
+ | List
+ | Tuple
+ | Union
+ | UserDefined %% described in Section 2
+
+Union :: Type1 | Type2
+
+Atom :: atom()
+ | Erlang_Atom %% 'foo', 'bar', ...
+
+Binary :: binary() %% <<_:_ * 8>>
+ | <<>>
+ | <<_:Erlang_Integer>> %% Base size
+ | <<_:_*Erlang_Integer>> %% Unit size
+ | <<_:Erlang_Integer, _:_*Erlang_Integer>>
+
+Fun :: fun() %% any function
+ | fun((...) -> Type) %% any arity, returning Type
+ | fun(() -> Type)
+ | fun((TList) -> Type)
+
+Integer :: integer()
+ | Erlang_Integer %% ..., -1, 0, 1, ... 42 ...
+ | Erlang_Integer..Erlang_Integer %% specifies an integer range
+
+List :: list(Type) %% Proper list ([]-terminated)
+ | improper_list(Type1, Type2) %% Type1=contents, Type2=termination
+ | maybe_improper_list(Type1, Type2) %% Type1 and Type2 as above
+
+Tuple :: tuple() %% stands for a tuple of any size
+ | {}
+ | {TList}
+
+TList :: Type
+ | Type, TList
+]]></pre>
+ <p>
+ Because lists are commonly used, they have shorthand type notations.
+ The type <c>list(T)</c> has the shorthand <c>[T]</c>. The shorthand <c>[T,...]</c> stands for
+ the set of non-empty proper lists whose elements are of type <c>T</c>.
+ The only difference between the two shorthands is that <c>[T]</c> may be an
+ empty list but <c>[T,...]</c> may not.
+ </p>
+ <p>
+ Notice that the shorthand for <c>list()</c>, i.e. the list of elements of unknown type,
+ is <c>[_]</c> (or <c>[any()]</c>), not <c>[]</c>.
+ The notation <c>[]</c> specifies the singleton type for the empty list.
+ </p>
+ <p>
+ For convenience, the following types are also built-in.
+ They can be thought as predefined aliases for the type unions also shown in
+ the table. (Some type unions below slightly abuse the syntax of types.)
+ </p>
+ <table>
+ <row>
+ <cell><b>Built-in type</b></cell><cell><b>Stands for</b></cell>
+ </row>
+ <row>
+ <cell><c>term()</c></cell><cell><c>any()</c></cell>
+ </row>
+ <row>
+ <cell><c>bool()</c></cell><cell><c>'false' | 'true'</c></cell>
+ </row>
+ <row>
+ <cell><c>byte()</c></cell><cell><c>0..255</c></cell>
+ </row>
+ <row>
+ <cell><c>char()</c></cell><cell><c>0..16#10ffff</c></cell>
+ </row>
+ <row>
+ <cell><c>non_neg_integer()</c></cell><cell><c>0..</c></cell>
+ </row>
+ <row>
+ <cell><c>pos_integer()</c></cell><cell><c>1..</c></cell>
+ </row>
+ <row>
+ <cell><c>neg_integer()</c></cell><cell><c>..-1</c></cell>
+ </row>
+ <row>
+ <cell><c>number()</c></cell><cell><c>integer() | float()</c></cell>
+ </row>
+ <row>
+ <cell><c>list()</c></cell><cell><c>[any()]</c></cell>
+ </row>
+ <row>
+ <cell><c>maybe_improper_list()</c></cell><cell><c>maybe_improper_list(any(), any())</c></cell>
+ </row>
+ <row>
+ <cell><c>maybe_improper_list(T)</c></cell><cell><c>maybe_improper_list(T, any())</c></cell>
+ </row>
+ <row>
+ <cell><c>string()</c></cell><cell><c>[char()]</c></cell>
+ </row>
+ <row>
+ <cell><c>nonempty_string()</c></cell><cell><c>[char(),...]</c></cell>
+ </row>
+ <row>
+ <cell><c>iolist()</c></cell><cell><c>maybe_improper_list(
+char() | binary() | iolist(), binary() | [])</c></cell>
+ </row>
+ <row>
+ <cell><c>module()</c></cell><cell><c>atom()</c></cell>
+ </row>
+ <row>
+ <cell><c>mfa()</c></cell><cell><c>{atom(),atom(),byte()}</c></cell>
+ </row>
+ <row>
+ <cell><c>node()</c></cell><cell><c>atom()</c></cell>
+ </row>
+ <row>
+ <cell><c>timeout()</c></cell><cell><c>'infinity' | non_neg_integer()</c></cell>
+ </row>
+ <row>
+ <cell><c>no_return()</c></cell><cell><c>none()</c></cell>
+ </row>
+ </table>
+
+ <p>
+ Users are not allowed to define types with the same names as the predefined or
+ built-in ones.
+ This is checked by the compiler and its violation results in a compilation
+ error.
+ (For bootstrapping purposes, it can also result to just a warning if this
+ involves a built-in type which has just been introduced.)
+ </p>
+ <note>
+ The following built-in list types also exist,
+ but they are expected to be rarely used. Hence, they have long names:
+ </note>
+ <pre>
+nonempty_maybe_improper_list(Type) :: nonempty_maybe_improper_list(Type, any())
+nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any())
+ </pre>
+ <p>
+ where the following two types
+ define the set of Erlang terms one would expect:
+ </p>
+ <pre>
+nonempty_improper_list(Type1, Type2)
+nonempty_maybe_improper_list(Type1, Type2)
+ </pre>
+ <p>
+ Also for convenience, we allow for record notation to be used.
+ Records are just shorthands for the corresponding tuples.
+ </p>
+ <pre>
+Record :: #Erlang_Atom{}
+ | #Erlang_Atom{Fields}
+ </pre>
+ <p>
+ Records have been extended to possibly contain type information.
+ This is described in the sub-section <seealso marker="#typeinrecords">"Type information in record declarations"</seealso> below.
+ </p>
+ </section>
+
+ <section>
+ <title>Type declarations of user-defined types</title>
+ <p>
+ As seen, the basic syntax of a type is an atom followed by closed
+ parentheses. New types are declared using '-type' compiler attributes
+ as in the following:
+ </p>
+ <pre>
+-type my_type() :: Type.
+ </pre>
+ <p>
+ where the type name is an atom (<c>'my_type'</c> in the above) followed by
+ parenthesis. Type is a type as defined in the previous section.
+ A current restriction is that Type can contain only predefined types
+ or user-defined types which have been previously defined.
+ This restriction is enforced by the compiler and results in a
+ compilation error. (A similar restriction currently exists for records).
+ </p>
+ <p>
+ This means that currently general recursive types cannot be defined.
+ Lifting this restriction is future work.
+ </p>
+ <p>
+ Type declarations can also be parameterized by including type variables
+ between the parentheses. The syntax of type variables is the same as
+ Erlang variables (starts with an upper case letter).
+ Naturally, these variables can - and should - appear on the RHS of the
+ definition. A concrete example appears below:
+ </p>
+ <pre>
+-type orddict(Key, Val) :: [{Key, Val}].
+ </pre>
+
+ </section>
+
+ <marker id="typeinrecords"/>
+ <section>
+ <title>
+ Type information in record declarations
+ </title>
+ <p>
+ The types of record fields can be specified in the declaration of the
+ record. The syntax for this is:
+ </p>
+ <pre>
+-record(rec, {field1 :: Type1, field2, field3 :: Type3}).
+ </pre>
+ <p>
+ For fields without type annotations, their type defaults to any().
+ I.e., the above is a shorthand for:
+ </p>
+ <pre>
+-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).
+ </pre>
+ <p>
+ In the presence of initial values for fields,
+ the type must be declared after the initialization as in the following:
+ </p>
+ <pre>
+-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).
+ </pre>
+ <p>
+ Naturally, the initial values for fields should be compatible
+ with (i.e. a member of) the corresponding types.
+ This is checked by the compiler and results in a compilation error
+ if a violation is detected. For fields without initial values,
+ the singleton type <c>'undefined'</c> is added to all declared types.
+ In other words, the following two record declarations have identical
+ effects:
+ </p>
+ <pre>
+-record(rec, {f1 = 42 :: integer(),
+ f2 :: float(),
+ f3 :: 'a' | 'b').
+
+-record(rec, {f1 = 42 :: integer(),
+ f2 :: 'undefined' | float(),
+ f3 :: 'undefined' | 'a' | 'b').
+ </pre>
+ <p>
+ For this reason, it is recommended that records contain initializers,
+ whenever possible.
+ </p>
+ <p>
+ Any record, containing type information or not, once defined,
+ can be used as a type using the syntax:
+ </p>
+ <pre>
+#rec{}
+ </pre>
+ <p>
+ In addition, the record fields can be further specified when using
+ a record type by adding type information about the field in the following
+ manner:
+ </p>
+ <pre>
+#rec{some_field :: Type}
+ </pre>
+ <p>
+ Any unspecified fields are assumed to have the type in the original
+ record declaration.
+ </p>
+ </section>
+
+ <section>
+ <title>Specifications (contracts) for functions</title>
+ <p>
+ A contract (or specification) for a function is given using the new
+ compiler attribute <c>'-spec'</c>. The basic format is as follows:
+ </p>
+ <pre>
+-spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.
+ </pre>
+ <p>
+ The arity of the function has to match the number of arguments,
+ or else a compilation error occurs.
+ </p>
+ <p>
+ This form can also be used in header files (.hrl) to declare type
+ information for exported functions.
+ Then these header files can be included in files that (implicitly or
+ explicitly) import these functions.
+ </p>
+ <p>
+ For most uses within a given module, the following shorthand is allowed:
+ </p>
+ <pre>
+-spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.
+ </pre>
+ <p>
+ Also, for documentation purposes, argument names can be given:
+ </p>
+ <pre>
+-spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.
+ </pre>
+ <p>
+ A function specification can be overloaded.
+ That is, it can have several types, separated by a semicolon (<c>;</c>):
+ </p>
+ <pre>
+-spec foo(T1, T2) -> T3
+ ; (T4, T5) -> T6.
+ </pre>
+ <p>
+ A current restriction, which currently results in a warning
+ (OBS: not an error) by the compiler, is that the domains of the argument
+ types cannot be overlapping.
+ For example, the following specification results in a warning:
+ </p>
+ <pre>
+-spec foo(pos_integer()) -> pos_integer()
+ ; (integer()) -> integer().
+ </pre>
+ <p>
+ Type variables can be used in specifications to specify relations for
+ the input and output arguments of a function.
+ For example, the following specification defines the type of a
+ polymorphic identity function:
+ </p>
+ <pre>
+-spec id(X) -> X.
+ </pre>
+ <p>
+ However, note that the above specification does not restrict the input
+ and output type in any way.
+ We can constrain these types by guard-like subtype constraints:
+ </p>
+ <pre>
+-spec id(X) -> X when is_subtype(X, tuple()).
+ </pre>
+ <p>
+ and provide bounded quantification. Currently,
+ the <c>is_subtype/2</c> guard is the only guard which can
+ be used in a <c>'-spec'</c> attribute.
+ </p>
+ <p>
+ The scope of an <c>is_subtype/2</c> constraint is the
+ <c>(...) -> RetType</c>
+ specification after which it appears. To avoid confusion,
+ we suggest that different variables are used in different constituents of
+ an overloaded contract as in the example below:
+ </p>
+ <pre>
+-spec foo({X, integer()}) -> X when is_subtype(X, atom())
+ ; ([Y]) -> Y when is_subtype(Y, number()).
+ </pre>
+ <p>
+ Some functions in Erlang are not meant to return;
+ either because they define servers or because they are used to
+ throw exceptions as the function below:
+ </p>
+ <pre>
+my_error(Err) -> erlang:throw({error, Err}).
+ </pre>
+ <p>
+ For such functions we recommend the use of the special no_return()
+ type for their "return", via a contract of the form:
+ </p>
+ <pre>
+-spec my_error(term()) -> no_return().
+ </pre>
+ </section>
+</chapter>
+