This file is indexed.

/usr/share/perl5/File/Dropbox.pm is in libfile-dropbox-perl 0.7-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
package File::Dropbox;
use strict;
use warnings;
use feature ':5.10';
use base qw{ Tie::Handle Exporter };
use Symbol;
use JSON;
use Errno qw{ ENOENT EISDIR EINVAL EPERM EACCES EAGAIN ECANCELED EFBIG };
use Fcntl qw{ SEEK_CUR SEEK_SET SEEK_END };
use Furl;
use IO::Socket::SSL;
use Net::DNS::Lite;

our $VERSION = 0.7;
our @EXPORT_OK = qw{ contents metadata putfile movefile copyfile createfolder deletefile };

my $hosts = {
	content => 'api-content.dropbox.com',
	api     => 'api.dropbox.com',
};

my $version = 1;

my $header1 = join ', ',
	'OAuth oauth_version="1.0"',
	'oauth_signature_method="PLAINTEXT"',
	'oauth_consumer_key="%s"',
	'oauth_token="%s"',
	'oauth_signature="%s&%s"';

my $header2 = 'Bearer %s';

sub new {
	my $self = Symbol::gensym;
	tie *$self, __PACKAGE__, my $this = { @_[1 .. @_ - 1] };

	*$self = $this;

	return $self;
} # new

