diff --git a/CMakeLists.txt b/CMakeLists.txt index e266f80..71fbaa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 2.8.12) project(davrods C) -set(IRODSRT_VERSION "4.2.3" CACHE STRING "iRODS client library version") +set(IRODSRT_VERSION "4.2.4" CACHE STRING "iRODS client library version") -set(DAVRODS_FEATURE "1.4.1") +set(DAVRODS_FEATURE "1.4.2") set(DAVRODS_VERSION "${IRODSRT_VERSION}_${DAVRODS_FEATURE}") set(DAVRODS_VERSION_DEB "${IRODSRT_VERSION}-${DAVRODS_FEATURE}") diff --git a/README.md b/README.md index 44a58f9..0f3d52e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Please choose the right version for your platform: | Davrods ver. | iRODS runtime ver. | Packages | | ------------ | ------------------ | --------------------------------------------------------------------------------- | +| 1.4.2 | 4.2.4 | [RPM, DEB](https://github.com/UtrechtUniversity/davrods/releases/tag/4.2.4_1.4.2) | | 1.4.1 | 4.2.3 | [RPM, DEB](https://github.com/UtrechtUniversity/davrods/releases/tag/4.2.3_1.4.1) | | 1.4.1 | 4.1.x | [RPM](https://github.com/UtrechtUniversity/davrods/releases/tag/4.1_1.4.1) | | 1.4.0 | 4.2.3 | [RPM, DEB](https://github.com/UtrechtUniversity/davrods/releases/tag/4.2.3_1.4.0) | @@ -80,9 +81,9 @@ package using the RPM or DEB file from the Download the Davrods package for your platform and install it using your package manager, for example: - yum install davrods-4.2.3_1.4.1-1.rpm + yum install davrods-4.2.4_1.4.2-1.rpm --or-- - apt install davrods-4.2.3_1.4.1.deb + apt install davrods-4.2.4_1.4.2.deb Now see the __Configuration__ section for instructions on how to configure Davrods once it has been installed. diff --git a/aux/deb/davrods-anonymous-vhost.conf b/aux/deb/davrods-anonymous-vhost.conf index 7edb555..de2ab66 100644 --- a/aux/deb/davrods-anonymous-vhost.conf +++ b/aux/deb/davrods-anonymous-vhost.conf @@ -192,6 +192,18 @@ # #DavRodsHtmlHeader "/etc/apache2/irods/header.html" # #DavRodsHtmlFooter "/etc/apache2/irods/footer.html" # +# # Depending on file type, web browser clients will either display +# # files directly or offer a download to the user. +# # This behavior can be influenced with the 'Content-Disposition' header. +# # +# # By default (value 'Off'), no such header is sent by Davrods. +# # When DavRodsForceDownload is 'On', Davrods will send +# # 'Content-Disposition: attachment' for all data objects, signalling that +# # web browsers should not display files inline, but offer a download +# # instead. +# # +# #DavRodsForceDownload Off +# # # }}} # # diff --git a/aux/deb/davrods-vhost.conf b/aux/deb/davrods-vhost.conf index 6f7f6e2..ac0df73 100644 --- a/aux/deb/davrods-vhost.conf +++ b/aux/deb/davrods-vhost.conf @@ -188,6 +188,18 @@ # #DavRodsHtmlHeader "/etc/apache2/irods/header.html" # #DavRodsHtmlFooter "/etc/apache2/irods/footer.html" # +# # Depending on file type, web browser clients will either display +# # files directly or offer a download to the user. +# # This behavior can be influenced with the 'Content-Disposition' header. +# # +# # By default (value 'Off'), no such header is sent by Davrods. +# # When DavRodsForceDownload is 'On', Davrods will send +# # 'Content-Disposition: attachment' for all data objects, signalling that +# # web browsers should not display files inline, but offer a download +# # instead. +# # +# #DavRodsForceDownload Off +# # # }}} # # diff --git a/aux/rpm/davrods-anonymous-vhost.conf b/aux/rpm/davrods-anonymous-vhost.conf index 7aa448d..556f345 100644 --- a/aux/rpm/davrods-anonymous-vhost.conf +++ b/aux/rpm/davrods-anonymous-vhost.conf @@ -192,6 +192,18 @@ # #DavRodsHtmlHeader "/etc/httpd/irods/header.html" # #DavRodsHtmlFooter "/etc/httpd/irods/footer.html" # +# # Depending on file type, web browser clients will either display +# # files directly or offer a download to the user. +# # This behavior can be influenced with the 'Content-Disposition' header. +# # +# # By default (value 'Off'), no such header is sent by Davrods. +# # When DavRodsForceDownload is 'On', Davrods will send +# # 'Content-Disposition: attachment' for all data objects, signalling that +# # web browsers should not display files inline, but offer a download +# # instead. +# # +# #DavRodsForceDownload Off +# # # }}} # # diff --git a/aux/rpm/davrods-vhost.conf b/aux/rpm/davrods-vhost.conf index 70f29b0..ffd8bb6 100644 --- a/aux/rpm/davrods-vhost.conf +++ b/aux/rpm/davrods-vhost.conf @@ -188,6 +188,18 @@ # #DavRodsHtmlHeader "/etc/httpd/irods/header.html" # #DavRodsHtmlFooter "/etc/httpd/irods/footer.html" # +# # Depending on file type, web browser clients will either display +# # files directly or offer a download to the user. +# # This behavior can be influenced with the 'Content-Disposition' header. +# # +# # By default (value 'Off'), no such header is sent by Davrods. +# # When DavRodsForceDownload is 'On', Davrods will send +# # 'Content-Disposition: attachment' for all data objects, signalling that +# # web browsers should not display files inline, but offer a download +# # instead. +# # +# #DavRodsForceDownload Off +# # # }}} # # diff --git a/changelog.txt b/changelog.txt index e8f1183..5a1e02d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +* Mon Dec 17 2018 Chris Smeele - 4.2.4_1.4.2-1 +- Fixed DavrodsExposedRoot config being ignored in some builds +- Added a Content-Disposition configuration setting (off by default) +- Fixed a URL encoding issue for relative paths in directory listings + * Sun Jul 15 2018 Chris Smeele - 4.2.3_1.4.1-1 - Improved robustness of connection reuse diff --git a/src/config.c b/src/config.c index 0c10a86..7564d5d 100644 --- a/src/config.c +++ b/src/config.c @@ -84,6 +84,8 @@ void *davrods_create_dir_config(apr_pool_t *p, char *dir) { conf->html_head = ""; conf->html_header = ""; conf->html_footer = ""; + + conf->force_download = DAVRODS_FORCE_DOWNLOAD_OFF; } return conf; } @@ -123,12 +125,15 @@ void *davrods_merge_dir_config(apr_pool_t *p, void *_parent, void *_child) { DAVRODS_PROP_MERGE(anonymous_auth_username); DAVRODS_PROP_MERGE(anonymous_auth_password); - assert(set_exposed_root(conf, exposed_root) >= 0); + { int ret = set_exposed_root(conf, exposed_root); + assert(ret >= 0); } DAVRODS_PROP_MERGE(html_head); DAVRODS_PROP_MERGE(html_header); DAVRODS_PROP_MERGE(html_footer); + DAVRODS_PROP_MERGE(force_download); + #undef DAVRODS_PROP_MERGE return conf; @@ -388,6 +393,24 @@ static const char *cmd_davrodshtmlfooter( return NULL; } +static const char *cmd_davrodsforcedownload( + cmd_parms *cmd, void *config, + const char *arg1 +) { + davrods_dir_conf_t *conf = (davrods_dir_conf_t*)config; + + if (!strcasecmp(arg1, "on")) { + conf->force_download = DAVRODS_FORCE_DOWNLOAD_ON; + } else if (!strcasecmp(arg1, "off")) { + conf->force_download = DAVRODS_FORCE_DOWNLOAD_OFF; + } else { + return "This directive accepts only 'On' and 'Off' values"; + } + + return NULL; +} + + // }}} const command_rec davrods_directives[] = { @@ -455,6 +478,10 @@ const command_rec davrods_directives[] = { DAVRODS_CONFIG_PREFIX "HtmlFooter", cmd_davrodshtmlfooter, NULL, ACCESS_CONF, "File that's inserted into HTML directory listings, in the body tag" ), + AP_INIT_TAKE1( + DAVRODS_CONFIG_PREFIX "ForceDownload", cmd_davrodsforcedownload, + NULL, ACCESS_CONF, "When On, prevents inline display of files in web browsers" + ), { NULL } }; diff --git a/src/config.h b/src/config.h index e48b274..9555f63 100644 --- a/src/config.h +++ b/src/config.h @@ -86,6 +86,13 @@ typedef struct { const char *html_header; const char *html_footer; + enum { + // Relevant only for webbrowser-style clients. + // Prevents inline display of data objects. + DAVRODS_FORCE_DOWNLOAD_OFF = 1, + DAVRODS_FORCE_DOWNLOAD_ON, + } force_download; + } davrods_dir_conf_t; extern const command_rec davrods_directives[]; diff --git a/src/repo.c b/src/repo.c index b880144..7639195 100644 --- a/src/repo.c +++ b/src/repo.c @@ -1050,6 +1050,14 @@ static dav_error *dav_repo_set_headers( // 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); + + davrods_dir_conf_t *conf = ap_get_module_config(r->per_dir_config, + &davrods_module); + assert(conf); + + if (conf->force_download == DAVRODS_FORCE_DOWNLOAD_ON) + // Prevent inline display of files in web browsers. + apr_table_setn(r->headers_out, "Content-Disposition", "attachment"); } return 0; @@ -1125,6 +1133,82 @@ static dav_error *deliver_file( return NULL; } +/** + * \brief Encode a path such that it can be safely used in a URI. + * + * Used within HTML directory listings. + * If the input path is safe, no new string is allocated. + * + * \param pool A memory pool + * \param path The path to escape + * + * \return An escaped path (may be the same as the input pointer) + */ +static const char *escape_uri_path( + apr_pool_t *pool, + const char *path +) { + // Apache's ap_escape_uri is not sufficient, as it is OS-dependent(!?) and + // does not encode certain reserved characters that can be problematic in + // relative URLs. + // + // Given the lack of an Apache function that does what we need, we do URL + // encoding ourselves, as per RFC 1808: + // https://tools.ietf.org/html/rfc1808 (page 4) + // + // We encode everything outside of the 'unreserved' character class, except for '/'. + // That is, every char not in [a-zA-Z0-9$_.+!*'(),/-]. + + static const char escape_table[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0, // !"#$%&'()*+,-./ + 0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1, // 0123456789:;<=>? + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // @ABCDEFGHIJKLMNO + 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0, // PQRSTUVWXYZ[\]^_ + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // `abcdefghijklmno + 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1, // pqrstuvwxyz{|}~ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + }; + + size_t length_orig = strlen(path); + size_t reserved_count = 0; + + for (size_t i = 0; i < length_orig; ++i) { + if (escape_table[(unsigned char)path[i]]) + ++reserved_count; + } + + if (!reserved_count) + return path; // Nothing to escape. + + // Each reserved char will take up 2 extra characters ('&' => '%26'). + size_t length_new = length_orig + reserved_count*2; + + char *new_path = apr_pcalloc(pool, length_new + 1); + assert(new_path); + for (size_t i = 0, j = 0; + i < length_orig && j < length_new; + ++i) { + + if (escape_table[(unsigned char)path[i]]) { + sprintf(new_path + j, "%%%.2X", (unsigned char)path[i]); + j += 3; + } else { + new_path[j++] = path[i]; + } + } + + return new_path; +} + /** * \brief Within a HTML directory listing, insert the contents of a local file. * @@ -1251,8 +1335,8 @@ static dav_error *deliver_directory( ap_escape_html(pool, resource->info->relative_uri), uri_ends_with_slash ? "" : "/", ap_escape_html(pool, resource->info->conf->rods_zone), - ap_escape_html(pool, ap_escape_uri(pool, root_dir_without_trailing_slash)), - ap_escape_html(pool, ap_escape_uri(pool, resource->info->relative_uri)), + ap_escape_html(pool, escape_uri_path(pool, root_dir_without_trailing_slash)), + ap_escape_html(pool, escape_uri_path(pool, resource->info->relative_uri)), uri_ends_with_slash ? "" : "/"); // Append a slash to fix relative links on this page. deliver_directory_try_insert_local_file(resource, bb, resource->info->conf->html_head); @@ -1283,8 +1367,8 @@ static dav_error *deliver_directory( *p = '\0'; apr_brigade_printf(bb, NULL, NULL, "%s%s", - ap_escape_html(pool, ap_escape_uri(pool, root_dir_without_trailing_slash)), - ap_escape_html(pool, ap_escape_uri(pool, path)), + ap_escape_html(pool, escape_uri_path(pool, root_dir_without_trailing_slash)), + ap_escape_html(pool, escape_uri_path(pool, path)), p == path ? "/" : ap_escape_html(pool, part+1), p == path ? "" : "/"); *p = '/'; @@ -1365,11 +1449,11 @@ static dav_error *deliver_directory( if (coll_entry.objType == COLL_OBJ_T) { // Collection links need a trailing slash for the '..' links to work correctly. apr_brigade_printf(bb, NULL, NULL, "%s/", - ap_escape_html(pool, ap_escape_uri(pool, name)), + ap_escape_html(pool, escape_uri_path(pool, name)), ap_escape_html(pool, name)); } else { apr_brigade_printf(bb, NULL, NULL, "%s", - ap_escape_html(pool, ap_escape_uri(pool, name)), + ap_escape_html(pool, escape_uri_path(pool, name)), ap_escape_html(pool, name)); }