... | ... |
@@ -14,63 +14,10 @@ has buffer_size => 8192; |
14 | 14 |
|
15 | 15 |
our $VERSION = '0.05'; |
16 | 16 |
my @SERVICES = ( |
17 |
- [ 'POST', 'service_rpc', qr{(.*?)/git-upload-pack$}, 'upload-pack' ], |
|
18 |
- [ 'POST', 'service_rpc', qr{(.*?)/git-receive-pack$}, 'receive-pack' ], |
|
19 |
- |
|
20 |
- [ 'GET', 'get_info_refs', qr{(.*?)/info/refs$} ], |
|
21 |
- [ 'GET', 'get_text_file', qr{(.*?)/HEAD$} ], |
|
22 |
- [ 'GET', 'get_text_file', qr{(.*?)/objects/info/alternates$} ], |
|
23 |
- [ 'GET', 'get_text_file', qr{(.*?)/objects/info/http-alternates$} ], |
|
24 |
- [ 'GET', 'get_info_packs', qr{(.*?)/objects/info/packs$} ], |
|
25 |
- [ 'GET', 'get_loose_object', qr{(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$} ], |
|
26 |
- [ |
|
27 |
- 'GET', 'get_pack_file', qr{(.*?)/objects/pack/pack-[0-9a-f]{40}\.pack$} |
|
28 |
- ], |
|
29 |
- [ 'GET', 'get_idx_file', qr{(.*?)/objects/pack/pack-[0-9a-f]{40}\.idx$} ], |
|
17 |
+ [ 'POST', 'service_rpc', qr{(.*?)/git-upload-pack$}, 'upload-pack' ], |
|
18 |
+ [ 'POST', 'service_rpc', qr{(.*?)/git-receive-pack$}, 'receive-pack' ], |
|
30 | 19 |
); |
31 | 20 |
|
32 |
-sub get_service { |
|
33 |
- my $self = shift; |
|
34 |
- my $req = shift; |
|
35 |
- |
|
36 |
- my $service = $req->param('service'); |
|
37 |
- return unless $service; |
|
38 |
- return unless substr( $service, 0, 4 ) eq 'git-'; |
|
39 |
- $service =~ s/git-//g; |
|
40 |
- return $service; |
|
41 |
-} |
|
42 |
- |
|
43 |
-sub match_routing { |
|
44 |
- my $self = shift; |
|
45 |
- my $req = shift; |
|
46 |
- |
|
47 |
- my ( $cmd, $path, $file, $rpc ); |
|
48 |
- for my $s (@SERVICES) { |
|
49 |
- my $match = $s->[2]; |
|
50 |
- if ( $req->path_info =~ /$match/ ) { |
|
51 |
- return ('not_allowed') if $s->[0] ne uc( $req->method ); |
|
52 |
- $cmd = $s->[1]; |
|
53 |
- $path = $1; |
|
54 |
- $file = $req->path_info; |
|
55 |
- $file =~ s|\Q$path/\E||; |
|
56 |
- $rpc = $s->[3]; |
|
57 |
- return ( $cmd, $path, $file, $rpc ); |
|
58 |
- } |
|
59 |
- } |
|
60 |
- return (); |
|
61 |
-} |
|
62 |
- |
|
63 |
-sub get_git_repo_dir { |
|
64 |
- my $self = shift; |
|
65 |
- my $path = shift; |
|
66 |
- |
|
67 |
- my $root = $self->root || `pwd`; |
|
68 |
- chomp $root; |
|
69 |
- $path = catdir( $root, $path ); |
|
70 |
- return $path if ( -d $path ); |
|
71 |
- return; |
|
72 |
-} |
|
73 |
- |
|
74 | 21 |
sub service_rpc { |
75 | 22 |
my $self = shift; |
76 | 23 |
my $args = shift; |
... | ... |
@@ -146,244 +93,4 @@ sub service_rpc { |
146 | 93 |
} |
147 | 94 |
} |
148 | 95 |
|
149 |
-sub get_info_refs { |
|
150 |
- my $self = shift; |
|
151 |
- my $args = shift; |
|
152 |
- |
|
153 |
- my $req = $args->{req}; |
|
154 |
- my $service = $self->get_service($req); |
|
155 |
- if ( $self->has_access( $args->{req}, $service ) ) { |
|
156 |
- my @cmd = |
|
157 |
- $self->git_command( $service, '--stateless-rpc', '--advertise-refs', |
|
158 |
- '.' ); |
|
159 |
- |
|
160 |
- my ( $cout, $cerr ) = ( gensym, gensym ); |
|
161 |
- my $pid = open3( my $cin, $cout, $cerr, @cmd ); |
|
162 |
- close $cin; |
|
163 |
- my ( $refs, $err, $buf ) = ( '', '', '' ); |
|
164 |
- my $s = IO::Select->new( $cout, $cerr ); |
|
165 |
- while ( my @ready = $s->can_read ) { |
|
166 |
- for my $handle (@ready) { |
|
167 |
- while ( sysread( $handle, $buf, BUFFER_SIZE ) ) { |
|
168 |
- if ( $handle == $cerr ) { |
|
169 |
- $err .= $buf; |
|
170 |
- } |
|
171 |
- else { |
|
172 |
- $refs .= $buf; |
|
173 |
- } |
|
174 |
- } |
|
175 |
- $s->remove($handle) if eof($handle); |
|
176 |
- } |
|
177 |
- } |
|
178 |
- close $cout; |
|
179 |
- close $cerr; |
|
180 |
- waitpid( $pid, 0 ); |
|
181 |
- |
|
182 |
- if ($err) { |
|
183 |
- $req->env->{'psgi.errors'}->print("git command failed: $err"); |
|
184 |
- return $self->return_400; |
|
185 |
- } |
|
186 |
- |
|
187 |
- my $res = $req->new_response(200); |
|
188 |
- $res->headers( |
|
189 |
- [ |
|
190 |
- 'Content-Type' => |
|
191 |
- sprintf( 'application/x-git-%s-advertisement', $service ), |
|
192 |
- ] |
|
193 |
- ); |
|
194 |
- my $body = |
|
195 |
- pkt_write("# service=git-${service}\n") . pkt_flush() . $refs; |
|
196 |
- $res->body($body); |
|
197 |
- return $res->finalize; |
|
198 |
- } |
|
199 |
- else { |
|
200 |
- return $self->dumb_info_refs($args); |
|
201 |
- } |
|
202 |
-} |
|
203 |
- |
|
204 |
-sub dumb_info_refs { |
|
205 |
- my $self = shift; |
|
206 |
- my $args = shift; |
|
207 |
- $self->update_server_info; |
|
208 |
- $self->send_file( $args, "text/plain; charset=utf-8" ); |
|
209 |
-} |
|
210 |
- |
|
211 |
-sub get_info_packs { |
|
212 |
- my $self = shift; |
|
213 |
- my $args = shift; |
|
214 |
- $self->send_file( $args, "text/plain; charset=utf-8" ); |
|
215 |
-} |
|
216 |
- |
|
217 |
-sub get_loose_object { |
|
218 |
- my $self = shift; |
|
219 |
- my $args = shift; |
|
220 |
- $self->send_file( $args, "application/x-git-loose-object" ); |
|
221 |
-} |
|
222 |
- |
|
223 |
-sub get_pack_file { |
|
224 |
- my $self = shift; |
|
225 |
- my $args = shift; |
|
226 |
- $self->send_file( $args, "application/x-git-packed-objects" ); |
|
227 |
-} |
|
228 |
- |
|
229 |
-sub get_idx_file { |
|
230 |
- my $self = shift; |
|
231 |
- my $args = shift; |
|
232 |
- $self->send_file( $args, "application/x-git-packed-objects-toc" ); |
|
233 |
-} |
|
234 |
- |
|
235 |
-sub get_text_file { |
|
236 |
- my $self = shift; |
|
237 |
- my $args = shift; |
|
238 |
- $self->send_file( $args, "text/plain" ); |
|
239 |
-} |
|
240 |
- |
|
241 |
-sub update_server_info { |
|
242 |
- my $self = shift; |
|
243 |
- system( $self->git_command('update-server-info') ); |
|
244 |
-} |
|
245 |
- |
|
246 |
-sub git_command { |
|
247 |
- my $self = shift; |
|
248 |
- my @commands = @_; |
|
249 |
- my $git_bin = $self->git_path; |
|
250 |
- return ( $git_bin, @commands ); |
|
251 |
-} |
|
252 |
- |
|
253 |
-sub has_access { |
|
254 |
- my $self = shift; |
|
255 |
- my ( $req, $rpc, $check_content_type ) = @_; |
|
256 |
- |
|
257 |
- if ( $check_content_type |
|
258 |
- && $req->content_type ne |
|
259 |
- sprintf( "application/x-git-%s-request", $rpc ) ) |
|
260 |
- { |
|
261 |
- return; |
|
262 |
- } |
|
263 |
- |
|
264 |
- return if !$rpc; |
|
265 |
- return $self->received_pack if $rpc eq 'receive-pack'; |
|
266 |
- return $self->upload_pack if $rpc eq 'upload-pack'; |
|
267 |
- return; |
|
268 |
-} |
|
269 |
- |
|
270 |
-sub send_file { |
|
271 |
- my $self = shift; |
|
272 |
- my ( $args, $content_type ) = @_; |
|
273 |
- |
|
274 |
- my $file = $args->{reqfile}; |
|
275 |
- return $self->return_404 unless -e $file; |
|
276 |
- |
|
277 |
- my @stat = stat $file; |
|
278 |
- my $res = $args->{req}->new_response(200); |
|
279 |
- $res->headers( |
|
280 |
- [ |
|
281 |
- 'Content-Type' => $content_type, |
|
282 |
- 'Last-Modified' => HTTP::Date::time2str( $stat[9] ), |
|
283 |
- 'Expires' => 'Fri, 01 Jan 1980 00:00:00 GMT', |
|
284 |
- 'Pragma' => 'no-cache', |
|
285 |
- 'Cache-Control' => 'no-cache, max-age=0, must-revalidate', |
|
286 |
- ] |
|
287 |
- ); |
|
288 |
- |
|
289 |
- if ( $stat[7] ) { |
|
290 |
- $res->header( 'Content-Length' => $stat[7] ); |
|
291 |
- } |
|
292 |
- open my $fh, "<:raw", $file |
|
293 |
- or return $self->return_403; |
|
294 |
- |
|
295 |
- Plack::Util::set_io_path( $fh, Cwd::realpath($file) ); |
|
296 |
- $res->body($fh); |
|
297 |
- $res->finalize; |
|
298 |
-} |
|
299 |
- |
|
300 |
-sub pkt_flush { |
|
301 |
- my $self = shift; |
|
302 |
- |
|
303 |
- return '0000'; |
|
304 |
-} |
|
305 |
- |
|
306 |
-sub pkt_write { |
|
307 |
- my $self = shift; |
|
308 |
- my $str = shift; |
|
309 |
- return sprintf( '%04x', length($str) + 4 ) . $str; |
|
310 |
-} |
|
311 |
- |
|
312 |
-sub return_not_allowed { |
|
313 |
- my $self = shift; |
|
314 |
- my $env = shift; |
|
315 |
- if ( $env->{SERVER_PROTOCOL} eq 'HTTP/1.1' ) { |
|
316 |
- return [ |
|
317 |
- 405, [ 'Content-Type' => 'text/plain', 'Content-Length' => 18 ], |
|
318 |
- ['Method Not Allowed'] |
|
319 |
- ]; |
|
320 |
- } |
|
321 |
- else { |
|
322 |
- return [ |
|
323 |
- 400, [ 'Content-Type' => 'text/plain', 'Content-Length' => 11 ], |
|
324 |
- ['Bad Request'] |
|
325 |
- ]; |
|
326 |
- } |
|
327 |
-} |
|
328 |
- |
|
329 |
-sub return_403 { |
|
330 |
- my $self = shift; |
|
331 |
- return [ |
|
332 |
- 403, [ 'Content-Type' => 'text/plain', 'Content-Length' => 9 ], |
|
333 |
- ['Forbidden'] |
|
334 |
- ]; |
|
335 |
-} |
|
336 |
- |
|
337 |
-sub return_400 { |
|
338 |
- my $self = shift; |
|
339 |
- return [ |
|
340 |
- 400, [ 'Content-Type' => 'text/plain', 'Content-Length' => 11 ], |
|
341 |
- ['Bad Request'] |
|
342 |
- ]; |
|
343 |
-} |
|
344 |
- |
|
345 |
-sub return_404 { |
|
346 |
- my $self = shift; |
|
347 |
- return [ |
|
348 |
- 404, [ 'Content-Type' => 'text/plain', 'Content-Length' => 9 ], |
|
349 |
- ['Not Found'] |
|
350 |
- ]; |
|
351 |
-} |
|
352 |
- |
|
353 | 96 |
1; |
354 |
-__END__ |
|
355 |
- |
|
356 |
-=head1 NAME |
|
357 |
- |
|
358 |
- Plack::App::GitSmartHttp - Git Smart HTTP Server PSGI(Plack) Implementation |
|
359 |
- |
|
360 |
-=head1 SYNOPSIS |
|
361 |
- |
|
362 |
- use Plack::App::GitSmartHttp; |
|
363 |
- |
|
364 |
- Plack::App::GitSmartHttp->new( |
|
365 |
- root => '/var/git/repos', |
|
366 |
- git_path => '/usr/bin/git', |
|
367 |
- upload_pack => 1, |
|
368 |
- received_pack => 1 |
|
369 |
- )->to_app; |
|
370 |
- |
|
371 |
-=head1 DESCRIPTION |
|
372 |
- |
|
373 |
- Plack::App::GitSmartHttp is Git Smart HTTP Server PSGI(Plack) Implementation. |
|
374 |
- |
|
375 |
-=head1 AUTHOR |
|
376 |
- |
|
377 |
- Ryuzo Yamamoto E<lt>ryuzo.yamamoto@gmail.comE<gt> |
|
378 |
- |
|
379 |
-=head1 SEE ALSO |
|
380 |
- |
|
381 |
- Smart HTTP Transport : <http://progit.org/2010/03/04/smart-http.html> |
|
382 |
- Grack : <https://github.com/schacon/grack> |
|
383 |
- |
|
384 |
-=head1 LICENSE |
|
385 |
- |
|
386 |
-This library is free software; you can redistribute it and/or modify |
|
387 |
-it under the same terms as Perl itself. |
|
388 |
- |
|
389 |
-=cut |
... | ... |
@@ -7,7 +7,8 @@ |
7 | 7 |
my $project = param('project'); |
8 | 8 |
my $git = app->git; |
9 | 9 |
my $sh = app->smart_http; |
10 |
- |
|
10 |
+ |
|
11 |
+ # Smart HTTP |
|
11 | 12 |
if ($service eq 'git-upload-pack') { |
12 | 13 |
|
13 | 14 |
my $rep = $git->rep($user, $project); |
... | ... |
@@ -44,15 +45,17 @@ |
44 | 45 |
|
45 | 46 |
$self->res->headers->content_type('application/x-git-upload-pack-advertisement'); |
46 | 47 |
|
47 |
- my $data = |
|
48 |
- $sh->pkt_write("# service=git-upload-pack\n") . $sh->pkt_flush() . $refs; |
|
49 |
- |
|
50 |
- warn $data; |
|
48 |
+ # Data start |
|
49 |
+ my $message = "# service=git-upload-pack\n"; |
|
50 |
+ my $data = sprintf( '%04x', length($message) + 4 ) . $message . '0000' . $refs; |
|
51 | 51 |
|
52 | 52 |
$self->render(data => $data); |
53 | 53 |
return; |
54 | 54 |
} |
55 |
+ # Dumb HTTP |
|
55 | 56 |
else { |
56 |
- $sh->dumb_info_refs; |
|
57 |
+ $git->cmd($user, $project, 'update-server-info'); |
|
58 |
+ $self->render_static("../data/rep/$user/$project.git/info/refs"); |
|
59 |
+ return; |
|
57 | 60 |
} |
58 | 61 |
%> |