sub TIEHANDLE {
	my $self = bless $_[1], ref $_[0] || $_[0];

	$self->{'chunk'}   //= 4 << 20;
	$self->{'root'}    //= 'sandbox';

	die 'Unexpected root value'
		unless $self->{'root'} =~ m{^(?:drop|sand)box$};

	$self->{'furl'} = Furl->new(
		timeout   => 10,
		inet_aton => \&Net::DNS::Lite::inet_aton,
		ssl_opts  => {
			SSL_verify_mode => SSL_VERIFY_PEER(),
		},

		%{ $self->{'furlopts'} //= {} },
	);

	$self->{'closed'}   = 1;
	$self->{'length'}   = 0;
	$self->{'position'} = 0;
	$self->{'mode'}     = '';
	$self->{'buffer'}   = '';

	return $self;
} # TIEHANDLE

sub READ {
	my ($self, undef, $length, $offset) = @_;

	undef $!;

	die 'Read is not supported on this handle'
		if $self->{'mode'} ne '<';

	substr($_[1] //= '', $offset // 0) = '', return 0
		if $self->EOF();

	my $furl = $self->{'furl'};

	my $url = 'https://';
	$url .= join '/', $hosts->{'content'}, $version;
	$url .= join '/', '/files', $self->{'root'}, $self->{'path'};

	my $response = $furl->get($url, [
		Range => sprintf('bytes=%i-%i', $self->{'position'}, $self->{'position'} + ($length || 1)),

		@{ &__headers__ },
	]);

	return $self->__error__($response)
		if $response->code != 206;

	my $meta  = $response->header('X-Dropbox-Metadata');
	my $bytes = $response->header('Content-Length');

	$self->{'position'} += $bytes > $length? $length : $bytes;

	substr($_[1] //= '', $offset // 0) = substr $response->content(), 0, $length;

	return $bytes;
} # READ

sub READLINE {
	my ($self) = @_;
	my $length;

	undef $!;

	die 'Readline is not supported on this handle'
		if $self->{'mode'} ne '<';

	if ($self->EOF()) {
		return if wantarray;

		# Special case: slurp mode + scalar context + empty file
		# return '' for first call and undef for subsequent
		return ''
			unless $self->{'eof'} or defined $/;

		$self->{'eof'} = 1;
		return undef;
	}

	{
		$length = length $self->{'buffer'};

		if (not wantarray and $length and defined $/) {
			my $position = index $self->{'buffer'}, $/;

			if (~$position) {
				$self->{'position'} += ($position += length $/);
				return substr $self->{'buffer'}, 0, $position, '';
			}
		}

		local $self->{'position'} = $self->{'position'} + $length;

		my $bytes = $self->READ($self->{'buffer'}, $self->{'chunk'}, $length);

		return if $!;
		redo   if not $length or $bytes;
	}

	$length = length $self->{'buffer'};

	if ($length) {
		# Multiline
		if (wantarray and defined $/) {
			$self->{'position'} += $length;

			my ($position, $length) = (0, length $/);
			my @lines;

			foreach ($self->{'buffer'}) {
				while (~(my $offset = index $_, $/, $position)) {
					$offset += $length;
					push @lines, substr $_, $position, $offset - $position;
					$position = $offset;
				}

				push @lines, substr $_, $position
					if $position < length;

				$_ = '';
			}

			return @lines;
		}

		# Slurp or last chunk
		$self->{'position'} += $length;
		return substr $self->{'buffer'}, 0, $length, '';
	}

	return undef;
} # READLINE

sub SEEK {
	my ($self, $position, $whence) = @_;

	undef $!;

	die 'Seek is not supported on this handle'
		if $self->{'mode'} ne '<';

	$self->{'buffer'} = '';

	delete $self->{'eof'};

	if ($whence == SEEK_SET) {
		$self->{'position'} = $position
	}

	elsif ($whence == SEEK_CUR) {
		$self->{'position'} += $position
	}

	elsif ($whence == SEEK_END) {
		$self->{'position'} = $self->{'length'} + $position
	}

	else {
		$! = EINVAL;
		return 0;
	}

	$self->{'position'} = 0
		if $self->{'position'} < 0;

	return 1;
} # SEEK

sub TELL {
	my ($self) = @_;

	die 'Tell is not supported on this handle'
		if $self->{'mode'} ne '<';

	return $self->{'position'};
} # TELL

sub WRITE {
	my ($self, $buffer, $length, $offset) = @_;

	undef $!;

	die 'Write is not supported on this handle'
		if $self->{'mode'} ne '>';

	die 'Append-only writes supported'
		if $offset and $offset != $self->{'offset'} + $self->{'length'};

	$self->{'offset'} //= $offset;
	$self->{'buffer'}  .= $buffer;
	$self->{'length'}  += $length;

	$self->__flush__() or return 0
		while $self->{'length'} >= $self->{'chunk'};

	return 1;
} # WRITE

sub CLOSE {
	my ($self) = @_;

	undef $!;

	return 1
		if $self->{'closed'};

	my $mode = $self->{'mode'};

	if ($mode eq '>') {
		if ($self->{'length'} or not $self->{'upload_id'}) {
			do {
				@{ $self }{qw{ closed mode }} = (1, '') and return 0
					unless $self->__flush__();
			} while length $self->{'buffer'};
		}
	}

	$self->{'closed'} = 1;
	$self->{'mode'}   = '';

	return $self->__flush__()
		if $mode eq '>';

	return 1;
} # CLOSE

sub OPEN {
	my ($self, $mode, $file) = @_;

	undef $!;

	($mode, $file) = $mode =~ m{^([<>]?)(.*)$}s
		unless $file;

	$mode ||= '<';

	$mode = '<' if $mode eq 'r';
	$mode = '>' if $mode eq 'a' or $mode eq 'w';

	die 'Unsupported mode'
		unless $mode eq '<' or $mode eq '>';

	$self->CLOSE()
		unless $self->{'closed'};

	$self->{'length'}   = 0;
	$self->{'position'} = 0;
	$self->{'buffer'}   = '';

	delete $self->{'offset'};
	delete $self->{'revision'};
	delete $self->{'upload_id'};
	delete $self->{'meta'};
	delete $self->{'eof'};

	$self->{'path'} = $file
		or die 'Path required';

	return 0
		if $mode eq '<' and not $self->__meta__();

	$self->{'mode'}   = $mode;
	$self->{'closed'} = 0;

	return 1;
} # OPEN

sub EOF {
	my ($self) = @_;

	die 'Eof is not supported on this handle'
		if $self->{'mode'} ne '<';

	return $self->{'position'} >= $self->{'length'};
} # EOF

sub BINMODE { 1 }

sub __headers__ {
	return [
		'Authorization',
		$_[0]->{'oauth2'}?
			sprintf $header2, $_[0]->{'access_token'}:
			sprintf $header1, @{ $_[0] }{qw{ app_key access_token app_secret access_secret }},
	];
}

sub __flush__ {
	my ($self) = @_;
	my $furl = $self->{'furl'};
	my $url;

	$url  = 'https://';
	$url .= join '/', $hosts->{'content'}, $version;

	$url .= join '/', '/commit_chunked_upload', $self->{'root'}, $self->{'path'}
		if $self->{'closed'};

	$url .= '/chunked_upload'
		unless $self->{'closed'};

	$url .= '?';

	$url .= join '=', 'upload_id', $self->{'upload_id'}
		if $self->{'upload_id'};

	$url .= '&'
		if $self->{'upload_id'};

	$url .= join '=', 'offset', $self->{'offset'} || 0
		unless $self->{'closed'};

	my $response;

	unless ($self->{'closed'}) {
		use bytes;

		my $buffer = substr $self->{'buffer'}, 0, $self->{'chunk'}, '';
		my $length = length $buffer;

		$self->{'length'} -= $length;
		$self->{'offset'} += $length;

		$response = $furl->put($url, &__headers__, $buffer);
	} else {
		$response = $furl->post($url, &__headers__);
	}

	return $self->__error__($response)
		if $response->code != 200;

	$self->{'meta'} = from_json($response->content())
		if $self->{'closed'};

	unless ($self->{'upload_id'}) {
		$response = from_json($response->content());
		$self->{'upload_id'} = $response->{'upload_id'};
	}

	return 1;
} # __flush__

sub __meta__ {
	my ($self) = @_;
	my ($url, $meta);

	my $furl = $self->{'furl'};

	$url  = 'https://';
	$url .= join '/', $hosts->{'api'}, $version;
	$url .= join '/', '/metadata', $self->{'root'}, $self->{'path'};

	$url .= '?hash='. delete $self->{'hash'}
		if $self->{'hash'};

	my $response = $furl->get($url, &__headers__);

	my $code = $response->code();

	if ($code == 200) {
		$meta = $self->{'meta'} = from_json($response->content());

		# XXX: Dropbox returns metadata for recently deleted files
		if ($meta->{'is_deleted'}) {
			$! = ENOENT;
			return 0;
		}
	} elsif ($code != 304) {
		return $self->__error__($response);
	}

	if ($meta->{'is_dir'}) {
		$! = EISDIR;
		return 0;
	}

	$self->{'revision'} = $meta->{'rev'};
	$self->{'length'}   = $meta->{'bytes'};

	return 1;
} # __meta__

sub __fileops__ {
	my ($type, $handle, $source, $target) = @_;

	my $self = *$handle{'HASH'};
	my $furl = $self->{'furl'};
	my ($url, @arguments);

	$url  = 'https://';
	$url .= join '/', $hosts->{'api'}, $version;
	$url .= join '/', '/fileops', $type;

	if ($type eq 'move' or $type eq 'copy') {
		@arguments = (
			from_path => $source,
			to_path   => $target,
		);
	} else {
		@arguments = (
			path => $source,
		);
	}

	push @arguments, root => $self->{'root'};

	my $response = $furl->post($url, $self->__headers__(), \@arguments);

	return $self->__error__($response)
		if $response->code != 200;

	$self->{'meta'} = from_json($response->content());

	return 1;
} # __fileops__

sub __error__ {
	my ($self, $response) = @_;
	my $code = $response->code();

	if ($code == 400) {
		$! = EINVAL;
	}

	elsif ($code == 401 or $code == 403) {
		$! = EACCES;
	}

	elsif ($code == 404) {
		$! = ENOENT;
		return 0;
	}

	elsif ($code == 406) {
		$! = EPERM;
		return 0;
	}

	elsif ($code == 500 and $response->content() =~ m{\A(?:Cannot|Failed)}) {
		$! = ECANCELED;
	}

	elsif ($code == 503) {
		$self->{'meta'} = { retry => $response->header('Retry-After') };

		$! = EAGAIN;
	}

	elsif ($code == 507) {
		$! = EFBIG;
	}

	else {
		die join ' ', $code, $response->decoded_content();
	}

	return 0;
} # __error__

sub contents ($;$$) {
	my ($handle, $path, $hash) = @_;

	die 'GLOB reference expected'
		unless ref $handle eq 'GLOB';

	*$handle->{'hash'} = $hash
		if $hash;

	if (open $handle, '<', $path || '/' or $! != EISDIR) {
		delete *$handle->{'meta'};
		return;
	}

	undef $!;
	return @{ *$handle->{'meta'}{'contents'} };
} # contents

sub putfile ($$$) {
	my ($handle, $path, $data) = @_;

	die 'GLOB reference expected'
		unless ref $handle eq 'GLOB';

	close $handle or return 0;

	my $self = *$handle{'HASH'};
	my $furl = $self->{'furl'};
	my ($url, $length);

	$url  = 'https://';
	$url .= join '/', $hosts->{'content'}, $version;
	$url .= join '/', '/files_put', $self->{'root'}, $path;

	{
		use bytes;
		$length = length $data;
	}

	my $response = $furl->put($url, $self->__headers__(), $data);

	return $self->__error__($response)
		if $response->code != 200;

	$self->{'path'} = $path;
	$self->{'meta'} = from_json($response->content());

	return 1;
} # putfile

sub movefile    ($$$) { __fileops__('move', @_) }
sub copyfile    ($$$) { __fileops__('copy', @_) }
sub deletefile   ($$) { __fileops__('delete', @_) }
sub createfolder ($$) { __fileops__('create_folder', @_) }

sub metadata ($) {
	my ($handle) = @_;

	die 'GLOB reference expected'
		unless ref $handle eq 'GLOB';

	my $self = *$handle{'HASH'};

	die 'Meta is unavailable for incomplete upload'
		if $self->{'mode'} eq '>';

	return $self->{'meta'};
} # metadata

=head1 NAME

File::Dropbox - Convenient and fast Dropbox API abstraction

=head1 SYNOPSIS

    use File::Dropbox;
    use Fcntl;

    # Application credentials
    my %app = (
        oauth2        => 1,
        access_token  => $access_token,
    );

    my $dropbox = File::Dropbox->new(%app);

    # Open file for writing
    open $dropbox, '>', 'example' or die $!;

    while (<>) {
        # Upload data using 4MB chunks
        print $dropbox $_;
    }

    # Commit upload (optional, close will be called on reopen)
    close $dropbox or die $!;

    # Open for reading
    open $dropbox, '<', 'example' or die $!;

    # Download and print to STDOUT
    # Buffered, default buffer size is 4MB
    print while <$dropbox>;

    # Reset file position
    seek $dropbox, 0, Fcntl::SEEK_SET;

    # Get first character (unbuffered)
    say getc $dropbox;

    close $dropbox;

=head1 DESCRIPTION

C<File::Dropbox> provides high-level Dropbox API abstraction based on L<Tie::Handle>. Code required to get C<access_token> and
C<access_secret> for signed OAuth 1.0 requests or C<access_token> for OAuth 2.0 requests is not included in this module.
To get C<app_key> and C<app_secret> you need to register your application with Dropbox.

At this moment Dropbox API is not fully supported, C<File::Dropbox> covers file read/write and directory listing methods. If you need full
API support take look at L<WebService::Dropbox>. C<File::Dropbox> main purpose is not 100% API coverage,
but simple and high-performance file operations.

Due to API limitations and design you can not do read and write operations on one file at the same time. Therefore handle can be in read-only
or write-only state, depending on last call to L<open|perlfunc/open>. Supported functions for read-only state are: L<open|perlfunc/open>,
L<close|perlfunc/close>, L<seek|perlfunc/seek>, L<tell|perlfunc/tell>, L<readline|perlfunc/readline>, L<read|perlfunc/read>,
L<sysread|perlfunc/sysread>, L<getc|perlfunc/getc>, L<eof|perlfunc/eof>. For write-only state: L<open|perlfunc/open>, L<close|perlfunc/close>,
L<syswrite|perlfunc/syswrite>, L<print|perlfunc/print>, L<printf|perlfunc/printf>, L<say|perlfunc/say>.

All API requests are done using L<Furl> module. For more accurate timeouts L<Net::DNS::Lite> is used, as described in L<Furl::HTTP>. Furl settings
can be overridden using C<furlopts>.

=head1 METHODS

=head2 new

    my $dropbox = File::Dropbox->new(
        access_secret => $access_secret,
        access_token  => $access_token,
        app_secret    => $app_secret,
        app_key       => $app_key,
        chunk         => 8 * 1024 * 1024,
        root          => 'dropbox',
        furlopts      => {
            timeout => 20
        }
    );

    my $dropbox = File::Dropbox->new(
        access_token => $access_token,
        oauth2       => 1
    );

Constructor, takes key-value pairs list

=over

=item access_secret

OAuth 1.0 access secret

=item access_token

OAuth 1.0 access token or OAuth 2.0 access token

=item app_secret

OAuth 1.0 app secret

=item app_key

OAuth 1.0 app key

=item oauth2

OAuth 2.0 switch, defaults to false.

=item chunk

Upload chunk size in bytes. Also buffer size for C<readline>. Optional. Defaults to 4MB.

=item root

Access type, C<sandbox> for app-folder only access and C<dropbox> for full access.

=item furlopts

Parameter hash, passed to L<Furl> constructor directly. Default options

    timeout   => 10,
    inet_aton => \&Net::DNS::Lite::inet_aton,
    ssl_opts  => {
        SSL_verify_mode => SSL_VERIFY_PEER(),
    }

=back

=head1 FUNCTIONS

All functions are not exported by default but can be exported on demand.

    use File::Dropbox qw{ contents metadata putfile };

First argument for all functions should be GLOB reference, returned by L</new>.

=head2 contents

Arguments: $dropbox [, $path]

Function returns list of hashrefs representing directory content. Hash fields described in L<Dropbox API
docs|https://www.dropbox.com/developers/core/docs#metadata>. C<$path> defaults to C</>. If there is
unfinished chunked upload on handle, it will be committed.

    foreach my $file (contents($dropbox, '/data')) {
        next if $file->{'is_dir'};
        say $file->{'path'}, ' - ', $file->{'bytes'};
    }

=head2 metadata

Arguments: $dropbox

Function returns stored metadata for read-only handle, closed write handle or after
call to L</contents> or L</putfile>.

    open $dropbox, '<', '/data/2013.dat' or die $!;

    my $meta = metadata($dropbox);

    if ($meta->{'bytes'} > 1024) {
        # Do something
    }

=head2 putfile

Arguments: $dropbox, $path, $data

Function is useful for uploading small files (up to 150MB possible) in one request (at least
two API requests required for chunked upload, used in open-write-close sequence). If there is
unfinished chunked upload on handle, it will be committed.

    local $/;
    open my $data, '<', '2012.dat' or die $!;

    putfile($dropbox, '/data/2012.dat', <$data>) or die $!;

    say 'Uploaded ', metadata($dropbox)->{'bytes'}, ' bytes';

    close $data;

=head2 copyfile

Arguments: $dropbox, $source, $target

Function copies file or directory from one location to another. Metadata for copy
can be accessed using L</metadata> function.

    copyfile($dropbox, '/data/2012.dat', '/data/2012.dat.bak') or die $!;

    say 'Created backup with revision ', metadata($dropbox)->{'revision'};

=head2 movefile

Arguments: $dropbox, $source, $target

Function moves file or directory from one location to another. Metadata for moved file
can be accessed using L</metadata> function.

    movefile($dropbox, '/data/2012.dat', '/data/2012.dat.bak') or die $!;

    say 'Created backup with size ', metadata($dropbox)->{'size'};

=head2 deletefile

Arguments: $dropbox, $path

Function deletes file or folder at specified path. Metadata for deleted item
is accessible via L</metadata> function.

    deletefile($dropbox, '/data/2012.dat.bak') or die $!;

    say 'Deleted backup with last modification ', metadata($dropbox)->{'modification'};

=head2 createfolder

Arguments: $dropbox, $path

Function creates folder at specified path. Metadata for created folder
is accessible via L</metadata> function.

    createfolder($dropbox, '/data/backups') or die $!;

    say 'Created folder at path ', metadata($dropbox)->{'path'};

=head1 SEE ALSO

L<Furl>, L<Furl::HTTP>, L<WebService::Dropbox>, L<Dropbox API|https://www.dropbox.com/developers/core/docs>

=head1 AUTHOR

Alexander Nazarov <nfokz@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright 2013-2016 Alexander Nazarov

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

1;