diff --git a/.gitignore b/.gitignore index 0e9f3a8..b9d98c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,8 @@ -# Output files -*.slo -*.la -*.lo -*.o -*.so -/.libs -/doc - # Local files +/build /dist *~* *.swp -*.swo .clang_complete GPATH GRTAGS diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c0d18d..ec0bb7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,11 @@ cmake_minimum_required(VERSION 2.8.12) project(davrods C) -set(IRODS_VERSION "4.2.1" CACHE STRING "iRODS client library version") +set(IRODSRT_VERSION "4.2.2" CACHE STRING "iRODS client library version") -set(DAVRODS_VERSION "${IRODS_VERSION}_1.3.0") +set(DAVRODS_FEATURE "1.4.0") +set(DAVRODS_VERSION "${IRODSRT_VERSION}_${DAVRODS_FEATURE}") +set(DAVRODS_VERSION_DEB "${IRODSRT_VERSION}-${DAVRODS_FEATURE}") find_program(APXS apxs DOC "Apache/HTTPD extension tool location") if(NOT APXS) @@ -28,11 +30,21 @@ set(IRODS_INCLUDE_DIR "/usr/include/irods" CACHE STRING "iRODS include directory if(IS_DIRECTORY /etc/httpd/conf.modules.d) # This looks like CentOS7's httpd, we know where to put our files on install. - set(INSTALLABLE_ON_THIS_SYSTEM TRUE) + set(SYSTEM_LOOKS_LIKE "CentOS7") +elseif(IS_DIRECTORY /etc/apache2/mods-available) + # This looks like Debian/Ubuntu, we know where to put our files on install. + # Thanks to @holtgrewe for the initial porting work. + set(SYSTEM_LOOKS_LIKE "Debian") + + # Debian notes, postinstall: + # + # a2enmod davrods + # a2enmod dav + # apache2ctl restart else() - set(INSTALLABLE_ON_THIS_SYSTEM FALSE) + set(SYSTEM_LOOKS_LIKE "--Unknown--") message(WARNING " -Davrods' build system currently only supports the cmake 'install' target on CentOS7-like systems. +Davrods' build system currently only supports the cmake 'install' target on CentOS7 and Debian-like systems. If you are running CentOS or similar, make sure httpd is installed before running cmake: This build system requires certain HTTPD directories to be in place. If you are running a different Linux distribution or if your HTTPD configuration layout differs otherwise, you can install Davrods manually after building. See the instructions in README.md.") endif() @@ -65,49 +77,78 @@ add_compile_options(-Wall set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro,-z,now") set(SOURCES - mod_davrods.c - auth.c - common.c - config.c - prop.c - propdb.c - repo.c - lock_local.c) + src/mod_davrods.c + src/auth.c + src/common.c + src/config.c + src/prop.c + src/propdb.c + src/repo.c + src/lock_local.c + src/byterange.c) add_library(mod_davrods SHARED ${SOURCES}) # Remove "lib" prefix from module SO file. set_property(TARGET mod_davrods PROPERTY PREFIX "") -if(INSTALLABLE_ON_THIS_SYSTEM) +# Enable OS-dependent installation targets +if(SYSTEM_LOOKS_LIKE STREQUAL "CentOS7") install(TARGETS mod_davrods DESTINATION ${HTTPD_BUILDSYS_MODULE_DIR}) - install(FILES davrods.conf + install(FILES aux/rpm/davrods.conf DESTINATION /etc/httpd/conf.modules.d RENAME 10-davrods.conf) - install(FILES davrods-vhost.conf - davrods-anonymous-vhost.conf + install(FILES aux/rpm/davrods-vhost.conf + aux/rpm/davrods-anonymous-vhost.conf DESTINATION /etc/httpd/conf.d/) - install(FILES irods_environment.json + install(FILES aux/common/irods_environment.json DESTINATION /etc/httpd/irods/) - install(FILES aux/listing/head.html - aux/listing/header.html - aux/listing/footer.html - aux/listing/README.md + install(FILES aux/common/listing/head.html + aux/common/listing/header.html + aux/common/listing/footer.html + aux/common/listing/README.md DESTINATION /etc/httpd/irods/) - install(FILES README.md COPYING COPYING.LESSER + install(FILES README.md COPYING COPYING.LESSER changelog.txt + DESTINATION /usr/share/doc/davrods-${DAVRODS_VERSION}/) + + install(DIRECTORY + DESTINATION /var/lib/davrods) + +elseif(SYSTEM_LOOKS_LIKE STREQUAL "Debian") + install(TARGETS mod_davrods + DESTINATION ${HTTPD_BUILDSYS_MODULE_DIR}) + + install(FILES aux/deb/davrods.conf + DESTINATION /etc/apache2/mods-available + RENAME davrods.load) + + install(FILES aux/deb/davrods-vhost.conf + aux/deb/davrods-anonymous-vhost.conf + DESTINATION /etc/apache2/sites-available/) + + install(FILES aux/common/irods_environment.json + DESTINATION /etc/apache2/irods/) + + install(FILES aux/common/listing/head.html + aux/common/listing/header.html + aux/common/listing/footer.html + aux/common/listing/README.md + DESTINATION /etc/apache2/irods/) + + install(FILES README.md COPYING COPYING.LESSER changelog.txt DESTINATION /usr/share/doc/davrods-${DAVRODS_VERSION}/) install(DIRECTORY DESTINATION /var/lib/davrods) endif() -if(INSTALLABLE_ON_THIS_SYSTEM) +if(SYSTEM_LOOKS_LIKE STREQUAL "CentOS7") set(CPACK_MONOLITHIC_INSTALL 1) set(CPACK_CMAKE_GENERATOR "Unix Makefiles") set(CPACK_GENERATOR "RPM") @@ -115,10 +156,7 @@ if(INSTALLABLE_ON_THIS_SYSTEM) set(CPACK_PACKAGE_VENDOR "Utrecht University ") set(CPACK_PACKAGE_CONTACT "Utrecht University ") set(CPACK_PACKAGE_VERSION "${DAVRODS_VERSION}") - #set(CPACK_PACKAGE_VERSION_MAJOR "4") - #set(CPACK_PACKAGE_VERSION_MINOR "2") - #set(CPACK_PACKAGE_VERSION_PATCH "1") - set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/package/description.txt") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/aux/common/description.txt") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A DAV level 2 compliant Apache interface to iRODS") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING.LESSER") @@ -126,17 +164,18 @@ if(INSTALLABLE_ON_THIS_SYSTEM) set(CPACK_RPM_PACKAGE_RELEASE "1") set(CPACK_RPM_PACKAGE_LICENSE "LGPLv3+") - set(CPACK_RPM_PACKAGE_REQUIRES "httpd >= 2.4, irods-runtime = ${IRODS_VERSION}") + set(CPACK_RPM_PACKAGE_REQUIRES "httpd >= 2.4, irods-runtime = ${IRODSRT_VERSION}") set(CPACK_RPM_PACKAGE_URL "https://github.com/UtrechtUniversity/davrods") - set(CPACK_RPM_CHANGELOG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/package/changelog.txt") + set(CPACK_RPM_CHANGELOG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/changelog.txt") set(CPACK_RPM_PACKAGE_AUTOREQ 0) set(CPACK_RPM_PACKAGE_AUTOPROV 0) - set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/package/postinst.sh") + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/aux/rpm/postinst.sh") set(CPACK_RPM_USER_FILELIST "%doc /usr/share/doc/davrods-${DAVRODS_VERSION}/README.md" "%doc /usr/share/doc/davrods-${DAVRODS_VERSION}/COPYING" "%doc /usr/share/doc/davrods-${DAVRODS_VERSION}/COPYING.LESSER" + "%doc /usr/share/doc/davrods-${DAVRODS_VERSION}/changelog.txt" "%config(noreplace) /etc/httpd/conf.modules.d/10-davrods.conf" "%config(noreplace) /etc/httpd/conf.d/davrods-vhost.conf" "%config(noreplace) /etc/httpd/conf.d/davrods-anonymous-vhost.conf" @@ -149,4 +188,28 @@ if(INSTALLABLE_ON_THIS_SYSTEM) set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_RPM_PACKAGE_RELEASE}") include(CPack) + +elseif(SYSTEM_LOOKS_LIKE STREQUAL "Debian") + set(CPACK_MONOLITHIC_INSTALL 1) + set(CPACK_CMAKE_GENERATOR "Unix Makefiles") + set(CPACK_GENERATOR "DEB") + set(CPACK_PACKAGE_NAME "davrods") + set(CPACK_PACKAGE_VENDOR "Utrecht University ") + set(CPACK_PACKAGE_CONTACT "Utrecht University ") + set(CPACK_PACKAGE_VERSION "${DAVRODS_VERSION_DEB}") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/aux/common/description.txt") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A DAV level 2 compliant Apache interface to iRODS") + + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING.LESSER") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "apache2 (>= 2.4), irods-runtime (= ${IRODSRT_VERSION})") + set(CPACK_DEBIAN_PACKAGE_SECTION "httpd") + set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/aux/deb/postinst") + + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + + include(CPack) + endif() diff --git a/README.md b/README.md index f7df78f..6a925be 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ page. ## License ## -Copyright (c) 2016, 2017, Utrecht University. +Copyright (c) 2016 - 2018, Utrecht University. Davrods is licensed under the GNU Lesser General Public License version 3 or higher (LGPLv3+). See the COPYING.LESSER file for details. diff --git a/aux/README.md b/aux/README.md new file mode 100644 index 0000000..b1e2e33 --- /dev/null +++ b/aux/README.md @@ -0,0 +1,7 @@ +# Auxiliary files + +This directory contains Davrods' auxiliary files, e.g. Apache +configuration files and RPM/DEB metadata. + +Files in `common/` are platform-independent. Other files are +platform-specific. diff --git a/package/description.txt b/aux/common/description.txt similarity index 100% rename from package/description.txt rename to aux/common/description.txt diff --git a/irods_environment.json b/aux/common/irods_environment.json similarity index 100% rename from irods_environment.json rename to aux/common/irods_environment.json diff --git a/aux/listing/README.md b/aux/common/listing/README.md similarity index 100% rename from aux/listing/README.md rename to aux/common/listing/README.md diff --git a/aux/listing/footer.html b/aux/common/listing/footer.html similarity index 100% rename from aux/listing/footer.html rename to aux/common/listing/footer.html diff --git a/aux/listing/head.html b/aux/common/listing/head.html similarity index 100% rename from aux/listing/head.html rename to aux/common/listing/head.html diff --git a/aux/listing/header.html b/aux/common/listing/header.html similarity index 100% rename from aux/listing/header.html rename to aux/common/listing/header.html diff --git a/aux/deb/davrods-anonymous-vhost.conf b/aux/deb/davrods-anonymous-vhost.conf new file mode 100644 index 0000000..7edb555 --- /dev/null +++ b/aux/deb/davrods-anonymous-vhost.conf @@ -0,0 +1,203 @@ +## -*- mode: apache -*- +## vim: ft=apache ts=4 sw=4 et +## +## davrods-anonymous-vhost.conf +## +## Davrods is a mod_dav WebDAV provider. Configuration directives for +## Davrods should be placed in a block. +## +## Below we provide an example vhost configuration that enables Davrods +## in anonymous access mode. See the file 'davrods-vhost.conf' for a +## vhost configuration *with* authentication. +## +## Note that in order to use anonymous access, your iRODS permissions +## need to be set correctly as well: +## 1) Create the anonymous user if it doesn't yet exist (or use a +## different user - see option DavRodsAnonymousLogin) +## 2) Grant read/write/own access to anonymous where appropriate +## 3) Point the DavRodsExposedRoot to a location where the anonymous +## user has at least 'read' access. +## +# +# +# # Enter your server name here. +# ServerName public.dav.example.com +# +# # NB: Some webdav clients expect the server to implement webdav at the root +# # location (they execute an OPTIONS request to verify existence of webdav +# # protocol support). +# +# +# +# # Options needed to enable Davrods. {{{ +# # ================================= +# +# # Disable built-in Apache directory listings - Davrods will +# # provide this instead. +# DirectoryIndex disabled +# +# # Don't restrict access on the HTTP side - auth must be +# # disabled for anonymous access to work. +# AuthType None +# Require all granted +# +# # The DAV provider for this location. +# # +# # Davrods implements multiple dav providers, use either: +# # - davrods-nolocks: WebDAV class 1 provider, no support for locking +# # - davrods-locallock (recommended): WebDAV class 2 provider, uses a DBM lock database local to this webserver +# # +# # Note that the davrods-locallock provider requires an apache-writable lockdb directory +# # (/var/lib/davrods, or a path specified using the DavRodsLockDB directive - see further down this file). +# # The RPM/DEB distribution creates this directory for you. +# # +# Dav davrods-locallock +# +# # }}} +# +# # Davrods configuration directives. {{{ +# # ================================= +# +# # Location of the iRODS environment file that specifies the iRODS +# # client configuration used by Davrods. +# # +# # Note: When options in the iRODS environment file overlap with Davrods +# # configuration directives, as with the host, port, and zone of the +# # iRODS server, the values specified in the iRODS environment file are +# # NOT used. +# # +# DavRodsEnvFile /etc/apache2/irods/irods_environment.json +# +# # The following options can be used to customize Davrods for your environment. +# # These options and their default values are provided below. +# # Having these directives commented out has the effect of enabling +# # the listed default option. +# +# # Hostname and port of the iRODS server to connect to. +# # +# #DavRodsServer localhost 1247 +# +# # Data grid zone id of the iRODS server. +# # +# #DavRodsZone tempZone +# +# # Authentication type to use when connecting to iRODS. +# # +# # Supported authentication types are 'Native' and 'Pam'. +# # ('Native' corresponds to what was formerly called 'Standard' auth in iRODS). +# # +# #DavRodsAuthScheme Native +# +# # Anonymous mode switch. +# # +# # (default: Off) +# # When 'Off', basic authentication is required to log into +# # Davrods. AuthType must be set to 'Basic' and AuthBasicProvider +# # must be set to 'irods'. There must also be a 'Require valid-user' +# # line. +# # +# # When 'On', Davrods will log into iRODS with a preset +# # username and password (See options DavRodsAnonymousLogin and +# # DavRodsAuthScheme). AuthType must be unset, or set to None, +# # and there should be no 'Require valid-user' line +# # (instead: Require all granted). +# # +# # This allows users to access Davrods without being prompted +# # for a login, making public access and embedding in web pages +# # easier. +# DavRodsAnonymousMode On +# +# # iRODS authentication options for Davrods anonymous mode. +# # +# # This option is used only when DavRodsAnonymousMode is set to +# # 'On'. +# # +# # Specifies the username and password to use for anonymous login. +# # The default value is 'anonymous', with an empty password. +# # (this user, if created, is treated specially within iRODS) +# # +# # The special 'anonymous' iRODS user normally requires the +# # DavRodsAuthScheme to be set to Native. +# # +# DavRodsAnonymousLogin "anonymous" "" +# +# # iRODS default resource to use for file uploads. +# # +# # Leave this empty to let the server decide. +# # +# #DavRodsDefaultResource "" +# +# # Exposed top collection of iRODS. +# # +# # Note that the collection chosen MUST be readable for all users, +# # otherwise they will experience problems when mounting the drive. +# # For example, if you set it to "Home", then as a rodsadmin user +# # execute the icommand: ichmod read public /zone-name/home +# # +# # Davrods accepts the following values for exposed-root: +# # - 'Zone' (collection /zone-name) +# # - 'Home' (collection /zone-name/home) +# # - 'User' (collection /zone-name/home/logged-in-username) +# # - full-path (named collection, must be absolute path, starts with /) +# # +# #DavRodsExposedRoot User +# +# # Size of the buffers used for file transfer to/from the iRODS server. +# # +# # The default values optimize performance for regular configurations. +# # The Tx buffer is used for transfer to iRODS (PUT), while the Rx +# # buffer is used for transfer from iRODS (GET). +# # Buffer sizes lower than 1024K will lead to decreased file transfer performance. +# # +# # The buffer sizes are specified as a number of kibibytes ('1' means 1024 bytes). +# # We use 4 MiB transfer buffers by default. +# # +# #DavRodsTxBufferKbs 4096 +# #DavRodsRxBufferKbs 4096 +# +# # Optionally Davrods can support rollback for aborted uploads. In this scenario +# # a temporary file is created during upload and upon succesful transfer this +# # temporary file is renamed to the destination filename. +# # NB: Please note that the use of temporary files may conflict with your iRODS +# # data policies (e.g. a acPostProcForPut would act upon the temporary filename). +# # Valid values for this option are 'On'/'Yes' and 'Off'/'No'. +# # +# #DavRodsTmpfileRollback Off +# +# # When using the davrods-locallock DAV provider (see the 'Dav' +# # directive above), this option can be used to set the location of the +# # lock database. +# # +# #DavRodsLockDB /var/lib/davrods/lockdb_locallock +# +# # Davrods provides read-only HTML directory listings for web browser access. +# # The UI is basic and unstyled by default, but can be modified with three +# # theming directives. +# # +# # Each of these directives points to a local HTML file that must be readable +# # by the apache user. +# # +# # The default value for each of these is "", which disables the option. +# # +# # - DavRodsHtmlHead is inserted in the HEAD tag of the listing. +# # - DavRodsHtmlHeader is inserted at the top of the listing's BODY tag. +# # - DavRodsHtmlFooter is inserted at the bottom of the listing's BODY tag. +# # +# # Example HTML files are provided in /etc/apache2/irods. You should edit these +# # before enabling them. +# # +# # To see an example, uncomment the following three lines: +# # +# #DavRodsHtmlHead "/etc/apache2/irods/head.html" +# #DavRodsHtmlHeader "/etc/apache2/irods/header.html" +# #DavRodsHtmlFooter "/etc/apache2/irods/footer.html" +# +# # }}} +# +# +# +# # We strongly recommend to enable Davrods only over SSL. +# # For HTTPS-only access, change the port at the start of the vhost block +# # from 80 to 443 and add your SSL options below. +# +# diff --git a/aux/deb/davrods-vhost.conf b/aux/deb/davrods-vhost.conf new file mode 100644 index 0000000..6f7f6e2 --- /dev/null +++ b/aux/deb/davrods-vhost.conf @@ -0,0 +1,200 @@ +## -*- mode: apache -*- +## vim: ft=apache ts=4 sw=4 et +## +## davrods-vhost.conf +## +## Davrods is a mod_dav WebDAV provider. Configuration directives for +## Davrods should be placed in a block. +## +## Below we provide an example vhost configuration that enables Davrods +## using its default options. +## +# +# +# # Enter your server name here. +# ServerName dav.example.com +# +# # NB: Some webdav clients expect the server to implement webdav at the root +# # location (they execute an OPTIONS request to verify existence of webdav +# # protocol support). +# +# +# +# # Options needed to enable Davrods. {{{ +# # ================================= +# +# # Disable built-in Apache directory listings - Davrods will +# # provide this instead. +# DirectoryIndex disabled +# +# # Restrict access to authenticated users. +# AuthType Basic +# Require valid-user +# +# # The realm name that will be shown to clients upon authentication +# AuthName DAV +# +# # Use the 'irods' HTTP basic authentication provider, implemented by Davrods. +# AuthBasicProvider irods +# +# # The DAV provider for this location. +# # +# # Davrods implements multiple dav providers, use either: +# # - davrods-nolocks: WebDAV class 1 provider, no support for locking +# # - davrods-locallock (recommended): WebDAV class 2 provider, uses a DBM lock database local to this webserver +# # +# # Note that the davrods-locallock provider requires an apache-writable lockdb directory +# # (/var/lib/davrods, or a path specified using the DavRodsLockDB directive - see further down this file). +# # The RPM/DEB distribution creates this directory for you. +# # +# Dav davrods-locallock +# +# # }}} +# +# # Davrods configuration directives. {{{ +# # ================================= +# +# # Location of the iRODS environment file that specifies the iRODS +# # client configuration used by Davrods. +# # +# # Note: When options in the iRODS environment file overlap with Davrods +# # configuration directives, as with the host, port, and zone of the +# # iRODS server, the values specified in the iRODS environment file are +# # NOT used. +# # +# DavRodsEnvFile /etc/apache2/irods/irods_environment.json +# +# # The following options can be used to customize Davrods for your environment. +# # These options and their default values are provided below. +# # Having these directives commented out has the effect of enabling +# # the listed default option. +# +# # Hostname and port of the iRODS server to connect to. +# # +# #DavRodsServer localhost 1247 +# +# # Data grid zone id of the iRODS server. +# # +# #DavRodsZone tempZone +# +# # Authentication type to use when connecting to iRODS. +# # +# # Supported authentication types are 'Native' and 'Pam'. +# # ('Native' corresponds to what was formerly called 'Standard' auth in iRODS). +# # +# #DavRodsAuthScheme Native +# +# # Anonymous mode switch. +# # +# # (default: Off) +# # When 'Off', basic authentication is required to log into +# # Davrods. AuthType must be set to 'Basic' and AuthBasicProvider +# # must be set to 'irods'. There must also be a 'Require valid-user' +# # line. +# # +# # When 'On', Davrods will log into iRODS with a preset +# # username and password (See options DavRodsAnonymousLogin and +# # DavRodsAuthScheme). AuthType must be unset, or set to None, +# # and there should be no 'Require valid-user' line +# # (instead: Require all granted). +# # +# # This allows users to access Davrods without being prompted +# # for a login, making public access and embedding in web pages +# # easier. +# #DavRodsAnonymousMode Off +# +# # iRODS authentication options for Davrods anonymous mode. +# # +# # This option is used only when DavRodsAnonymousMode is set to +# # 'On'. +# # +# # Specifies the username and password to use for anonymous login. +# # The default value is 'anonymous', with an empty password. +# # (this user, if created, is treated specially within iRODS) +# # +# # The special 'anonymous' iRODS user normally requires the +# # DavRodsAuthScheme to be set to Native. +# # +# #DavRodsAnonymousLogin "anonymous" "" +# +# # iRODS default resource to use for file uploads. +# # +# # Leave this empty to let the server decide. +# # +# #DavRodsDefaultResource "" +# +# # Exposed top collection of iRODS. +# # +# # Note that the collection chosen MUST be readable for all users, +# # otherwise they will experience problems when mounting the drive. +# # For example, if you set it to "Home", then as a rodsadmin user +# # execute the icommand: ichmod read public /zone-name/home +# # +# # Davrods accepts the following values for exposed-root: +# # - 'Zone' (collection /zone-name) +# # - 'Home' (collection /zone-name/home) +# # - 'User' (collection /zone-name/home/logged-in-username) +# # - full-path (named collection, must be absolute path, starts with /) +# # +# #DavRodsExposedRoot User +# +# # Size of the buffers used for file transfer to/from the iRODS server. +# # +# # The default values optimize performance for regular configurations. +# # The Tx buffer is used for transfer to iRODS (PUT), while the Rx +# # buffer is used for transfer from iRODS (GET). +# # Buffer sizes lower than 1024K will lead to decreased file transfer performance. +# # +# # The buffer sizes are specified as a number of kibibytes ('1' means 1024 bytes). +# # We use 4 MiB transfer buffers by default. +# # +# #DavRodsTxBufferKbs 4096 +# #DavRodsRxBufferKbs 4096 +# +# # Optionally Davrods can support rollback for aborted uploads. In this scenario +# # a temporary file is created during upload and upon succesful transfer this +# # temporary file is renamed to the destination filename. +# # NB: Please note that the use of temporary files may conflict with your iRODS +# # data policies (e.g. a acPostProcForPut would act upon the temporary filename). +# # Valid values for this option are 'On'/'Yes' and 'Off'/'No'. +# # +# #DavRodsTmpfileRollback Off +# +# # When using the davrods-locallock DAV provider (see the 'Dav' +# # directive above), this option can be used to set the location of the +# # lock database. +# # +# #DavRodsLockDB /var/lib/davrods/lockdb_locallock +# +# # Davrods provides read-only HTML directory listings for web browser access. +# # The UI is basic and unstyled by default, but can be modified with three +# # theming directives. +# # +# # Each of these directives points to a local HTML file that must be readable +# # by the apache user. +# # +# # The default value for each of these is "", which disables the option. +# # +# # - DavRodsHtmlHead is inserted in the HEAD tag of the listing. +# # - DavRodsHtmlHeader is inserted at the top of the listing's BODY tag. +# # - DavRodsHtmlFooter is inserted at the bottom of the listing's BODY tag. +# # +# # Example HTML files are provided in /etc/apache2/irods. You should edit these +# # before enabling them. +# # +# # To see an example, uncomment the following three lines: +# # +# #DavRodsHtmlHead "/etc/apache2/irods/head.html" +# #DavRodsHtmlHeader "/etc/apache2/irods/header.html" +# #DavRodsHtmlFooter "/etc/apache2/irods/footer.html" +# +# # }}} +# +# +# +# # To avoid cleartext password communication we strongly recommend to +# # enable Davrods only over SSL. +# # For HTTPS-only access, change the port at the start of the vhost block +# # from 80 to 443 and add your SSL options below. +# +# diff --git a/aux/deb/davrods.conf b/aux/deb/davrods.conf new file mode 100644 index 0000000..89c385a --- /dev/null +++ b/aux/deb/davrods.conf @@ -0,0 +1 @@ +LoadModule davrods_module /usr/lib/apache2/modules/mod_davrods.so diff --git a/aux/deb/postinst b/aux/deb/postinst new file mode 100644 index 0000000..08a6d93 --- /dev/null +++ b/aux/deb/postinst @@ -0,0 +1,7 @@ +#!/bin/sh + +# Post-install script for DEB distros. + +# Fix permissions for lockdb directory. +chmod 700 /var/lib/davrods +chown www-data:www-data /var/lib/davrods diff --git a/davrods-anonymous-vhost.conf b/aux/rpm/davrods-anonymous-vhost.conf similarity index 99% rename from davrods-anonymous-vhost.conf rename to aux/rpm/davrods-anonymous-vhost.conf index b4bfb4f..7aa448d 100644 --- a/davrods-anonymous-vhost.conf +++ b/aux/rpm/davrods-anonymous-vhost.conf @@ -49,7 +49,7 @@ # # # # Note that the davrods-locallock provider requires an apache-writable lockdb directory # # (/var/lib/davrods, or a path specified using the DavRodsLockDB directive - see further down this file). -# # The RPM distribution creates this directory for you. +# # The RPM/DEB distribution creates this directory for you. # # # Dav davrods-locallock # @@ -95,7 +95,7 @@ # # Davrods. AuthType must be set to 'Basic' and AuthBasicProvider # # must be set to 'irods'. There must also be a 'Require valid-user' # # line. -# # +# # # # When 'On', Davrods will log into iRODS with a preset # # username and password (See options DavRodsAnonymousLogin and # # DavRodsAuthScheme). AuthType must be unset, or set to None, diff --git a/davrods-vhost.conf b/aux/rpm/davrods-vhost.conf similarity index 99% rename from davrods-vhost.conf rename to aux/rpm/davrods-vhost.conf index 071a0e1..70f29b0 100644 --- a/davrods-vhost.conf +++ b/aux/rpm/davrods-vhost.conf @@ -45,7 +45,7 @@ # # # # Note that the davrods-locallock provider requires an apache-writable lockdb directory # # (/var/lib/davrods, or a path specified using the DavRodsLockDB directive - see further down this file). -# # The RPM distribution creates this directory for you. +# # The RPM/DEB distribution creates this directory for you. # # # Dav davrods-locallock # @@ -91,7 +91,7 @@ # # Davrods. AuthType must be set to 'Basic' and AuthBasicProvider # # must be set to 'irods'. There must also be a 'Require valid-user' # # line. -# # +# # # # When 'On', Davrods will log into iRODS with a preset # # username and password (See options DavRodsAnonymousLogin and # # DavRodsAuthScheme). AuthType must be unset, or set to None, diff --git a/davrods.conf b/aux/rpm/davrods.conf similarity index 100% rename from davrods.conf rename to aux/rpm/davrods.conf diff --git a/package/postinst.sh b/aux/rpm/postinst.sh similarity index 100% rename from package/postinst.sh rename to aux/rpm/postinst.sh diff --git a/package/changelog.txt b/changelog.txt similarity index 100% rename from package/changelog.txt rename to changelog.txt diff --git a/auth.c b/src/auth.c similarity index 99% rename from auth.c rename to src/auth.c index 26f9b30..c0d8181 100644 --- a/auth.c +++ b/src/auth.c @@ -29,7 +29,7 @@ #include #include -#ifdef APLOG_USE_MODULE +#ifdef APLOG_USE_MODULE APLOG_USE_MODULE(davrods); #endif diff --git a/auth.h b/src/auth.h similarity index 92% rename from auth.h rename to src/auth.h index 44d861b..1dd4986 100644 --- a/auth.h +++ b/src/auth.h @@ -19,8 +19,8 @@ * You should have received a copy of the GNU Lesser General Public License * along with Davrods. If not, see . */ -#ifndef _RODS_AUTH_H -#define _RODS_AUTH_H +#ifndef _DAVRODS_AUTH_H +#define _DAVRODS_AUTH_H #include "mod_davrods.h" #include @@ -29,4 +29,4 @@ authn_status check_rods(request_rec *r, const char *username, const char *passwo void davrods_auth_register(apr_pool_t *p); -#endif /* _RODS_AUTH_H */ +#endif /* _DAVRODS_AUTH_H */ diff --git a/src/byterange.c b/src/byterange.c new file mode 100644 index 0000000..493f89c --- /dev/null +++ b/src/byterange.c @@ -0,0 +1,644 @@ +/** + * \file + * \brief Davrods GET+Range request support. + * \author Chris Smeele + * \copyright Copyright (c) 2018, Utrecht University + * + * Copyright (c) 2018, Utrecht University + * + * This file is part of Davrods. + * + * Davrods is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Davrods is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Davrods. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Apache HTTP Server + * Copyright 2013 The Apache Software Foundation. + * + * This product includes software developed at + * The Apache Software Foundation (http://www.apache.org/). + * + * Portions of this software were developed at the National Center + * for Supercomputing Applications (NCSA) at the University of + * Illinois at Urbana-Champaign. + * + * This software contains code derived from the RSA Data Security + * Inc. MD5 Message-Digest Algorithm, including various + * modifications by Spyglass Inc., Carnegie Mellon University, and + * Bell Communications Research, Inc (Bellcore). + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This code is based on: + * - Apache's byterange_filter.c: For supporting the HTTP Range header. + * - Earlier Davrods' repo.c: For reading and sending iRODS file contents. + */ + +#include "apr.h" + +#include "apr_strings.h" +#include "apr_buckets.h" +#include "apr_lib.h" +#include "apr_signal.h" + +#define APR_WANT_STDIO /* for sscanf */ +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "util_filter.h" +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_main.h" +#include "http_request.h" +#include "http_vhost.h" +#include "http_log.h" /* For errors detected in basic auth common + * support code... */ +#include "apr_date.h" /* For apr_date_parse_http and APR_DATE_BAD */ +#include "util_charset.h" +#include "util_ebcdic.h" +#include "util_time.h" + +#include "mod_core.h" + +#if APR_HAVE_STDARG_H +#include +#endif +#if APR_HAVE_UNISTD_H +#include +#endif + +#include "byterange.h" +#include "repo.h" + +#ifndef AP_DEFAULT_MAX_RANGES +#define AP_DEFAULT_MAX_RANGES 200 +#endif +#ifndef AP_DEFAULT_MAX_OVERLAPS +#define AP_DEFAULT_MAX_OVERLAPS 20 +#endif +#ifndef AP_DEFAULT_MAX_REVERSALS +#define AP_DEFAULT_MAX_REVERSALS 20 +#endif + +#define MAX_PREALLOC_RANGES 100 + +#ifdef APLOG_USE_MODULE +APLOG_USE_MODULE(davrods); +#endif + +typedef struct indexes_t { + apr_off_t start; + apr_off_t end; +} indexes_t; + +/** + * \brief Parse a range request, joining ranges where possible. + * + * This is unmodified from Apache's byterange filter code. + * + * \return number of ranges (merged) or -1 for no-good + */ +static int ap_set_byterange(request_rec *r, apr_off_t clength, + apr_array_header_t **indexes, + int *overlaps, int *reversals) +{ + const char *range; + const char *ct; + char *cur; + apr_array_header_t *merged; + int num_ranges = 0, unsatisfiable = 0; + apr_off_t ostart = 0, oend = 0, sum_lengths = 0; + int in_merge = 0; + indexes_t *idx; + int ranges = 1; + int i; + const char *it; + + *overlaps = 0; + *reversals = 0; + + if (r->assbackwards) { + return 0; + } + + /* + * Check for Range request-header (HTTP/1.1) or Request-Range for + * backwards-compatibility with second-draft Luotonen/Franks + * byte-ranges (e.g. Netscape Navigator 2-3). + * + * We support this form, with Request-Range, and (farther down) we + * send multipart/x-byteranges instead of multipart/byteranges for + * Request-Range based requests to work around a bug in Netscape + * Navigator 2-3 and MSIE 3. + */ + + if (!(range = apr_table_get(r->headers_in, "Range"))) { + range = apr_table_get(r->headers_in, "Request-Range"); + } + + if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) { + return 0; + } + + /* is content already a single range? */ + if (apr_table_get(r->headers_out, "Content-Range")) { + return 0; + } + + /* is content already a multiple range? */ + if ((ct = apr_table_get(r->headers_out, "Content-Type")) + && (!strncasecmp(ct, "multipart/byteranges", 20) + || !strncasecmp(ct, "multipart/x-byteranges", 22))) { + return 0; + } + + /* + * Check the If-Range header for Etag or Date. + */ + if (AP_CONDITION_NOMATCH == ap_condition_if_range(r, r->headers_out)) { + return 0; + } + + range += 6; + it = range; + while (*it) { + if (*it++ == ',') { + ranges++; + } + } + it = range; + if (ranges > MAX_PREALLOC_RANGES) { + ranges = MAX_PREALLOC_RANGES; + } + *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t)); + while ((cur = ap_getword(r->pool, &range, ','))) { + char *dash; + char *errp; + apr_off_t number, start, end; + + if (!*cur) + break; + + /* + * Per RFC 2616 14.35.1: If there is at least one syntactically invalid + * byte-range-spec, we must ignore the whole header. + */ + + if (!(dash = strchr(cur, '-'))) { + return 0; + } + + if (dash == cur) { + /* In the form "-5" */ + if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) { + return 0; + } + if (number < 1) { + return 0; + } + start = clength - number; + end = clength - 1; + } + else { + *dash++ = '\0'; + if (apr_strtoff(&number, cur, &errp, 10) || *errp) { + return 0; + } + start = number; + if (*dash) { + if (apr_strtoff(&number, dash, &errp, 10) || *errp) { + return 0; + } + end = number; + if (start > end) { + return 0; + } + } + else { /* "5-" */ + end = clength - 1; + /* + * special case: 0- + * ignore all other ranges provided + * return as a single range: 0- + */ + if (start == 0) { + num_ranges = 0; + sum_lengths = 0; + in_merge = 1; + oend = end; + ostart = start; + apr_array_clear(*indexes); + break; + } + } + } + + if (start < 0) { + start = 0; + } + if (start >= clength) { + unsatisfiable = 1; + continue; + } + if (end >= clength) { + end = clength - 1; + } + + if (!in_merge) { + /* new set */ + ostart = start; + oend = end; + in_merge = 1; + continue; + } + in_merge = 0; + + if (start >= ostart && end <= oend) { + in_merge = 1; + } + + if (start < ostart && end >= ostart-1) { + ostart = start; + ++*reversals; + in_merge = 1; + } + if (end >= oend && start <= oend+1 ) { + oend = end; + in_merge = 1; + } + + if (in_merge) { + ++*overlaps; + continue; + } else { + idx = (indexes_t *)apr_array_push(*indexes); + idx->start = ostart; + idx->end = oend; + sum_lengths += oend - ostart + 1; + /* new set again */ + in_merge = 1; + ostart = start; + oend = end; + num_ranges++; + } + } + + if (in_merge) { + idx = (indexes_t *)apr_array_push(*indexes); + idx->start = ostart; + idx->end = oend; + sum_lengths += oend - ostart + 1; + num_ranges++; + } + else if (num_ranges == 0 && unsatisfiable) { + /* If all ranges are unsatisfiable, we should return 416 */ + return -1; + } + if (sum_lengths > clength) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "Sum of ranges larger than file, ignoring."); + return 0; + } + + /* + * create the merged table now, now that we know we need it + */ + merged = apr_array_make(r->pool, num_ranges, sizeof(char *)); + idx = (indexes_t *)(*indexes)->elts; + for (i = 0; i < (*indexes)->nelts; i++, idx++) { + char **new = (char **)apr_array_push(merged); + *new = apr_psprintf(r->pool, "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT, + idx->start, idx->end); + } + + r->status = HTTP_PARTIAL_CONTENT; + r->range = apr_array_pstrcat(r->pool, merged, ','); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01583) + "Range: %s | %s (%d : %d : %"APR_OFF_T_FMT")", + it, r->range, *overlaps, *reversals, clength); + + return num_ranges; +} + +/* + * Here we try to be compatible with clients that want multipart/x-byteranges + * instead of multipart/byteranges (also see above), as per HTTP/1.1. We + * look for the Request-Range header (e.g. Netscape 2 and 3) as an indication + * that the browser supports an older protocol. We also check User-Agent + * for Microsoft Internet Explorer 3, which needs this as well. + */ +static int use_range_x(request_rec *r) +{ + const char *ua; + return (apr_table_get(r->headers_in, "Request-Range") + || ((ua = apr_table_get(r->headers_in, "User-Agent")) + && ap_strstr_c(ua, "MSIE 3"))); +} + +#define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT + +/** + * \brief Send a 416 Range not Satisfiable status code. + * + * This was modified from Apache's original code to + * remove filter-specific behavior. + */ +static apr_status_t send_416( + const dav_resource *resource, + ap_filter_t *output, + apr_bucket_brigade *bb +) { + apr_bucket *e; + conn_rec *c = resource->info->r->connection; + resource->info->r->status = HTTP_OK; + e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL, + resource->info->r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + e = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + return ap_pass_brigade(output, bb); +} + +/** + * \brief Seek in an iRODS object. + */ +static dav_error *deliver_seek( + const dav_resource *resource, + openedDataObjInp_t *data_obj, + size_t pos +) { + openedDataObjInp_t seek_inp = { 0 }; + seek_inp.l1descInx = data_obj->l1descInx; + seek_inp.offset = pos; + seek_inp.whence = SEEK_SET; + + fileLseekOut_t *seek_out = NULL; + int status = rcDataObjLseek(resource->info->rods_conn, &seek_inp, &seek_out); + + if (seek_out) + free(seek_out); + + if (status < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, resource->info->r, + "rcDataObjLseek failed: %d = %s", status, get_rods_error_msg(status)); + return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "Could not seek file for range request"); + } else { + return NULL; + } +} + +/** + * \brief Read data object bytes from iRODS, send them to the client. + */ +static dav_error *deliver_file_bytes( + const dav_resource *resource, + openedDataObjInp_t *data_obj, + ap_filter_t *output, + apr_bucket_brigade *bb, + size_t seek_pos, + size_t bytes_to_read, + size_t *total_read +) { + apr_pool_t *pool = resource->pool; + bytesBuf_t read_buffer = { 0 }; + + *total_read = 0; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, resource->info->r, + "Reading data object in %luK chunks", + resource->info->conf->rods_rx_buffer_size / 1024); + + // NB: + // ap_set_byterange joins and truncates requested ranges when necessary, + // and filters invalid ranges. + // For this reason we can assume that any error occuring during a seek is + // not an issue with the original range request, but an issue with the + // iRODS object or the iRODS connection instead. + // So errors here will result in a 500, not a 416. + dav_error *err = deliver_seek(resource, data_obj, seek_pos); + if (err) + return err; + + while (*total_read < bytes_to_read) { + // Have a buffer size of at most rods_rx_buffer_size bytes. + size_t buffer_size = MIN(bytes_to_read - *total_read, + resource->info->conf->rods_rx_buffer_size); + // Request to read that amount of bytes. + data_obj->len = buffer_size; + + int bytes_read = 0; + + // Read the data object. + bytes_read = rcDataObjRead(resource->info->rods_conn, data_obj, &read_buffer); + + if (bytes_read < 0) { + if (read_buffer.buf) + free(read_buffer.buf); + + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, resource->info->r, + "rcDataObjRead failed: %d = %s", + bytes_read, get_rods_error_msg(bytes_read)); + + return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "Could not read from requested resource"); + } + + *total_read += bytes_read; + + if (bytes_read == 0) { + // No errors, but nothing to read either, EOF. + free(read_buffer.buf); + read_buffer.buf = NULL; + return NULL; + } + + apr_brigade_write(bb, NULL, NULL, read_buffer.buf, bytes_read); + + free(read_buffer.buf); + read_buffer.buf = NULL; + + // Flush our output after each buffer_size bytes. + int status; + if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { + return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, status, + "Could not write contents to filter."); + } + } + + return NULL; +} + +/** + * \brief Process a GET request with an optional Range header. + * + * This is based on ap_byterange_filter. + * Changes mostly come down to having to deal with an iRODS object + * as the input instead of a bucket brigade (Davrods is not a filter). + */ +dav_error *davrods_byterange_deliver_file(const dav_resource *resource, + openedDataObjInp_t *data_obj, + ap_filter_t *output, + apr_bucket_brigade *bb) { + + conn_rec *c = resource->info->r->connection; + request_rec *r = resource->info->r; + + // Set up Range limits. + + core_dir_config *core_conf = ap_get_core_module_config(resource->info->r->per_dir_config); + + int max_ranges = ((core_conf->max_ranges >= 0 || core_conf->max_ranges == AP_MAXRANGES_UNLIMITED) + ? core_conf->max_ranges : AP_DEFAULT_MAX_RANGES); + int max_overlaps = ((core_conf->max_overlaps >= 0 || core_conf->max_overlaps == AP_MAXRANGES_UNLIMITED) + ? core_conf->max_overlaps : AP_DEFAULT_MAX_OVERLAPS ); + int max_reversals = ((core_conf->max_reversals >= 0 || core_conf->max_reversals == AP_MAXRANGES_UNLIMITED) + ? core_conf->max_reversals : AP_DEFAULT_MAX_REVERSALS); + + size_t obj_length = resource->info->stat->objSize; + + // Parse a Range header, if it exists. + apr_array_header_t *indexes; + int overlaps = 0; + int reversals = 0; + int num_ranges = ap_set_byterange(resource->info->r, + obj_length, + &indexes, + &overlaps, + &reversals); + + if (num_ranges) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, resource->info->r, + "Ranges: %d, overlaps: %d, reversals: %d", + num_ranges, overlaps, reversals); + } + + if (num_ranges == 0 + || (max_ranges >= 0 && num_ranges > max_ranges) + || (max_overlaps >= 0 && overlaps > max_overlaps) + || (max_reversals >= 0 && reversals > max_reversals)) { + + // No Range header or we hit a limit? + // Deliver the entire file. + + size_t total_read = 0; + dav_error *err = deliver_file_bytes(resource, + data_obj, + output, + bb, + 0, + obj_length, + &total_read); + return err; + + } else if (num_ranges < 0) { + + // All ranges are unsatisfiable. + + send_416(resource, output, bb); + return NULL; + } + + // This is a range request. Deliver each range. + + apr_table_unset(r->headers_out, "Content-Length"); + + char *bound_head = NULL; + + if (num_ranges > 1) { + // Output in multipart format and generate multipart boundaries. + ap_set_content_type(r, + apr_pstrcat(r->pool, "multipart", + use_range_x(r) ? "/x-" : "/", + "byteranges; boundary=", + ap_multipart_boundary, NULL)); + bound_head = apr_pstrcat(r->pool, + CRLF "--", ap_multipart_boundary, + CRLF "Content-range: bytes ", + NULL); + ap_xlate_proto_to_ascii(bound_head, strlen(bound_head)); + } + + indexes_t *idx = (indexes_t*)indexes->elts; + + // For each range... + for (int i = 0; i < indexes->nelts; i++, idx++) { + apr_off_t range_start = idx->start; + apr_off_t range_end = idx->end; + + // For single range requests, we must produce Content-Range header. + // Otherwise, we need to produce the multipart boundaries. + if (num_ranges == 1) { + apr_table_setn(r->headers_out, "Content-Range", + apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, + range_start, range_end, obj_length)); + } else { + char *ts; + + apr_bucket *e = apr_bucket_pool_create(bound_head, strlen(bound_head), + r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + + ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF, + range_start, range_end, obj_length); + ap_xlate_proto_to_ascii(ts, strlen(ts)); + e = apr_bucket_pool_create(ts, strlen(ts), r->pool, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + + // Now output the content for that range. + size_t total_read = 0; + dav_error *err = deliver_file_bytes(resource, + data_obj, + output, + bb, + range_start, + range_end - range_start + 1, + &total_read); + if (err) + return err; + } + + if (num_ranges > 1) { + char *end; + + // Add the final boundary. + end = apr_pstrcat(r->pool, CRLF "--", ap_multipart_boundary, "--" CRLF, NULL); + ap_xlate_proto_to_ascii(end, strlen(end)); + apr_bucket *e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + + return NULL; +} diff --git a/src/byterange.h b/src/byterange.h new file mode 100644 index 0000000..f692485 --- /dev/null +++ b/src/byterange.h @@ -0,0 +1,35 @@ +/** + * \file + * \brief Davrods GET+Range request support. + * \author Chris Smeele + * \copyright Copyright (c) 2018, Utrecht University + * + * This file is part of Davrods. + * + * Davrods is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Davrods is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Davrods. If not, see . + */ +#ifndef _DAVRODS_BYTERANGE_H +#define _DAVRODS_BYTERANGE_H + +#include "common.h" + +#include +#include + +dav_error *davrods_byterange_deliver_file(const dav_resource *resource, + openedDataObjInp_t *data_obj, + ap_filter_t *output, + apr_bucket_brigade *bb); + +#endif /* _DAVRODS_BYTERANGE_H */ diff --git a/common.c b/src/common.c similarity index 100% rename from common.c rename to src/common.c diff --git a/common.h b/src/common.h similarity index 100% rename from common.h rename to src/common.h diff --git a/config.c b/src/config.c similarity index 100% rename from config.c rename to src/config.c diff --git a/config.h b/src/config.h similarity index 100% rename from config.h rename to src/config.h diff --git a/lock_local.c b/src/lock_local.c similarity index 100% rename from lock_local.c rename to src/lock_local.c diff --git a/lock_local.h b/src/lock_local.h similarity index 100% rename from lock_local.h rename to src/lock_local.h diff --git a/mod_davrods.c b/src/mod_davrods.c similarity index 100% rename from mod_davrods.c rename to src/mod_davrods.c diff --git a/mod_davrods.h b/src/mod_davrods.h similarity index 100% rename from mod_davrods.h rename to src/mod_davrods.h diff --git a/prop.c b/src/prop.c similarity index 100% rename from prop.c rename to src/prop.c diff --git a/prop.h b/src/prop.h similarity index 100% rename from prop.h rename to src/prop.h diff --git a/propdb.c b/src/propdb.c similarity index 100% rename from propdb.c rename to src/propdb.c diff --git a/propdb.h b/src/propdb.h similarity index 100% rename from propdb.h rename to src/propdb.h diff --git a/repo.c b/src/repo.c similarity index 96% rename from repo.c rename to src/repo.c index aa2929a..d999583 100644 --- a/repo.c +++ b/src/repo.c @@ -2,7 +2,7 @@ * \file * \brief Davrods DAV repository. * \author Chris Smeele - * \copyright Copyright (c) 2016, Utrecht University + * \copyright Copyright (c) 2016-2018, Utrecht University * * This file is part of Davrods. * @@ -21,6 +21,7 @@ */ #include "repo.h" #include "auth.h" // For anonymous access. +#include "byterange.h" #include #include @@ -923,8 +924,15 @@ static dav_error *dav_repo_set_headers( // A GET on a collection => client must be a web browser / standard HTTP client. // We will output an HTML directory listing. ap_set_content_type(r, "text/html; charset=utf-8"); + // Do not let them cache directory content renders. apr_table_setn(r->headers_out, "Cache-Control", "no-cache, must-revalidate"); + + // We do not accept range requests on directory content listings. + // The spec doesn't require us to state this, but it's nice to be + // explicit when you *do* accept ranges on other resources. + apr_table_setn(r->headers_out, "Accept-Ranges", "none"); + } else { char *date_str = apr_pcalloc(r->pool, APR_RFC822_DATE_LEN); assert(date_str); @@ -942,6 +950,12 @@ static dav_error *dav_repo_set_headers( if (etag && strlen(etag)) apr_table_setn(r->headers_out, "ETag", etag); + // We accept range requests on data objects. + // This will advertise range header support with configured limits, if any. + ap_set_accept_ranges(r); + + // This will be overwritten in byterange.c if the request turns out to be + // a valid range request. ap_set_content_length(r, resource->info->stat->objSize); } @@ -954,14 +968,15 @@ static dav_error *deliver_file( ) { // Download a file from iRODS to the WebDAV client. + // Setup the output brigade. apr_pool_t *pool = resource->pool; apr_bucket_brigade *bb; - apr_bucket *bkt; bb = apr_brigade_create(pool, output->c->bucket_alloc); dataObjInp_t open_params = {{ 0 }}; + // Open the iRODS object. open_params.openFlags = O_RDONLY; strcpy(open_params.objPath, resource->info->rods_path); @@ -970,88 +985,41 @@ static dav_error *deliver_file( if ((status = rcDataObjOpen(resource->info->rods_conn, &open_params)) < 0) { apr_brigade_destroy(bb); - ap_log_rerror( - APLOG_MARK, APLOG_ERR, APR_SUCCESS, resource->info->r, - "rcDataObjOpen failed: %d = %s", status, get_rods_error_msg(status) - ); - - // Note: This might be a CONFLICT situation where the file was deleted - // in a separate concurrent request. + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, resource->info->r, + "rcDataObjOpen failed: %d = %s", status, get_rods_error_msg(status)); - return dav_new_error( - pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, - "Could not open requested resource for reading" - ); - } else { - // `status` contains some sort of file descriptor. - openedDataObjInp_t data_obj = { .l1descInx = status }; + return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "Could not open requested resource for reading"); + } - bytesBuf_t read_buffer = { 0 }; + // `status` now contains a sort of file descriptor. + openedDataObjInp_t data_obj = { .l1descInx = status }; - size_t buffer_size = resource->info->conf->rods_rx_buffer_size; - data_obj.len = buffer_size; + // Hand the request over to our byterange component, + // so it can deal with Range requests. + dav_error *err = davrods_byterange_deliver_file(resource, &data_obj, output, bb); + if (err) { + apr_brigade_destroy(bb); + return err; + } - int bytes_read = 0; + // Done, close the object. + openedDataObjInp_t close_params = { 0 }; + close_params.l1descInx = data_obj.l1descInx; + status = rcDataObjClose(resource->info->rods_conn, &close_params); + if (status < 0) { ap_log_rerror( - APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, resource->info->r, - "Reading data object in %luK chunks", buffer_size / 1024 + APLOG_MARK, APLOG_WARNING, APR_SUCCESS, resource->info->r, + "rcDataObjClose failed: %d = %s", + status, get_rods_error_msg(status) ); - - // Read from iRODS, write to the client. - do { - bytes_read = rcDataObjRead(resource->info->rods_conn, &data_obj, &read_buffer); - - if (bytes_read < 0) { - if (read_buffer.buf) - free(read_buffer.buf); - apr_brigade_destroy(bb); - - ap_log_rerror( - APLOG_MARK, APLOG_ERR, APR_SUCCESS, resource->info->r, - "rcDataObjRead failed: %d = %s", bytes_read, get_rods_error_msg(bytes_read) - ); - return dav_new_error( - pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, - "Could not read from requested resource" - ); - } - apr_brigade_write(bb, NULL, NULL, read_buffer.buf, bytes_read); - - free(read_buffer.buf); - read_buffer.buf = NULL; - - if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { - apr_brigade_destroy(bb); - return dav_new_error( - pool, HTTP_INTERNAL_SERVER_ERROR, 0, status, - "Could not write contents to filter." - ); - } - } while ((size_t)bytes_read == buffer_size); - - openedDataObjInp_t close_params = { 0 }; - close_params.l1descInx = data_obj.l1descInx; - - status = rcDataObjClose(resource->info->rods_conn, &close_params); - if (status < 0) { - ap_log_rerror( - APLOG_MARK, APLOG_WARNING, APR_SUCCESS, resource->info->r, - "rcDataObjClose failed: %d = %s (proceeding as if nothing happened)", - status, get_rods_error_msg(status) - ); - // We already gave the entire file to the client, it makes no sense to send them an error here. - - //return dav_new_error( - // pool, HTTP_INTERNAL_SERVER_ERROR, 0, status, - // "Could not close requested data object" - //); - } - + // We already gave the entire file to the client, + // it makes no sense to send them an error here. } - bkt = apr_bucket_eos_create(output->c->bucket_alloc); - + // Terminate and clean up the bucket brigade. + apr_bucket *bkt = apr_bucket_eos_create(output->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, bkt); if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { diff --git a/repo.h b/src/repo.h similarity index 100% rename from repo.h rename to src/repo.h