Author: svn-role Date: Tue Dec 3 04:00:09 2019 New Revision: 1870739 URL: http://svn.apache.org/viewvc?rev=1870739&view=rev Log: Merge r1866425 from trunk:
* r1866425 mod_dav_svn: Set Last-Modified response header for 'external' GET requests. Justification: The Last-Modified header was removed in r1724790 for performance reasons. However, for external requests the Last-Modified header is needed for certain use cases. Bringing it back only for external requests fixes these, while keeping the performance gain for checkout / update. Votes: +1: jcorvel, brane, rhuijben Modified: subversion/branches/1.10.x/ (props changed) subversion/branches/1.10.x/STATUS subversion/branches/1.10.x/subversion/mod_dav_svn/repos.c subversion/branches/1.10.x/subversion/tests/cmdline/mod_dav_svn_tests.py Propchange: subversion/branches/1.10.x/ ------------------------------------------------------------------------------ Merged /subversion/trunk:r1866425 Modified: subversion/branches/1.10.x/STATUS URL: http://svn.apache.org/viewvc/subversion/branches/1.10.x/STATUS?rev=1870739&r1=1870738&r2=1870739&view=diff ============================================================================== --- subversion/branches/1.10.x/STATUS (original) +++ subversion/branches/1.10.x/STATUS Tue Dec 3 04:00:09 2019 @@ -34,13 +34,3 @@ Veto-blocked changes: Approved changes: ================= - - * r1866425 - mod_dav_svn: Set Last-Modified response header for 'external' GET requests. - Justification: - The Last-Modified header was removed in r1724790 for performance reasons. - However, for external requests the Last-Modified header is needed for - certain use cases. Bringing it back only for external requests fixes - these, while keeping the performance gain for checkout / update. - Votes: - +1: jcorvel, brane, rhuijben Modified: subversion/branches/1.10.x/subversion/mod_dav_svn/repos.c URL: http://svn.apache.org/viewvc/subversion/branches/1.10.x/subversion/mod_dav_svn/repos.c?rev=1870739&r1=1870738&r2=1870739&view=diff ============================================================================== --- subversion/branches/1.10.x/subversion/mod_dav_svn/repos.c (original) +++ subversion/branches/1.10.x/subversion/mod_dav_svn/repos.c Tue Dec 3 04:00:09 2019 @@ -3139,6 +3139,50 @@ seek_stream(dav_stream *stream, apr_off_ && resource->baselined)) +/* Return the last modification time of RESOURCE, or -1 if the DAV + resource type is not handled, or if an error occurs. Temporary + allocations are made from RESOURCE->POOL. */ +static apr_time_t +get_last_modified(const dav_resource *resource) +{ + apr_time_t last_modified; + svn_error_t *serr; + svn_revnum_t created_rev; + svn_string_t *date_time; + + if (RESOURCE_LACKS_ETAG_POTENTIAL(resource)) + return -1; + + if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root, + resource->info->repos_path, + resource->pool))) + { + svn_error_clear(serr); + return -1; + } + + if ((serr = svn_fs_revision_prop2(&date_time, resource->info->repos->fs, + created_rev, SVN_PROP_REVISION_DATE, + TRUE, resource->pool, resource->pool))) + { + svn_error_clear(serr); + return -1; + } + + if (date_time == NULL || date_time->data == NULL) + return -1; + + if ((serr = svn_time_from_cstring(&last_modified, date_time->data, + resource->pool))) + { + svn_error_clear(serr); + return -1; + } + + return last_modified; +} + + const char * dav_svn__getetag(const dav_resource *resource, apr_pool_t *pool) { @@ -3219,6 +3263,23 @@ set_headers(request_rec *r, const dav_re if (!resource->exists) return NULL; + if ((resource->type == DAV_RESOURCE_TYPE_REGULAR) + && (resource->info->repos_path == resource->info->uri_path->data)) + { + /* Include Last-Modified header for 'external' GET or HEAD requests + (i.e. requests to URI's not under /!svn), to support usage of an + SVN server as a file server, where the client needs timestamps + for instance to use as "last modification time" of files on disk. */ + const apr_time_t last_modified = get_last_modified(resource); + if (last_modified != -1) + { + /* Note the modification time for the requested resource, and + include the Last-Modified header in the response. */ + ap_update_mtime(r, last_modified); + ap_set_last_modified(r); + } + } + /* generate our etag and place it into the output */ apr_table_setn(r->headers_out, "ETag", dav_svn__getetag(resource, resource->pool)); Modified: subversion/branches/1.10.x/subversion/tests/cmdline/mod_dav_svn_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/1.10.x/subversion/tests/cmdline/mod_dav_svn_tests.py?rev=1870739&r1=1870738&r2=1870739&view=diff ============================================================================== --- subversion/branches/1.10.x/subversion/tests/cmdline/mod_dav_svn_tests.py (original) +++ subversion/branches/1.10.x/subversion/tests/cmdline/mod_dav_svn_tests.py Tue Dec 3 04:00:09 2019 @@ -640,6 +640,53 @@ def propfind_propname(sbox): actual_response = r.read() verify_xml_response(expected_response, actual_response) +@SkipUnless(svntest.main.is_ra_type_dav) +def last_modified_header(sbox): + "verify 'Last-Modified' header on 'external' GETs" + + sbox.build(create_wc=False, read_only=True) + + headers = { + 'Authorization': 'Basic ' + base64.b64encode(b'jconstant:rayjandom').decode(), + } + + h = svntest.main.create_http_connection(sbox.repo_url) + + # GET /repos/iota + # Expect to see a Last-Modified header. + h.request('GET', sbox.repo_url + '/iota', None, headers) + r = h.getresponse() + if r.status != httplib.OK: + raise svntest.Failure('Request failed: %d %s' % (r.status, r.reason)) + svntest.verify.compare_and_display_lines(None, 'Last-Modified', + svntest.verify.RegexOutput('.+'), + r.getheader('Last-Modified')) + r.read() + + # HEAD /repos/iota + # Expect to see a Last-Modified header. + h.request('HEAD', sbox.repo_url + '/iota', None, headers) + r = h.getresponse() + if r.status != httplib.OK: + raise svntest.Failure('Request failed: %d %s' % (r.status, r.reason)) + svntest.verify.compare_and_display_lines(None, 'Last-Modified', + svntest.verify.RegexOutput('.+'), + r.getheader('Last-Modified')) + r.read() + + # GET /repos/!svn/rvr/1/iota + # There should not be a Last-Modified header (it's costly and not useful, + # see r1724790) + h.request('GET', sbox.repo_url + '/!svn/rvr/1/iota', None, headers) + r = h.getresponse() + if r.status != httplib.OK: + raise svntest.Failure('Request failed: %d %s' % (r.status, r.reason)) + last_modified = r.getheader('Last-Modified') + if last_modified: + raise svntest.Failure('Unexpected Last-Modified header: %s' % last_modified) + r.read() + + ######################################################################## # Run the tests @@ -652,6 +699,7 @@ test_list = [ None, propfind_404, propfind_allprop, propfind_propname, + last_modified_header, ] serial_only = True