apps/kepler/handlers.go 7.8 K raw
1
package main
2
3
import (
4
	"mime"
5
	"net/http"
6
	"path"
7
	"strconv"
8
	"strings"
9
)
10
11
const commitsPerPage = 30
12
13
func (a *App) base(repoName string) pageBase {
14
	return pageBase{SiteName: a.SiteName, RepoName: repoName, BaseURL: a.BaseURL}
15
}
16
17
// requestBaseURL derives the externally-visible base URL (scheme://host) from
18
// the incoming request, honoring a reverse proxy's X-Forwarded-Proto.
19
func (a *App) requestBaseURL(r *http.Request) string {
20
	scheme := "http"
21
	if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
22
		scheme = "https"
23
	}
24
	return scheme + "://" + r.Host
25
}
26
27
func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
28
	repos, err := a.listRepos()
29
	if err != nil {
30
		a.Log.Error("list repos failed", "err", err)
31
		http.Error(w, "failed to list repos", http.StatusInternalServerError)
32
		return
33
	}
34
	a.renderPage(w, "index.html", indexPageData{pageBase: a.base(""), Repos: repos})
35
}
36
37
func (a *App) repoHandler(w http.ResponseWriter, r *http.Request) {
38
	repo, summary, err := a.openRepo(r.PathValue("repo"))
39
	if err != nil {
40
		http.NotFound(w, r)
41
		return
42
	}
43
	branches, _ := listBranches(repo)
44
	tags, _ := listTags(repo)
45
	commits, _, _ := commitLog(repo, summary.DefaultRef, 0, 1)
46
	entries, _, _ := treeAt(repo, summary.DefaultRef, "")
47
48
	var latest CommitInfo
49
	if len(commits) > 0 {
50
		latest = commits[0]
51
	}
52
53
	data := repoPageData{
54
		pageBase:     a.base(summary.Name),
55
		Repo:         summary,
56
		Ref:          summary.DefaultRef,
57
		DefaultRef:   summary.DefaultRef,
58
		Branches:     branches,
59
		Tags:         tags,
60
		Commits:      commits,
61
		LatestCommit: latest,
62
		HasLatest:    len(commits) > 0,
63
		Entries:      entries,
64
	}
65
66
	if a.CloneBaseURL != "" {
67
		data.CloneHTTPSURL = a.CloneBaseURL + "/" + summary.Name + ".git"
68
	}
69
	if a.CloneSSHHost != "" {
70
		data.CloneSSHURL = a.CloneSSHHost + ":" + summary.Name + ".git"
71
	}
72
73
	if c, err := resolveRef(repo, summary.DefaultRef); err == nil {
74
		if tree, err := c.Tree(); err == nil {
75
			if src, ok := findReadme(tree); ok {
76
				rawBase := "/" + summary.Name + "/raw/" + summary.DefaultRef + "/"
77
				if html, err := renderMarkdown(src, rawBase); err == nil {
78
					data.ReadmeHTML = html
79
					data.HasReadme = true
80
				}
81
			}
82
		}
83
	}
84
85
	a.renderPage(w, "repo.html", data)
86
}
87
88
func (a *App) treeHandler(w http.ResponseWriter, r *http.Request) {
89
	repo, summary, err := a.openRepo(r.PathValue("repo"))
90
	if err != nil {
91
		http.NotFound(w, r)
92
		return
93
	}
94
	ref := r.PathValue("ref")
95
	subPath := strings.Trim(r.PathValue("path"), "/")
96
97
	entries, _, err := treeAt(repo, ref, subPath)
98
	if err != nil {
99
		http.NotFound(w, r)
100
		return
101
	}
102
103
	data := treePageData{
104
		pageBase:    a.base(summary.Name),
105
		Repo:        summary,
106
		Ref:         ref,
107
		DefaultRef:  summary.DefaultRef,
108
		Path:        subPath,
109
		Breadcrumbs: makeBreadcrumbs(summary.Name, ref, subPath),
110
		Entries:     entries,
111
	}
112
	a.renderPage(w, "tree.html", data)
113
}
114
115
func (a *App) blobHandler(w http.ResponseWriter, r *http.Request) {
116
	repo, summary, err := a.openRepo(r.PathValue("repo"))
117
	if err != nil {
118
		http.NotFound(w, r)
119
		return
120
	}
121
	ref := r.PathValue("ref")
122
	subPath := strings.Trim(r.PathValue("path"), "/")
123
124
	content, size, isBin, err := blobAt(repo, ref, subPath)
125
	if err != nil {
126
		http.NotFound(w, r)
127
		return
128
	}
129
130
	data := blobPageData{
131
		pageBase:    a.base(summary.Name),
132
		Repo:        summary,
133
		Ref:         ref,
134
		DefaultRef:  summary.DefaultRef,
135
		Path:        subPath,
136
		Breadcrumbs: makeBreadcrumbs(summary.Name, ref, subPath),
137
		Binary:      isBin,
138
		Size:        size,
139
	}
140
	if !isBin {
141
		data.HighlightedHTML = renderBlobLines(content)
142
	}
143
	a.renderPage(w, "blob.html", data)
144
}
145
146
func (a *App) rawHandler(w http.ResponseWriter, r *http.Request) {
147
	repo, _, err := a.openRepo(r.PathValue("repo"))
148
	if err != nil {
149
		http.NotFound(w, r)
150
		return
151
	}
152
	ref := r.PathValue("ref")
153
	subPath := strings.Trim(r.PathValue("path"), "/")
154
155
	data, err := blobBytes(repo, ref, subPath)
156
	if err != nil {
157
		http.NotFound(w, r)
158
		return
159
	}
160
	ct := http.DetectContentType(data)
161
	// http.DetectContentType can't sniff SVG (returns text/xml or
162
	// text/plain), which browsers won't render in <img>. Trust the
163
	// extension for types the sniffer gets wrong.
164
	if ext := strings.ToLower(path.Ext(subPath)); ext != "" {
165
		if byExt := mime.TypeByExtension(ext); byExt != "" {
166
			ct = byExt
167
		}
168
	}
169
	w.Header().Set("Content-Type", ct)
170
	_, _ = w.Write(data)
171
}
172
173
func (a *App) logHandler(w http.ResponseWriter, r *http.Request) {
174
	repo, summary, err := a.openRepo(r.PathValue("repo"))
175
	if err != nil {
176
		http.NotFound(w, r)
177
		return
178
	}
179
	ref := r.PathValue("ref")
180
	page := 0
181
	if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil && p > 0 {
182
		page = p
183
	}
184
	commits, hasNext, err := commitLog(repo, ref, page, commitsPerPage)
185
	if err != nil {
186
		http.NotFound(w, r)
187
		return
188
	}
189
	a.renderPage(w, "log.html", logPageData{
190
		pageBase:   a.base(summary.Name),
191
		Repo:       summary,
192
		Ref:        ref,
193
		DefaultRef: summary.DefaultRef,
194
		Commits:    commits,
195
		Page:       page,
196
		NextPage:   page + 1,
197
		PrevPage:   page - 1,
198
		HasNext:    hasNext,
199
		HasPrev:    page > 0,
200
	})
201
}
202
203
func (a *App) commitHandler(w http.ResponseWriter, r *http.Request) {
204
	repo, summary, err := a.openRepo(r.PathValue("repo"))
205
	if err != nil {
206
		http.NotFound(w, r)
207
		return
208
	}
209
	sha := r.PathValue("sha")
210
	info, files, stats, err := commitDiff(repo, sha)
211
	if err != nil {
212
		http.NotFound(w, r)
213
		return
214
	}
215
	a.renderPage(w, "commit.html", commitPageData{
216
		pageBase:   a.base(summary.Name),
217
		Repo:       summary,
218
		DefaultRef: summary.DefaultRef,
219
		Commit:     info,
220
		Files:      files,
221
		Stats:      stats,
222
	})
223
}
224
225
func (a *App) refsHandler(w http.ResponseWriter, r *http.Request) {
226
	repo, summary, err := a.openRepo(r.PathValue("repo"))
227
	if err != nil {
228
		http.NotFound(w, r)
229
		return
230
	}
231
	branches, _ := listBranches(repo)
232
	tags, _ := listTags(repo)
233
	a.renderPage(w, "refs.html", refsPageData{
234
		pageBase:   a.base(summary.Name),
235
		Repo:       summary,
236
		DefaultRef: summary.DefaultRef,
237
		Branches:   branches,
238
		Tags:       tags,
239
	})
240
}
241
242
func (a *App) archiveHandler(w http.ResponseWriter, r *http.Request) {
243
	repo, summary, err := a.openRepo(r.PathValue("repo"))
244
	if err != nil {
245
		http.NotFound(w, r)
246
		return
247
	}
248
	ref, format, ok := parseArchiveName(r.PathValue("name"))
249
	if !ok {
250
		http.NotFound(w, r)
251
		return
252
	}
253
	prefix := summary.Name + "-" + ref
254
	switch format {
255
	case "tar.gz":
256
		w.Header().Set("Content-Type", "application/gzip")
257
		w.Header().Set("Content-Disposition", `attachment; filename="`+summary.Name+"-"+ref+`.tar.gz"`)
258
		if err := writeTarGz(w, repo, ref, prefix); err != nil {
259
			a.Log.Error("tar.gz failed", "err", err)
260
		}
261
	case "zip":
262
		w.Header().Set("Content-Type", "application/zip")
263
		w.Header().Set("Content-Disposition", `attachment; filename="`+summary.Name+"-"+ref+`.zip"`)
264
		if err := writeZip(w, repo, ref, prefix); err != nil {
265
			a.Log.Error("zip failed", "err", err)
266
		}
267
	}
268
}
269
270
func (a *App) atomHandler(w http.ResponseWriter, r *http.Request) {
271
	repo, summary, err := a.openRepo(r.PathValue("repo"))
272
	if err != nil {
273
		http.NotFound(w, r)
274
		return
275
	}
276
	commits, _, err := commitLog(repo, summary.DefaultRef, 0, 20)
277
	if err != nil {
278
		http.Error(w, "log failed", http.StatusInternalServerError)
279
		return
280
	}
281
	baseURL := a.requestBaseURL(r)
282
	feed, err := buildAtomFeed(a.SiteName, summary.Name, baseURL, commits)
283
	if err != nil {
284
		http.Error(w, "atom build failed", http.StatusInternalServerError)
285
		return
286
	}
287
	w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
288
	_, _ = w.Write(feed)
289
}
290
291
func makeBreadcrumbs(repoName, ref, subPath string) []Breadcrumb {
292
	out := []Breadcrumb{{Name: repoName, Href: "/" + repoName + "/tree/" + ref}}
293
	if subPath == "" {
294
		return out
295
	}
296
	parts := strings.Split(subPath, "/")
297
	acc := ""
298
	for _, p := range parts {
299
		if acc == "" {
300
			acc = p
301
		} else {
302
			acc = acc + "/" + p
303
		}
304
		out = append(out, Breadcrumb{Name: p, Href: "/" + repoName + "/tree/" + ref + "/" + acc})
305
	}
306
	return out
307
}