1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
# frozen_string_literal: true
class Projects::RepositoriesController < Projects::ApplicationController
include ExtractsPath
include StaticObjectExternalStorage
include HotlinkInterceptor
include Gitlab::RepositoryArchiveRateLimiter
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
skip_before_action :default_cache_headers, only: :archive
# Authorize
before_action :check_archive_rate_limiting!, only: :archive
before_action :require_non_empty_project, except: :create
before_action :intercept_hotlinking!, only: :archive
before_action :assign_archive_vars, only: :archive
before_action :assign_append_sha, only: :archive
before_action :authorize_download_code!
before_action :authorize_admin_project!, only: :create
before_action :redirect_to_external_storage, only: :archive, if: :static_objects_external_storage_enabled?
feature_category :source_code_management
def create
@project.create_repository unless @project.repository_exists?
redirect_to project_path(@project)
end
def archive
return render_404 if html_request?
set_cache_headers
return if archive_not_modified?
send_git_archive @repository, **repo_params
rescue StandardError => e
logger.error("#{self.class.name}: #{e}")
git_not_found!
end
private
def repo_params
@repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha }
end
def set_cache_headers
commit_id = archive_metadata['CommitId']
if Feature.enabled?(:improve_blobs_cache_headers)
expires_in(cache_max_age(commit_id),
public: Guest.can?(:download_code, project), must_revalidate: true, stale_if_error: 5.minutes,
stale_while_revalidate: 1.minute, 's-maxage': 1.minute)
fresh_when(strong_etag: [commit_id, archive_metadata['ArchivePath']])
else
expires_in cache_max_age(commit_id), public: Guest.can?(:download_code, project)
fresh_when(etag: archive_metadata['ArchivePath'])
end
end
def archive_not_modified?
# Check response freshness (Last-Modified and ETag)
# against request If-Modified-Since and If-None-Match conditions.
request.fresh?(response)
end
def archive_metadata
@archive_metadata ||= @repository.archive_metadata(
@ref,
'', # Where archives are stored isn't really important for ETag purposes
repo_params[:format],
path: repo_params[:path],
append_sha: @append_sha
)
end
def cache_max_age(commit_id)
if @ref == commit_id
# This is a link to an archive by a commit SHA. That means that the archive
# is immutable. The only reason to invalidate the cache is if the commit
# was deleted or if the user lost access to the repository.
Repository::ARCHIVE_CACHE_TIME_IMMUTABLE
else
# A branch or tag points at this archive. That means that the expected archive
# content may change over time.
Repository::ARCHIVE_CACHE_TIME
end
end
def assign_append_sha
@append_sha = params[:append_sha]
if @ref
shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
@append_sha = false if @filename == shortname
end
end
def assign_archive_vars
if params[:id]
@ref, @filename = extract_ref_and_filename(params[:id])
else
@ref = params[:ref]
@filename = nil
end
rescue InvalidPathError
render_404
end
# path can be of the form:
# master
# master/first.zip
# master/first/second.tar.gz
# master/first/second/third.zip
#
# In the archive case, we know that the last value is always the filename, so we
# do a greedy match to extract the ref. This avoid having to pull all ref names
# from Redis.
def extract_ref_and_filename(id)
path = id.strip
data = path.match(%r{(.*)/(.*)})
if data
[data[1], data[2]]
else
[path, nil]
end
end
def check_archive_rate_limiting!
check_archive_rate_limit!(current_user, @project) do
render(plain: _('This archive has been requested too many times. Try again later.'), status: :too_many_requests)
end
end
end
Projects::RepositoriesController.prepend_mod_with('Projects::RepositoriesController')
|