1# ====================================================================
2#    Licensed to the Apache Software Foundation (ASF) under one
3#    or more contributor license agreements.  See the NOTICE file
4#    distributed with this work for additional information
5#    regarding copyright ownership.  The ASF licenses this file
6#    to you under the Apache License, Version 2.0 (the
7#    "License"); you may not use this file except in compliance
8#    with the License.  You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12#    Unless required by applicable law or agreed to in writing,
13#    software distributed under the License is distributed on an
14#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15#    KIND, either express or implied.  See the License for the
16#    specific language governing permissions and limitations
17#    under the License.
18# ====================================================================
19
20require "English"
21require "time"
22require "stringio"
23require "tempfile"
24require "svn/util"
25require "svn/error"
26require "svn/ext/core"
27
28class Time
29  MILLION = 1_000_000 #:nodoc:
30
31  class << self
32    def from_apr_time(apr_time)
33      return apr_time if apr_time.is_a?(Time)
34      sec, usec = apr_time.divmod(MILLION)
35      Time.at(sec, usec)
36    end
37
38    def from_svn_format(str)
39      return nil if str.nil?
40      return str if str.is_a?(Time)
41      from_apr_time(Svn::Core.time_from_cstring(str))
42    end
43
44    def parse_svn_format(str)
45      return str if str.is_a?(Time)
46      matched, result = Svn::Core.parse_date(str, Time.now.to_apr_time)
47      if matched
48        from_apr_time(result)
49      else
50        nil
51      end
52    end
53  end
54
55  def to_apr_time
56    to_i * MILLION + usec
57  end
58
59  def to_svn_format
60    Svn::Core.time_to_cstring(self.to_apr_time)
61  end
62
63  def to_svn_human_format
64    Svn::Core.time_to_human_cstring(self.to_apr_time)
65  end
66end
67
68module Svn
69  module Core
70    Util.set_constants(Ext::Core, self)
71    Util.set_methods(Ext::Core, self)
72
73    nls_init
74    Util.reset_message_directory
75
76    # for backward compatibility
77    SWIG_INVALID_REVNUM = INVALID_REVNUM
78    SWIG_IGNORED_REVNUM = IGNORED_REVNUM
79
80    class << self
81      alias binary_mime_type? mime_type_is_binary
82      alias prop_diffs2 prop_diffs
83
84      def prop_diffs(target_props, source_props)
85        Property.prop_diffs(target_props, source_props)
86      end
87    end
88
89
90    DEFAULT_CHARSET = default_charset
91    LOCALE_CHARSET = locale_charset
92
93    AuthCredSSLClientCert = AuthCredSslClientCert
94    AuthCredSSLClientCertPw = AuthCredSslClientCertPw
95    AuthCredSSLServerTrust = AuthCredSslServerTrust
96
97    dirent_all = 0
98    constants.each do |name|
99      dirent_all |= const_get(name) if /^DIRENT_/ =~ name
100    end
101    DIRENT_ALL = dirent_all
102
103    Pool = Svn::Ext::Core::Apr_pool_wrapper_t
104
105    class Pool
106      RECOMMENDED_MAX_FREE_SIZE = ALLOCATOR_RECOMMENDED_MAX_FREE
107      MAX_FREE_UNLIMITED = ALLOCATOR_MAX_FREE_UNLIMITED
108
109      class << self
110        def number_of_pools
111          ObjectSpace.each_object(Pool) {}
112        end
113      end
114
115      alias _initialize initialize
116      private :_initialize
117      def initialize(parent=nil)
118        _initialize(parent)
119        @parent = parent
120      end
121
122      def destroy
123        @parent = nil
124        _destroy
125      end
126      private :_destroy
127    end
128
129    class Stream
130      if Core.const_defined?(:STREAM_CHUNK_SIZE)
131        CHUNK_SIZE = Core::STREAM_CHUNK_SIZE
132      else
133        CHUNK_SIZE = 8192
134      end
135
136      def write(data)
137        Core.stream_write(self, data)
138      end
139
140      def read(len=nil)
141        if len.nil?
142          read_all
143        else
144          buf = ""
145          while len > CHUNK_SIZE
146            buf << _read(CHUNK_SIZE)
147            len -= CHUNK_SIZE
148          end
149          buf << _read(len)
150          buf
151        end
152      end
153
154      def close
155        Core.stream_close(self)
156      end
157
158      def copy(other, &cancel_proc)
159        Core.stream_copy2(self, other, cancel_proc)
160      end
161
162      private
163      def _read(size)
164        Core.stream_read(self, size)
165      end
166
167      def read_all
168        buf = ""
169        while chunk = _read(CHUNK_SIZE)
170          buf << chunk
171        end
172        buf
173      end
174    end
175
176
177    class AuthBaton
178      attr_reader :providers, :parameters
179
180      alias _initialize initialize
181      private :_initialize
182      def initialize(providers=[], parameters={})
183        _initialize(providers)
184        @providers = providers
185        self.parameters = parameters
186      end
187
188      def [](name)
189        Core.auth_get_parameter(self, name)
190      end
191
192      def []=(name, value)
193        Core.auth_set_parameter(self, name, value)
194        @parameters[name] = value
195      end
196
197      def parameters=(params)
198        @parameters = {}
199        params.each do |key, value|
200          self[key] = value
201        end
202      end
203    end
204
205    module Authenticatable
206      attr_accessor :auth_baton
207
208      def add_simple_provider
209        add_provider(Core.auth_get_simple_provider)
210      end
211
212      if Util.windows?
213        if Core.respond_to?(:auth_get_windows_simple_provider)
214          def add_windows_simple_provider
215            add_provider(Core.auth_get_windows_simple_provider)
216          end
217        elsif Core.respond_to?(:auth_get_platform_specific_provider)
218          def add_windows_simple_provider
219            add_provider(Core.auth_get_platform_specific_provider("windows","simple"))
220          end
221        end
222      end
223
224      if Core.respond_to?(:auth_get_keychain_simple_provider)
225        def add_keychain_simple_provider
226          add_provider(Core.auth_get_keychain_simple_provider)
227        end
228      end
229
230      def add_username_provider
231        add_provider(Core.auth_get_username_provider)
232      end
233
234      def add_ssl_client_cert_file_provider
235        add_provider(Core.auth_get_ssl_client_cert_file_provider)
236      end
237
238      def add_ssl_client_cert_pw_file_provider
239        add_provider(Core.auth_get_ssl_client_cert_pw_file_provider)
240      end
241
242      def add_ssl_server_trust_file_provider
243        add_provider(Core.auth_get_ssl_server_trust_file_provider)
244      end
245
246      if Core.respond_to?(:auth_get_windows_ssl_server_trust_provider)
247        def add_windows_ssl_server_trust_provider
248          add_provider(Core.auth_get_windows_ssl_server_trust_provider)
249        end
250      end
251
252      def add_simple_prompt_provider(retry_limit, &prompt)
253        args = [retry_limit]
254        klass = AuthCredSimple
255        add_prompt_provider("simple", args, prompt, klass)
256      end
257
258      def add_username_prompt_provider(retry_limit, &prompt)
259        args = [retry_limit]
260        klass = AuthCredUsername
261        add_prompt_provider("username", args, prompt, klass)
262      end
263
264      def add_ssl_server_trust_prompt_provider(&prompt)
265        args = []
266        klass = AuthCredSSLServerTrust
267        add_prompt_provider("ssl_server_trust", args, prompt, klass)
268      end
269
270      def add_ssl_client_cert_prompt_provider(retry_limit, &prompt)
271        args = [retry_limit]
272        klass = AuthCredSSLClientCert
273        add_prompt_provider("ssl_client_cert", args, prompt, klass)
274      end
275
276      def add_ssl_client_cert_pw_prompt_provider(retry_limit, &prompt)
277        args = [retry_limit]
278        klass = AuthCredSSLClientCertPw
279        add_prompt_provider("ssl_client_cert_pw", args, prompt, klass)
280      end
281
282      def add_platform_specific_client_providers(config=nil)
283        add_providers(Core.auth_get_platform_specific_client_providers(config))
284      end
285
286      private
287      def add_prompt_provider(name, args, prompt, credential_class)
288        real_prompt = Proc.new do |*prompt_args|
289          credential = credential_class.new
290          prompt.call(credential, *prompt_args)
291          credential
292        end
293        method_name = "swig_rb_auth_get_#{name}_prompt_provider"
294        baton, provider = Core.send(method_name, real_prompt, *args)
295        provider.instance_variable_set("@baton", baton)
296        provider.instance_variable_set("@prompt", real_prompt)
297        add_provider(provider)
298      end
299
300      def add_provider(provider)
301        add_providers([provider])
302      end
303
304      def add_providers(new_providers)
305        if auth_baton
306          providers = auth_baton.providers
307          parameters = auth_baton.parameters
308        else
309          providers = []
310          parameters = {}
311        end
312        self.auth_baton = AuthBaton.new(providers + new_providers, parameters)
313      end
314    end
315
316    class AuthProviderObject
317      class << self
318        undef new
319      end
320    end
321
322
323    Diff = SWIG::TYPE_p_svn_diff_t
324    class Diff
325      attr_accessor :original, :modified, :latest, :ancestor
326
327      class << self
328        def version
329          Core.diff_version
330        end
331
332        def file_diff(original, modified, options=nil)
333          options ||= Core::DiffFileOptions.new
334          diff = Core.diff_file_diff_2(original, modified, options)
335          if diff
336            diff.original = original
337            diff.modified = modified
338          end
339          diff
340        end
341
342        def file_diff3(original, modified, latest, options=nil)
343          options ||= Core::DiffFileOptions.new
344          diff = Core.diff_file_diff3_2(original, modified, latest, options)
345          if diff
346            diff.original = original
347            diff.modified = modified
348            diff.latest = latest
349          end
350          diff
351        end
352
353        def file_diff4(original, modified, latest, ancestor, options=nil)
354          options ||= Core::DiffFileOptions.new
355          args = [original, modified, latest, ancestor, options]
356          diff = Core.diff_file_diff4_2(*args)
357          if diff
358            diff.original = original
359            diff.modified = modified
360            diff.latest = latest
361            diff.ancestor = ancestor
362          end
363          diff
364        end
365      end
366
367      def unified(orig_label, mod_label, header_encoding=nil)
368        header_encoding ||= Svn::Core.locale_charset
369        output = StringIO.new
370        args = [
371          output, self, @original, @modified,
372          orig_label, mod_label, header_encoding
373        ]
374        Core.diff_file_output_unified2(*args)
375        output.rewind
376        output.read
377      end
378
379      def merge(conflict_original=nil, conflict_modified=nil,
380                conflict_latest=nil, conflict_separator=nil,
381                display_original_in_conflict=true,
382                display_resolved_conflicts=true)
383        header_encoding ||= Svn::Core.locale_charset
384        output = StringIO.new
385        args = [
386          output, self, @original, @modified, @latest,
387          conflict_original, conflict_modified,
388          conflict_latest, conflict_separator,
389          display_original_in_conflict,
390          display_resolved_conflicts,
391        ]
392        Core.diff_file_output_merge(*args)
393        output.rewind
394        output.read
395      end
396
397      def conflict?
398        Core.diff_contains_conflicts(self)
399      end
400
401      def diff?
402        Core.diff_contains_diffs(self)
403      end
404    end
405
406    class DiffFileOptions
407      class << self
408        def parse(*args)
409          options = new
410          options.parse(*args)
411          options
412        end
413      end
414
415      def parse(*args)
416        args = args.first if args.size == 1 and args.first.is_a?(Array)
417        Svn::Core.diff_file_options_parse(self, args)
418      end
419    end
420
421    class Version
422
423      alias _initialize initialize
424      def initialize(major=nil, minor=nil, patch=nil, tag=nil)
425        _initialize
426        self.major = major if major
427        self.minor = minor if minor
428        self.patch = patch if patch
429        self.tag = tag || ""
430      end
431
432      def ==(other)
433        valid? and other.valid? and Core.ver_equal(self, other)
434      end
435
436      def compatible?(other)
437        valid? and other.valid? and Core.ver_compatible(self, other)
438      end
439
440      def valid?
441        (major and minor and patch and tag) ? true : false
442      end
443
444      alias _tag= tag=
445      def tag=(value)
446        @tag = value
447        self._tag = value
448      end
449
450      def to_a
451        [major, minor, patch, tag]
452      end
453
454      def to_s
455        "#{major}.#{minor}.#{patch}#{tag}"
456      end
457    end
458
459    # Following methods are also available:
460    #
461    # [created_rev]
462    #   Returns a revision at which the instance was last modified.
463    # [have_props?]
464    #   Returns +true+ if the instance has properties.
465    # [last_author]
466    #   Returns an author who last modified the instance.
467    # [size]
468    #   Returns a size of the instance.
469    class Dirent
470      alias have_props? has_props
471
472      # Returns +true+ when the instance is none.
473      def none?
474        kind == NODE_NONE
475      end
476
477      # Returns +true+ when the instance is a directory.
478      def directory?
479        kind == NODE_DIR
480      end
481
482      # Returns +true+ when the instance is a file.
483      def file?
484        kind == NODE_FILE
485      end
486
487      # Returns +true+ when the instance is an unknown node.
488      def unknown?
489        kind == NODE_UNKNOWN
490      end
491
492      # Returns a Time when the instance was last changed.
493      #
494      # Svn::Core::Dirent#time is replaced by this method, _deprecated_,
495      # and provided for backward compatibility with the 1.3 API.
496      def time2
497        __time = time
498        __time && Time.from_apr_time(__time)
499      end
500    end
501
502    Config = SWIG::TYPE_p_svn_config_t
503
504    class Config
505      include Enumerable
506
507      class << self
508        def get(path=nil)
509          Core.config_get_config(path)
510        end
511        alias config get
512
513        def read(file, must_exist=true)
514          Core.config_read(file, must_exist)
515        end
516
517        def ensure(dir)
518          Core.config_ensure(dir)
519        end
520
521        def read_auth_data(cred_kind, realm_string, config_dir=nil)
522          Core.config_read_auth_data(cred_kind, realm_string, config_dir)
523        end
524
525        def write_auth_data(hash, cred_kind, realm_string, config_dir=nil)
526          Core.config_write_auth_data(hash, cred_kind,
527                                      realm_string, config_dir)
528        end
529      end
530
531      def merge(file, must_exist=true)
532        Core.config_merge(self, file, must_exist)
533      end
534
535      def get(section, option, default=nil)
536        Core.config_get(self, section, option, default)
537      end
538
539      def get_bool(section, option, default)
540        Core.config_get_bool(self, section, option, default)
541      end
542
543      def set(section, option, value)
544        Core.config_set(self, section, option, value)
545      end
546      alias_method :[]=, :set
547
548      def set_bool(section, option, value)
549        Core.config_set_bool(self, section, option, value)
550      end
551
552      def each
553        each_section do |section|
554          each_option(section) do |name, value|
555            yield(section, name, value)
556            true
557          end
558          true
559        end
560      end
561
562      def each_option(section)
563        receiver = Proc.new do |name, value|
564          yield(name, value)
565        end
566        Core.config_enumerate2(self, section, receiver)
567      end
568
569      def each_section
570        receiver = Proc.new do |name|
571          yield(name)
572        end
573        Core.config_enumerate_sections2(self, receiver)
574      end
575
576      def find_group(key, section)
577        Core.config_find_group(self, key, section)
578      end
579
580      def get_server_setting(group, name, default=nil)
581        Core.config_get_server_setting(self, group, name, default)
582      end
583
584      def get_server_setting_int(group, name, default)
585        Core.config_get_server_setting_int(self, group, name, default)
586      end
587
588      alias_method :_to_s, :to_s
589      def to_s
590        result = ""
591        each_section do |section|
592          result << "[#{section}]\n"
593          each_option(section) do |name, value|
594            result << "#{name} = #{value}\n"
595          end
596          result << "\n"
597        end
598        result
599      end
600
601      def inspect
602        "#{_to_s}#{to_hash.inspect}"
603      end
604
605      def to_hash
606        sections = {}
607        each do |section, name, value|
608          sections[section] ||= {}
609          sections[section][name] = value
610        end
611        sections
612      end
613
614      def ==(other)
615        other.is_a?(self.class) and to_hash == other.to_hash
616      end
617    end
618
619    module Property
620      module_function
621      def kind(name)
622        kind, len = Core.property_kind(name)
623        [kind, name[0...len]]
624      end
625
626      def svn_prop?(name)
627        Core.prop_is_svn_prop(name)
628      end
629
630      def needs_translation?(name)
631        Core.prop_needs_translation(name)
632      end
633
634      def categorize(props)
635        categorize2(props).collect do |categorized_props|
636          Util.hash_to_prop_array(categorized_props)
637        end
638      end
639      alias_method :categorize_props, :categorize
640      module_function :categorize_props
641
642      def categorize2(props)
643        Core.categorize_props(props)
644      end
645
646      def diffs(target_props, source_props)
647        Util.hash_to_prop_array(diffs2(target_props, source_props))
648      end
649      alias_method :prop_diffs, :diffs
650      module_function :prop_diffs
651
652      def diffs2(target_props, source_props)
653        Core.prop_diffs2(target_props, source_props)
654      end
655
656      def has_svn_prop?(props)
657        Core.prop_has_svn_prop(props)
658      end
659      alias_method :have_svn_prop?, :has_svn_prop?
660      module_function :have_svn_prop?
661
662      def valid_name?(name)
663        Core.prop_name_is_valid(name)
664      end
665    end
666
667    module Depth
668      module_function
669      def from_string(str)
670        return nil if str.nil?
671        Core.depth_from_word(str)
672      end
673
674      def to_string(depth)
675        Core.depth_to_word(depth)
676      end
677
678      def infinity_or_empty_from_recurse(depth_or_recurse)
679        case depth_or_recurse
680          when true  then DEPTH_INFINITY
681          when false then DEPTH_EMPTY
682          else depth_or_recurse
683        end
684      end
685
686      def infinity_or_immediates_from_recurse(depth_or_recurse)
687        case depth_or_recurse
688          when true  then DEPTH_INFINITY
689          when false then DEPTH_IMMEDIATES
690          else depth_or_recurse
691        end
692      end
693    end
694
695    module MimeType
696      module_function
697      def parse(source)
698        file = Tempfile.new("svn-ruby-mime-type")
699        file.print(source)
700        file.close
701        Core.io_parse_mimetypes_file(file.path)
702      end
703
704      def parse_file(path)
705        Core.io_parse_mimetypes_file(path)
706      end
707
708      def detect(path, type_map={})
709        Core.io_detect_mimetype2(path, type_map)
710      end
711    end
712
713    class CommitInfo
714      alias _date date
715      def date
716        Time.from_svn_format(_date)
717      end
718    end
719
720    # Following methods are also available:
721    #
722    # [action]
723    #   Returns an action taken to the path at the revision.
724    # [copyfrom_path]
725    #   If the path was added at the revision by the copy action from
726    #   another path at another revision, returns an original path.
727    #   Otherwise, returns +nil+.
728    # [copyfrom_rev]
729    #   If the path was added at the revision by the copy action from
730    #   another path at another revision, returns an original revision.
731    #   Otherwise, returns <tt>-1</tt>.
732    class LogChangedPath
733      # Returns +true+ when the path is added by the copy action.
734      def copied?
735        Util.copy?(copyfrom_path, copyfrom_rev)
736      end
737    end
738
739    # For backward compatibility
740    class Prop
741      attr_accessor :name, :value
742      def initialize(name, value)
743        @name = name
744        @value = value
745      end
746
747      def ==(other)
748        other.is_a?(self.class) and
749          [@name, @value] == [other.name, other.value]
750      end
751    end
752
753    class MergeRange
754      def to_a
755        [self.start, self.end, self.inheritable]
756      end
757
758      def inspect
759        super.gsub(/>$/, ":#{to_a.inspect}>")
760      end
761
762      def ==(other)
763        to_a == other.to_a
764      end
765    end
766
767    class MergeInfo < Hash
768      class << self
769        def parse(input)
770          new(Core.mergeinfo_parse(input))
771        end
772      end
773
774      def initialize(info)
775        super()
776        info.each do |path, ranges|
777          self[path] = RangeList.new(*ranges)
778        end
779      end
780
781      def diff(to, consider_inheritance=false)
782        Core.mergeinfo_diff(self, to, consider_inheritance).collect do |result|
783          self.class.new(result)
784        end
785      end
786
787      def merge(changes)
788        self.class.new(Core.swig_mergeinfo_merge(self, changes))
789      end
790
791      def remove(eraser)
792        self.class.new(Core.mergeinfo_remove(eraser, self))
793      end
794
795      def sort
796        self.class.new(Core.swig_mergeinfo_sort(self))
797      end
798
799      def to_s
800        Core.mergeinfo_to_string(self)
801      end
802    end
803
804    class RangeList < Array
805      def initialize(*ranges)
806        super()
807        ranges.each do |range|
808          self << Svn::Core::MergeRange.new(*range.to_a)
809        end
810      end
811
812      def diff(to, consider_inheritance=false)
813        result = Core.rangelist_diff(self, to, consider_inheritance)
814        deleted = result.pop
815        added = result
816        [added, deleted].collect do |result|
817          self.class.new(*result)
818        end
819      end
820
821      def merge(changes)
822        self.class.new(*Core.swig_rangelist_merge(self, changes))
823      end
824
825      def remove(eraser, consider_inheritance=false)
826        self.class.new(*Core.rangelist_remove(eraser, self,
827                                              consider_inheritance))
828      end
829
830      def intersect(other, consider_inheritance=false)
831        self.class.new(*Core.rangelist_intersect(self, other,
832                                                 consider_inheritance))
833      end
834
835      def reverse
836        self.class.new(*Core.swig_rangelist_reverse(self))
837      end
838
839      def to_s
840        Core.rangelist_to_string(self)
841      end
842    end
843
844    class LogEntry
845      alias_method(:revision_properties, :revprops)
846      alias_method(:has_children?, :has_children)
847      undef_method(:has_children)
848    end
849  end
850end
851