1
2
3
4
5
6
7
8
9 package github
10
11 import (
12 "context"
13 "encoding/base64"
14 "encoding/json"
15 "errors"
16 "fmt"
17 "io"
18 "net/http"
19 "net/url"
20 "path"
21 "strings"
22 )
23
24 var ErrPathForbidden = errors.New("path must not contain '..' due to auth vulnerability issue")
25
26
27 type RepositoryContent struct {
28 Type *string `json:"type,omitempty"`
29
30
31 Target *string `json:"target,omitempty"`
32 Encoding *string `json:"encoding,omitempty"`
33 Size *int `json:"size,omitempty"`
34 Name *string `json:"name,omitempty"`
35 Path *string `json:"path,omitempty"`
36
37
38
39 Content *string `json:"content,omitempty"`
40 SHA *string `json:"sha,omitempty"`
41 URL *string `json:"url,omitempty"`
42 GitURL *string `json:"git_url,omitempty"`
43 HTMLURL *string `json:"html_url,omitempty"`
44 DownloadURL *string `json:"download_url,omitempty"`
45 SubmoduleGitURL *string `json:"submodule_git_url,omitempty"`
46 }
47
48
49 type RepositoryContentResponse struct {
50 Content *RepositoryContent `json:"content,omitempty"`
51 Commit `json:"commit,omitempty"`
52 }
53
54
55 type RepositoryContentFileOptions struct {
56 Message *string `json:"message,omitempty"`
57 Content []byte `json:"content"`
58 SHA *string `json:"sha,omitempty"`
59 Branch *string `json:"branch,omitempty"`
60 Author *CommitAuthor `json:"author,omitempty"`
61 Committer *CommitAuthor `json:"committer,omitempty"`
62 }
63
64
65
66 type RepositoryContentGetOptions struct {
67 Ref string `url:"ref,omitempty"`
68 }
69
70
71 func (r RepositoryContent) String() string {
72 return Stringify(r)
73 }
74
75
76 func (r *RepositoryContent) GetContent() (string, error) {
77 var encoding string
78 if r.Encoding != nil {
79 encoding = *r.Encoding
80 }
81
82 switch encoding {
83 case "base64":
84 if r.Content == nil {
85 return "", errors.New("malformed response: base64 encoding of null content")
86 }
87 c, err := base64.StdEncoding.DecodeString(*r.Content)
88 return string(c), err
89 case "":
90 if r.Content == nil {
91 return "", nil
92 }
93 return *r.Content, nil
94 default:
95 return "", fmt.Errorf("unsupported content encoding: %v", encoding)
96 }
97 }
98
99
100
101
102 func (s *RepositoriesService) GetReadme(ctx context.Context, owner, repo string, opts *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
103 u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
104 u, err := addOptions(u, opts)
105 if err != nil {
106 return nil, nil, err
107 }
108
109 req, err := s.client.NewRequest("GET", u, nil)
110 if err != nil {
111 return nil, nil, err
112 }
113
114 readme := new(RepositoryContent)
115 resp, err := s.client.Do(ctx, req, readme)
116 if err != nil {
117 return nil, resp, err
118 }
119
120 return readme, resp, nil
121 }
122
123
124
125
126
127
128
129
130
131 func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *Response, error) {
132 dir := path.Dir(filepath)
133 filename := path.Base(filepath)
134 _, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
135 if err != nil {
136 return nil, resp, err
137 }
138
139 for _, contents := range dirContents {
140 if *contents.Name == filename {
141 if contents.DownloadURL == nil || *contents.DownloadURL == "" {
142 return nil, resp, fmt.Errorf("no download link found for %s", filepath)
143 }
144
145 dlResp, err := s.client.client.Get(*contents.DownloadURL)
146 if err != nil {
147 return nil, &Response{Response: dlResp}, err
148 }
149
150 return dlResp.Body, &Response{Response: dlResp}, nil
151 }
152 }
153
154 return nil, resp, fmt.Errorf("no file named %s found in %s", filename, dir)
155 }
156
157
158
159
160
161
162
163
164
165 func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *RepositoryContent, *Response, error) {
166 dir := path.Dir(filepath)
167 filename := path.Base(filepath)
168 _, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
169 if err != nil {
170 return nil, nil, resp, err
171 }
172
173 for _, contents := range dirContents {
174 if *contents.Name == filename {
175 if contents.DownloadURL == nil || *contents.DownloadURL == "" {
176 return nil, contents, resp, fmt.Errorf("no download link found for %s", filepath)
177 }
178
179 dlResp, err := s.client.client.Get(*contents.DownloadURL)
180 if err != nil {
181 return nil, contents, &Response{Response: dlResp}, err
182 }
183
184 return dlResp.Body, contents, &Response{Response: dlResp}, nil
185 }
186 }
187
188 return nil, nil, resp, fmt.Errorf("no file named %s found in %s", filename, dir)
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202 func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opts *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) {
203 if strings.Contains(path, "..") {
204 return nil, nil, nil, ErrPathForbidden
205 }
206
207 escapedPath := (&url.URL{Path: strings.TrimSuffix(path, "/")}).String()
208 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath)
209 u, err = addOptions(u, opts)
210 if err != nil {
211 return nil, nil, nil, err
212 }
213
214 req, err := s.client.NewRequest("GET", u, nil)
215 if err != nil {
216 return nil, nil, nil, err
217 }
218
219 var rawJSON json.RawMessage
220 resp, err = s.client.Do(ctx, req, &rawJSON)
221 if err != nil {
222 return nil, nil, resp, err
223 }
224
225 fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
226 if fileUnmarshalError == nil {
227 return fileContent, nil, resp, nil
228 }
229
230 directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
231 if directoryUnmarshalError == nil {
232 return nil, directoryContent, resp, nil
233 }
234
235 return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", fileUnmarshalError, directoryUnmarshalError)
236 }
237
238
239
240
241
242 func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
243 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
244 req, err := s.client.NewRequest("PUT", u, opts)
245 if err != nil {
246 return nil, nil, err
247 }
248
249 createResponse := new(RepositoryContentResponse)
250 resp, err := s.client.Do(ctx, req, createResponse)
251 if err != nil {
252 return nil, resp, err
253 }
254
255 return createResponse, resp, nil
256 }
257
258
259
260
261
262 func (s *RepositoriesService) UpdateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
263 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
264 req, err := s.client.NewRequest("PUT", u, opts)
265 if err != nil {
266 return nil, nil, err
267 }
268
269 updateResponse := new(RepositoryContentResponse)
270 resp, err := s.client.Do(ctx, req, updateResponse)
271 if err != nil {
272 return nil, resp, err
273 }
274
275 return updateResponse, resp, nil
276 }
277
278
279
280
281
282 func (s *RepositoriesService) DeleteFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
283 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
284 req, err := s.client.NewRequest("DELETE", u, opts)
285 if err != nil {
286 return nil, nil, err
287 }
288
289 deleteResponse := new(RepositoryContentResponse)
290 resp, err := s.client.Do(ctx, req, deleteResponse)
291 if err != nil {
292 return nil, resp, err
293 }
294
295 return deleteResponse, resp, nil
296 }
297
298
299 type ArchiveFormat string
300
301 const (
302
303 Tarball ArchiveFormat = "tarball"
304
305
306 Zipball ArchiveFormat = "zipball"
307 )
308
309
310
311
312
313
314 func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat ArchiveFormat, opts *RepositoryContentGetOptions, followRedirects bool) (*url.URL, *Response, error) {
315 u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
316 if opts != nil && opts.Ref != "" {
317 u += fmt.Sprintf("/%s", opts.Ref)
318 }
319 resp, err := s.client.roundTripWithOptionalFollowRedirect(ctx, u, followRedirects)
320 if err != nil {
321 return nil, nil, err
322 }
323 defer resp.Body.Close()
324
325 if resp.StatusCode != http.StatusFound {
326 return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
327 }
328
329 parsedURL, err := url.Parse(resp.Header.Get("Location"))
330 if err != nil {
331 return nil, newResponse(resp), err
332 }
333
334 return parsedURL, newResponse(resp), nil
335 }
336
View as plain text