[api] fix branching of packages via project links
[opensuse:build-service.git] / src / api / app / controllers / source_controller.rb
1 require "rexml/document"
2
3 class SourceController < ApplicationController
4   validate_action :index => :directory, :packagelist => :directory, :filelist => :directory
5   validate_action :project_meta => :project, :package_meta => :package, :pattern_meta => :pattern
6  
7   skip_before_filter :extract_user, :only => [:file, :project_meta, :project_config] 
8
9   def index
10     projectlist
11   end
12
13   def projectlist
14     if request.post?
15       # a bit danguerous, never implment a command without proper permission check
16       dispatch_command
17     elsif request.get?
18       if params[:deleted]
19         pass_to_backend
20       else
21         @dir = Project.find :all
22         render :text => @dir.dump_xml, :content_type => "text/xml"
23       end
24     end
25   end
26
27   def index_project
28     project_name = params[:project]
29     pro = DbProject.find_by_name project_name
30     if pro.nil?
31       unless params[:cmd] == "undelete"
32         render_error :status => 404, :errorcode => 'unknown_project',
33           :message => "Unknown project #{project_name}"
34         return
35       end
36     elsif params[:cmd] == "undelete"
37       render_error :status => 403, :errorcode => 'create_project_no_permission',
38           :message => "Can not undelete, project exists already '#{project_name}'"
39       return
40     end
41     
42     if request.get?
43       if params[:deleted]
44         pass_to_backend
45       else
46         @dir = Package.find :all, :project => project_name
47         render :text => @dir.dump_xml, :content_type => "text/xml"
48       end
49       return
50     elsif request.delete?
51       unless @http_user.can_modify_project?(pro)
52         logger.debug "No permission to delete project #{project_name}"
53         render_error :status => 403, :errorcode => 'delete_project_no_permission',
54           :message => "Permission denied (delete project #{project_name})"
55         return
56       end
57
58       #deny deleting if other packages use this as develproject
59       unless pro.develpackages.empty?
60         msg = "Unable to delete project #{pro.name}; following packages use this project as develproject: "
61         msg += pro.develpackages.map {|pkg| pkg.db_project.name+"/"+pkg.name}.join(", ")
62         render_error :status => 400, :errorcode => 'develproject_dependency',
63           :message => msg
64         return
65       end
66       #check all packages, if any get refered as develpackage
67       pro.db_packages.each do |pkg|
68         unless pkg.develpackages.empty?
69           msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
70           msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
71           render_error :status => 400, :errorcode => 'develpackage_dependency',
72             :message => msg
73           return
74         end
75       end
76
77       #find linking repos
78       lreps = Array.new
79       pro.repositories.each do |repo|
80         repo.linking_repositories.each do |lrep|
81           lreps << lrep
82         end
83       end
84
85       if lreps.length > 0
86         if params[:force] and not params[:force].empty?
87           #replace links to this projects with links to the "deleted" project
88           del_repo = DbProject.find_by_name("deleted").repositories[0]
89           lreps.each do |link_rep|
90             link_rep.path_elements.find(:all).each { |pe| pe.destroy }
91             link_rep.path_elements.create(:link => del_repo, :position => 1)
92             link_rep.save
93             #update backend
94             link_rep.db_project.store
95           end
96         else
97           lrepstr = lreps.map{|l| l.db_project.name+'/'+l.name}.join "\n"
98           render_error :status => 403, :errorcode => "repo_dependency",
99             :message => "Unable to delete project #{project_name}; following repositories depend on this project:\n#{lrepstr}\n"
100           return
101         end
102       end
103
104       #destroy all packages
105       pro.db_packages.each do |pack|
106         DbPackage.transaction do
107           logger.info "destroying package #{pack.name}"
108           pack.destroy
109         end
110       end
111
112       DbProject.transaction do
113         logger.info "destroying project #{pro.name}"
114         pro.destroy
115         logger.debug "delete request to backend: /source/#{pro.name}"
116         Suse::Backend.delete "/source/#{pro.name}"
117       end
118
119       render_ok
120       return
121     elsif request.post?
122       cmd = params[:cmd]
123
124       if ['undelete'].include?(cmd) 
125         unless @http_user.can_create_project?(project_name)
126           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
127             :message => "no permission to execute command '#{cmd}'"
128           return
129         end
130         dispatch_command
131
132         # read meta data from backend to restore database object
133         path = request.path + "/_meta"
134         Project.new(backend_get(path)).save
135
136         # restore all package meta data objects in DB
137         backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}'"
138         backend_pkgs.each_package do |package|
139           path = request.path + "/" + package.name + "/_meta"
140           Package.new(backend_get(path), :project => params[:project]).save
141         end
142         return
143       end
144
145       if @http_user.can_modify_project?(pro)
146         dispatch_command
147       else
148         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
149           :message => "no permission to execute command '#{cmd}'"
150         return
151       end
152     else
153       render_error :status => 400, :errorcode => "illegal_request",
154         :message => "illegal POST request to #{request.request_uri}"
155     end
156   end
157
158   def index_package
159     valid_http_methods :get, :delete, :post
160     project_name = params[:project]
161     package_name = params[:package]
162     cmd = params[:cmd]
163     deleted = params[:deleted]
164
165     prj = DbProject.find_by_name(project_name)
166     unless prj
167       if request.get?
168         # Check if this is a package on a remote OBS instance
169         answer = Suse::Backend.get(request.path)
170         if answer
171           pass_to_backend
172           return
173         end
174       end
175       render_error :status => 404, :errorcode => "unknown_project",
176         :message => "unknown project '#{project_name}'"
177       return
178     end
179     # look also via linked projects, package source may come from another project
180     begin
181       if request.get?
182         # include project links on get
183         pkg = prj.find_package(package_name)
184       else
185         # look only in local packages for operations on them
186         pkg = prj.db_packages.find_by_name(package_name)
187       end
188     rescue DbProject::CycleError => e
189       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
190       return
191     end
192     unless deleted.blank? and not request.delete?
193       unless package_name == "_project" or pkg or DbProject.find_remote_project(project_name)
194         render_error :status => 404, :errorcode => "unknown_package",
195           :message => "unknown package '#{package_name}' in project '#{project_name}'"
196         return
197       end
198     end
199
200     if request.get?
201       pass_to_backend
202       return
203     elsif request.delete?
204       if package_name == "_project"
205         render_error :status => 403, :errorcode => "delete_package_no_permission",
206           :message => "_project package can not be deleted."
207         return
208       end
209
210       if not @http_user.can_modify_package?(pkg)
211         render_error :status => 403, :errorcode => "delete_package_no_permission",
212           :message => "no permission to delete package #{package_name}"
213         return
214       end
215       
216       #deny deleting if other packages use this as develpackage
217       # Shall we offer a --force option here as well ?
218       # Shall we ask the other package owner accepting to be a devel package ?
219       unless pkg.develpackages.empty?
220         msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
221         msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
222         render_error :status => 400, :errorcode => 'develpackage_dependency',
223           :message => msg
224         return
225       end
226
227       DbPackage.transaction do
228         pkg.destroy
229         Suse::Backend.delete "/source/#{project_name}/#{package_name}"
230         if package_name == "_product"
231           update_product_autopackages
232         end
233       end
234       render_ok
235     elsif request.post?
236       if ['undelete'].include?(cmd) 
237         unless @http_user.can_modify_project?(prj)
238           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
239             :message => "no permission to execute command '#{cmd}'"
240           return
241         end
242         dispatch_command
243
244         # read meta data from backend to restore database object
245         path = request.path + "/_meta"
246         Package.new(backend_get(path), :project => params[:project]).save
247         return
248       end
249
250       if ['branch'].include?(cmd) 
251         dispatch_command
252         return
253       elsif pkg.nil?
254         unless @http_user.can_modify_project?(prj)
255           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
256             :message => "no permission to execute command '#{cmd}' for not existing package"
257           return
258         end
259       elsif not ['diff'].include?(cmd) and not @http_user.can_modify_package?(pkg)
260         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
261           :message => "no permission to execute command '#{cmd}'"
262         return
263       end
264       
265       dispatch_command
266     end
267   end
268
269   # updates packages automatically generated in the backend after submitting a product file
270   def update_product_autopackages
271     backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}' and starts-with(@name,'_product:')"
272     b_pkg_index = backend_pkgs.each_package.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
273     frontend_pkgs = DbProject.find_by_name(params[:project]).db_packages.find(:all, :conditions => "name LIKE '_product:%'")
274     f_pkg_index = frontend_pkgs.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
275
276     all_pkgs = [b_pkg_index.keys, f_pkg_index.keys].flatten.uniq
277
278     wt_state = ActiveXML::Config.global_write_through
279     begin
280       ActiveXML::Config.global_write_through = false
281       all_pkgs.each do |pkg|
282         if b_pkg_index.has_key?(pkg) and not f_pkg_index.has_key?(pkg)
283           # new autopackage, import in database
284           Package.new(b_pkg_index[pkg].dump_xml, :project => params[:project]).save
285         elsif f_pkg_index.has_key?(pkg) and not b_pkg_index.has_key?(pkg)
286           # autopackage was removed, remove from database
287           f_pkg_index[pkg].destroy
288         end
289       end
290     ensure
291       ActiveXML::Config.global_write_through = wt_state
292     end
293   end
294
295   # /source/:project/_attribute/:attribute
296   # /source/:project/:package/:binary/_attribute/:attribute
297   def attribute_meta
298     valid_http_methods :get, :post, :delete
299     params[:user] = @http_user.login if @http_user
300
301     binary=nil
302     binary=params[:binary] if params[:binary]
303
304     if params[:package]
305       @attribute_container = DbPackage.find_by_project_and_name(params[:project], params[:package])
306       unless @attribute_container
307         render_error :message => "Unknown package '#{params[:project]}/#{params[:package]}'",
308           :status => 404, :errorcode => "unknown_package"
309         return
310       end
311     else
312       @attribute_container = DbProject.find_by_name(params[:project])
313       unless @attribute_container
314         render_error :message => "Unknown project '#{params[:project]}'",
315           :status => 404, :errorcode => "unknown_project"
316         return
317       end
318     end
319
320     if request.get?
321       params[:binary]=binary if binary
322       render :text => @attribute_container.render_attribute_axml(params), :content_type => 'text/xml'
323       return
324     end
325
326     if request.post?
327       begin
328         req = BsRequest.new(request.body.read)
329         req.data # trigger XML parsing
330       rescue ActiveXML::ParseError => e
331         render_error :message => "Invalid XML",
332           :status => 400, :errorcode => "invalid_xml"
333         return
334       end
335     end
336
337     # permission checking
338     if params[:attribute]
339       aname = params[:attribute]
340       name_parts = aname.split(/:/)
341       if name_parts.length != 2
342         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
343       end
344       if a=@attribute_container.find_attribute(name_parts[0],name_parts[1],binary)
345         unless @http_user.can_modify_attribute? a
346           render_error :status => 403, :errorcode => "change_attribute_no_permission", 
347             :message => "user #{user.login} has no permission to modify attribute"
348           return
349         end
350       else
351         unless request.post?
352           render_error :status => 403, :errorcode => "not_existing_attribute", 
353             :message => "Attempt to modify not existing attribute"
354           return
355         end
356         unless @http_user.can_create_attribute_in? @attribute_container, :namespace => name_parts[0], :name => name_parts[1]
357           render_error :status => 403, :errorcode => "change_attribute_no_permission", 
358             :message => "user #{user.login} has no permission to change attribute"
359           return
360         end
361       end
362     else
363       if request.post?
364         req.each_attribute do |attr|
365           begin
366             can_create = @http_user.can_create_attribute_in? @attribute_container, :namespace => attr.namespace, :name => attr.name
367           rescue ActiveRecord::RecordNotFound => e
368             render_error :status => 404, :errorcode => "not_found",
369               :message => e.message
370             return
371           rescue ArgumentError => e
372             render_error :status => 400, :errorcode => "change_attribute_attribute_error",
373               :message => e.message
374             return
375           end
376           unless can_create
377             render_error :status => 403, :errorcode => "change_attribute_no_permission", 
378               :message => "user #{user.login} has no permission to change attribute"
379             return
380           end
381         end
382       else
383         render_error :status => 403, :errorcode => "internal_error", 
384           :message => "INTERNAL ERROR: unhandled request"
385         return
386       end
387     end
388
389     # execute action
390     if request.post?
391       req.each_attribute do |attr|
392         begin
393           @attribute_container.store_attribute_axml(attr, binary)
394         rescue DbProject::SaveError => e
395           render_error :status => 403, :errorcode => "save_error", :message => e.message
396           return
397         rescue DbPackage::SaveError => e
398           render_error :status => 403, :errorcode => "save_error", :message => e.message
399           return
400         end
401       end
402       @attribute_container.store
403       render_ok
404     elsif request.delete?
405       @attribute_container.find_attribute(name_parts[0], name_parts[1],binary).destroy
406       @attribute_container.store
407       render_ok
408     else
409       render_error :message => "INTERNAL ERROR: Unhandled operation",
410         :status => 404, :errorcode => "unknown_operation"
411     end
412   end
413
414   # /source/:project/_pattern/:pattern
415   def pattern_meta
416     valid_http_methods :get, :put, :delete
417
418     params[:user] = @http_user.login if @http_user
419     
420     @project = DbProject.find_by_name params[:project]
421     unless @project
422       render_error :message => "Unknown project '#{params[:project]}'",
423         :status => 404, :errorcode => "unknown_project"
424       return
425     end
426
427     if request.get?
428       pass_to_backend
429     else
430       # PUT and DELETE
431       permerrormsg = nil
432       if request.put?
433         permerrormsg = "no permission to store pattern"
434       elsif request.delete?
435         permerrormsg = "no permission to delete pattern"
436       end
437
438       unless @http_user.can_modify_project? @project
439         logger.debug "user #{user.login} has no permission to modify project #{@project}"
440         render_error :status => 403, :errorcode => "change_project_no_permission", 
441           :message => permerrormsg
442         return
443       end
444       
445       path = request.path + build_query_from_hash(params, [:rev, :user, :comment])
446       pass_to_backend path
447     end
448   end
449
450   # GET /source/:project/_pattern
451   def index_pattern
452     valid_http_methods :get
453
454     unless DbProject.find_by_name(params[:project])
455       render_error :message => "Unknown project '#{params[:project]}'",
456         :status => 404, :errorcode => "unknown_project"
457       return
458     end
459     
460     pass_to_backend
461   end
462
463   def project_meta
464     valid_http_methods :get, :put
465
466     project_name = params[:project]
467     if project_name.nil?
468       render_error :status => 400, :errorcode => 'missing_parameter',
469         :message => "parameter 'project' is missing"
470       return
471     end
472
473     if request.get?
474       @project = DbProject.find_by_name( project_name )
475
476       if @project
477         render :text => @project.to_axml(params[:view]), :content_type => 'text/xml'
478       elsif DbProject.find_remote_project(project_name)
479         # project from remote buildservice, get metadata from backend
480         pass_to_backend
481       else
482         render_error :message => "Unknown project '#{project_name}'",
483           :status => 404, :errorcode => "unknown_project"
484       end
485       return
486     end
487
488     return unless extract_user
489
490     #assemble path for backend
491     params[:user] = @http_user.login
492     path = request.path
493     path += build_query_from_hash(params, [:user, :comment, :rev])
494
495     if request.put?
496       unless valid_project_name? project_name
497         render_error :status => 400, :errorcode => "invalid_project_name",
498           :message => "invalid project name '#{project_name}'"
499         return
500       end
501
502       # Need permission
503       logger.debug "Checking permission for the put"
504       allowed = false
505       request_data = request.raw_post
506
507       @project = DbProject.find_by_name( project_name )
508       if @project
509         #project exists, change it
510         unless @http_user.can_modify_project? @project
511           logger.debug "user #{user.login} has no permission to modify project #{@project}"
512           render_error :status => 403, :errorcode => "change_project_no_permission", 
513             :message => "no permission to change project"
514           return
515         end
516       else
517         #project is new
518         unless @http_user.can_create_project? project_name
519           logger.debug "Not allowed to create new project"
520           render_error :status => 403, :errorcode => 'create_project_no_permission',
521             :message => "not allowed to create new project '#{project_name}'"
522           return
523         end
524       end
525
526       p = Project.new(request_data, :name => project_name)
527
528       if p.name != project_name
529         render_error :status => 400, :errorcode => 'project_name_mismatch',
530           :message => "project name in xml data does not match resource path component"
531         return
532       end
533
534       if (p.has_element? :remoteurl or p.has_element? :remoteproject) and not @http_user.is_admin?
535         render_error :status => 403, :errorcode => "change_project_no_permission",
536           :message => "admin rights are required to change remoteurl or remoteproject"
537         return
538       end
539
540       p.add_person(:userid => @http_user.login) unless @project
541       p.save
542
543       render_ok
544     else
545       render_error :status => 400, :errorcode => 'illegal_request',
546         :message => "Illegal request: POST #{request.path}"
547     end
548   end
549
550   def project_config
551     valid_http_methods :get, :put
552
553     #check if project exists
554     unless (@project = DbProject.find_by_name(params[:project]))
555       render_error :status => 404, :errorcode => 'project_not_found',
556         :message => "Unknown project #{params[:project]}"
557       return
558     end
559
560     if request.get?
561       path = request.path
562       path += build_query_from_hash(params, [:rev])
563       pass_to_backend path
564       return
565     end
566
567     return unless extract_user
568
569     #assemble path for backend
570     params[:user] = @http_user.login
571     path = request.path
572     path += build_query_from_hash(params, [:user, :comment])
573
574     if request.put?
575       unless @http_user.can_modify_project?(@project)
576         render_error :status => 403, :errorcode => 'put_project_config_no_permission',
577           :message => "No permission to write build configuration for project '#{params[:project]}'"
578         return
579       end
580
581       pass_to_backend path
582       return
583     end
584     render_error :status => 400, :errorcode => 'illegal_request',
585         :message => "Illegal request: #{request.path}"
586   end
587
588   def project_pubkey
589     valid_http_methods :get, :delete
590
591     #assemble path for backend
592     params[:user] = @http_user.login if request.delete?
593     path = request.path
594     path += build_query_from_hash(params, [:user, :comment, :rev])
595
596     #check if project exists
597     unless (@project = DbProject.find_by_name(params[:project]))
598       render_error :status => 404, :errorcode => 'project_not_found',
599         :message => "Unknown project #{params[:project]}"
600       return
601     end
602
603     if request.get?
604       pass_to_backend path
605     elsif request.delete?
606       #check for permissions
607       unless @http_user.can_modify_project?(@project)
608         render_error :status => 403, :errorcode => 'delete_project_pubkey_no_permission',
609           :message => "No permission to delete public key for project '#{params[:project]}'"
610         return
611       end
612
613       pass_to_backend path
614       return
615     end
616   end
617
618   def update_package_meta(project_name, package_name, request_data, user=nil, comment=nil)
619     allowed = false
620     # Try to fetch the package to see if it already exists
621     @package = Package.find( package_name, :project => project_name )
622
623     if @package
624       # Being here means that the package already exists
625       allowed = permissions.package_change? @package
626       if allowed
627         @package = Package.new( request_data, :project => project_name, :name => package_name )
628       else
629         logger.debug "user #{user} has no permission to change package #{@package}"
630         render_error :status => 403, :errorcode => "change_package_no_permission",
631           :message => "no permission to change package"
632         return
633       end
634     else
635       # Ok, the package is new
636       allowed = permissions.package_create?( project_name )
637
638       if allowed
639         #FIXME: parameters that get substituted into the url must be specified here... should happen
640         #somehow automagically... no idea how this might work
641         @package = Package.new( request_data, :project => project_name, :name => package_name )
642       else
643         # User is not allowed by global permission.
644         logger.debug "Not allowed to create new packages"
645         render_error :status => 403, :errorcode => "create_package_no_permission",
646           :message => "no permission to create package for project #{project_name}"
647         return
648       end
649     end
650
651     if allowed
652       if( @package.name != package_name )
653         render_error :status => 400, :errorcode => 'package_name_mismatch',
654           :message => "package name in xml data does not match resource path component"
655         return
656       end
657
658       begin
659         @package.save
660       rescue DbPackage::CycleError => e
661         render_error :status => 400, :errorcode => 'devel_cycle', :message => e.message
662         return
663       end
664       render_ok
665     else
666       logger.debug "user #{user} has no permission to write package meta for package #{@package}"
667     end
668   end
669   private :update_package_meta
670
671   def package_meta
672     valid_http_methods :put, :get
673    
674     project_name = params[:project]
675     package_name = params[:package]
676
677     if project_name.nil?
678       render_error :status => 400, :errorcode => "parameter_missing",
679         :message => "parameter 'project' missing"
680       return
681     end
682
683     if package_name.nil?
684       render_error :status => 400, :errorcode => "parameter_missing",
685         :message => "parameter 'package' missing"
686       return
687     end
688
689     unless pro = DbProject.find_by_name(project_name)
690       pro, pro_name = DbProject.find_remote_project(project_name)
691       unless request.get? and pro
692         render_error :status => 404, :errorcode => "unknown_project",
693           :message => "Unknown project '#{project_name}'"
694         return
695       end
696     end
697
698     unless valid_package_name? package_name
699       render_error :status => 400, :errorcode => "invalid_package_name",
700         :message => "invalid package name '#{package_name}'"
701       return
702     end
703
704     if request.get?
705       begin
706         pack = pro.find_package( package_name )
707       rescue DbProject::CycleError => e
708         render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
709         return
710       end
711       unless pack
712         # check if this comes from a remote project, also true for _project package
713         answer = Suse::Backend.get(request.path)
714         if answer
715           render :text => answer.body.to_s, :content_type => 'text/xml'
716         else
717           render_error :status => 404, :errorcode => "unknown_package",
718             :message => "Unknown package '#{package_name}'"
719         end
720         return
721       end
722
723       render :text => pack.to_axml(params[:view]), :content_type => 'text/xml'
724     else
725       update_package_meta(project_name, package_name, request.raw_post, @http_user.login, params[:comment])
726     end
727   end
728
729   def file
730     valid_http_methods :get, :delete, :put
731     project_name = params[:project]
732     package_name = params[:package]
733     file = params[:file]
734     path = "/source/#{CGI.escape(project_name)}/#{CGI.escape(package_name)}/#{CGI.escape(file)}"
735
736     if request.get?
737       path += build_query_from_hash(params, [:rev, :meta])
738       pass_to_backend path
739       return
740     end
741
742     #authenticate
743     return unless extract_user
744
745     pack = DbPackage.find_by_project_and_name(project_name, package_name)
746     if package_name == "_project"
747       allowed = permissions.project_change? project_name
748     else
749       if pack.nil? and package_name != "_project"
750         render_error :status => 403, :errorcode => 'not_found',
751           :message => "The given package #{package_name} does not exist in project #{project_name}"
752         return
753       end
754       allowed = permissions.package_change? pack
755     end
756
757     params[:user] = @http_user.login
758     if request.put?
759       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink, :meta])
760       
761       if  allowed
762         # file validation where possible
763         if params[:file] == "_link"
764            validator = Suse::Validator.new( "link" )
765            validator.validate(request)
766         elsif params[:file] == "_aggregate"
767            validator = Suse::Validator.new( "aggregate" )
768            validator.validate(request)
769         end
770
771         pass_to_backend path
772         pack.update_timestamp
773         if package_name == "_product"
774           update_product_autopackages
775         end
776       else
777         render_error :status => 403, :errorcode => 'put_file_no_permission',
778           :message => "Insufficient permissions to store file in package #{package_name}, project #{project_name}"
779       end
780     elsif request.delete?
781       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink])
782       
783       if  allowed
784         Suse::Backend.delete path
785         pack = DbPackage.find_by_project_and_name(project_name, package_name)
786         pack.update_timestamp
787         if package_name == "_product"
788           update_product_autopackages
789         end
790         render_ok
791       else
792         render_error :status => 403, :errorcode => 'delete_file_no_permission',
793           :message => "Insufficient permissions to delete file"
794       end
795     end
796   end
797
798   private
799
800   # POST /source?cmd=branch
801   def index_branch
802     # set defaults
803     mparams=params
804     if not params[:target_project]
805       mparams[:target_project] = "home:#{@http_user.login}:branches:#{params[:attribute].gsub(':', '_')}"
806       mparams[:target_project] += ":#{params[:package]}" if params[:package]
807     end
808     if not params[:update_project_attribute]
809       params[:update_project_attribute] = "OBS:UpdateProject"
810     end
811     if not params[:attribute]
812       params[:attribute] = "OBS:Maintained"
813     end
814
815     # permission check
816     unless @http_user.can_create_project?(mparams[:target_project])
817       render_error :status => 403, :errorcode => "create_project_no_permission",
818         :message => "no permission to create project '#{mparams[:target_project]}' while executing branch command"
819       return
820     end
821
822     # find packages
823     at = AttribType.find_by_name(params[:attribute])
824     if not at
825       render_error :status => 403, :errorcode => 'not_found',
826         :message => "The given attribute #{params[:attribute]} does not exist"
827       return
828     end
829     if params[:value]
830       @packages = DbPackage.find_by_attribute_type_and_value( at, params[:value], params[:package] )
831     else
832       @packages = DbPackage.find_by_attribute_type( at, params[:package] )
833     end
834     unless @packages.length > 0
835       render_error :status => 403, :errorcode => "not_found",
836         :message => "no packages could get found"
837       return
838     end
839
840     #create branch project
841     oprj = DbProject.find_by_name mparams[:target_project]
842     if oprj.nil?
843       DbProject.transaction do
844         oprj = DbProject.new :name => mparams[:target_project], :title => "Branch Project _FIXME_", :description => "_FIXME_"
845         oprj.add_user @http_user, "maintainer"
846         oprj.build_flags.create( :position => 1, :status => "disable" )
847         oprj.publish_flags.create( :position => 1, :status => "disable" )
848         oprj.store
849       end
850     else
851       unless @http_user.can_modify_project?(oprj)
852         render_error :status => 403, :errorcode => "modify_project_no_permission",
853           :message => "no permission to modify project '#{mparams[:target_project]}' while executing branch by attribute command"
854         return
855       end
856     end
857
858     # create package branches
859     # collect also the needed repositories here
860     @packages.each do |p|
861     
862       # is a update project defined and a package there ?
863       pac = p
864       aname = params[:update_project_attribute]
865       name_parts = aname.split(/:/)
866       if name_parts.length != 2
867         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
868       end
869
870       # find origin package to be branched
871       branch_target_project = pac.db_project.name
872       branch_target_package = pac.name
873       if a = p.db_project.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
874         if pa = DbPackage.find_by_project_and_name( a.values[0].value, p.name )
875           pac = pa
876           branch_target_project = pac.db_project.name
877           branch_target_package = pac.name
878         else
879           # package exists not yet in update project, but it may have a project link ?
880           p = DbProject.find_by_name(a.values[0].value)
881           if p and p.find_package( pac.name )
882             branch_target_project = a.values[0].value
883           end
884         end
885       end
886       proj_name = pac.db_project.name.gsub(':', '_')
887       pack_name = pac.name.gsub(':', '_')+"."+proj_name
888
889       # create branch package
890       # no find_package call here to check really this project only
891       if opkg = oprj.db_packages.find_by_name(pack_name)
892         render_error :status => 400, :errorcode => "double_branch_package",
893           :message => "branch target package already exists: #{oprj.name}/#{opkg.name}"
894         return
895       else
896         opkg = oprj.db_packages.new(:name => pack_name, :title => pac.title, :description => pac.description)
897         oprj.db_packages << opkg
898       end
899
900       # create repositories, if missing
901       pac.db_project.repositories.each do |repo|
902         orepo = oprj.repositories.create :name => proj_name+"_"+repo.name
903         orepo.architectures = repo.architectures
904         orepo.path_elements.create(:link => repo, :position => 1)
905       end
906
907       opkg.store
908
909       # branch sources in backend
910       Suse::Backend.post "/source/#{oprj.name}/#{opkg.name}?cmd=branch&oproject=#{CGI.escape(branch_target_project)}&opackage=#{CGI.escape(branch_target_package)}", nil
911     end
912
913     # store project data in DB and XML
914     oprj.store
915
916     # all that worked ? :)
917     render_ok :data => {:targetproject => mparams[:target_project]}
918   end
919
920   # POST /source/<project>?cmd=extendkey
921   def index_project_extendkey
922     valid_http_methods :post
923     params[:user] = @http_user.login
924
925     path = request.path
926     path << build_query_from_hash(params, [:cmd, :user, :comment])
927     pass_to_backend path
928   end
929
930   # POST /source/<project>?cmd=createkey
931   def index_project_createkey
932     valid_http_methods :post
933     params[:user] = @http_user.login
934
935     path = request.path
936     path << build_query_from_hash(params, [:cmd, :user, :comment])
937     pass_to_backend path
938   end
939
940   # POST /source/<project>?cmd=undelete
941   def index_project_undelete
942     valid_http_methods :post
943     params[:user] = @http_user.login
944
945     path = request.path
946     path << build_query_from_hash(params, [:cmd, :user, :comment])
947     pass_to_backend path
948   end
949
950   # POST /source/<project>?cmd=createpatchinfo
951   def index_project_createpatchinfo
952     name=""
953     if params[:name]
954       name=params[:name] if params[:name]
955     end
956     pkg_name = "_patchinfo:#{name.gsub(/\W/, '_')}"
957     patchinfo_path = "#{request.path}/#{pkg_name}"
958
959     # request binaries in project from backend
960     binaries = list_all_binaries_in_path("/build/#{params[:project]}")
961
962     if binaries.length < 1 and not params[:force]
963       render_error :status => 400, :errorcode => "no_matched_binaries",
964         :message => "No binary packages were found in project repositories"
965       return
966     end
967
968     # FIXME: check for still building packages
969
970     # create patchinfo package
971     if not DbPackage.find_by_project_and_name( params[:project], pkg_name )
972       prj = DbProject.find_by_name( params[:project] )
973       pkg = DbPackage.new(:name => pkg_name, :title => "Patchinfo", :description => "Collected packages for update")
974       prj.db_packages << pkg
975       Package.find(pkg_name, :project => params[:project]).save
976       if name==""
977         name=pkg_name
978       end
979     else
980       # shall we do a force check here ?
981     end
982
983     # create patchinfo XML file
984     node = Builder::XmlMarkup.new(:indent=>2)
985     xml = node.patchinfo(:name => name) do |n|
986       binaries.each do |binary|
987         node.binary(binary)
988       end
989       node.packager    @http_user.login
990       node.bugzilla    ""
991       node.swampid     ""
992       node.category    ""
993       node.rating      ""
994       node.summary     ""
995       node.description ""
996       # FIXME add all bugnumbers from attributes
997     end
998     backend_put( patchinfo_path+"/_patchinfo?user="+@http_user.login+"&comment=generated%20file%20by%20frontend", xml )
999
1000     render_ok
1001   end
1002
1003   def list_all_binaries_in_path path
1004     d = backend_get(path)
1005     data = REXML::Document.new(d)
1006     binaries = []
1007
1008     data.elements.each("directory/entry") do |e|
1009       name = e.attributes["name"]
1010       list_all_binaries_in_path("#{path}/#{name}").each do |l|
1011         binaries.push( l )
1012       end
1013     end
1014     data.elements.each("binarylist/binary") do |b|
1015       name = b.attributes["filename"]
1016       # strip main name from binary
1017       # patchinfos are only designed for rpms so far
1018       binaries.push( name.sub(/-[^-]*-[^-]*.rpm$/, '' ) )
1019     end
1020
1021     binaries.uniq!
1022     return binaries
1023   end
1024
1025   # POST /source/<project>/<package>?cmd=undelete
1026   def index_package_undelete
1027     valid_http_methods :post
1028     params[:user] = @http_user.login
1029
1030     path = request.path
1031     path << build_query_from_hash(params, [:cmd, :user, :comment])
1032     pass_to_backend path
1033   end
1034
1035   # POST /source/<project>/<package>?cmd=createSpecFileTemplate
1036   def index_package_createSpecFileTemplate
1037     specfile_path = "#{request.path}/#{params[:package]}.spec"
1038     begin
1039       backend_get( specfile_path )
1040       render_error :status => 400, :errorcode => "spec_file_exists",
1041         :message => "SPEC file already exists."
1042       return
1043     rescue ActiveXML::Transport::NotFoundError
1044       specfile = File.read "#{RAILS_ROOT}/files/specfiletemplate"
1045       backend_put( specfile_path, specfile )
1046     end
1047     render_ok
1048   end
1049
1050   # POST /source/<project>/<package>?cmd=rebuild
1051   def index_package_rebuild
1052     project_name = params[:project]
1053     package_name = params[:package]
1054     repo_name = params[:repo]
1055     arch_name = params[:arch]
1056
1057     path = "/build/#{project_name}?cmd=rebuild&package=#{package_name}"
1058     
1059     p = DbProject.find_by_name project_name
1060     if p.nil?
1061       render_error :status => 400, :errorcode => 'unknown_project',
1062         :message => "Unknown project '#{project_name}'"
1063       return
1064     end
1065
1066     begin
1067       pkg = p.find_package( package_name )
1068     rescue DbProject::CycleError => e
1069       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1070       return
1071     end
1072     unless pkg
1073       render_error :status => 400, :errorcode => 'unknown_package',
1074         :message => "Unknown package '#{package_name}'"
1075       return
1076     end
1077
1078     if repo_name
1079       path += "&repository=#{repo_name}"
1080       if p.repositories.find_by_name(repo_name).nil?
1081         render_error :status => 400, :errorcode => 'unknown_repository',
1082           :message=> "Unknown repository '#{repo_name}'"
1083         return
1084       end
1085     end
1086
1087     if arch_name
1088       path += "&arch=#{arch_name}"
1089     end
1090
1091     backend.direct_http( URI(path), :method => "POST", :data => "" )
1092     render_ok
1093   end
1094
1095   # POST /source/<project>/<package>?cmd=commit
1096   def index_package_commit
1097     valid_http_methods :post
1098     params[:user] = @http_user.login
1099
1100     path = request.path
1101     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
1102     pass_to_backend path
1103
1104     if params[:package] == "_product"
1105       update_product_autopackages
1106     end
1107   end
1108
1109   # POST /source/<project>/<package>?cmd=commitfilelist
1110   def index_package_commitfilelist
1111     valid_http_methods :post
1112     params[:user] = @http_user.login
1113
1114     path = request.path
1115     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
1116     pass_to_backend path
1117     
1118     if params[:package] == "_product"
1119       update_product_autopackages
1120     end
1121   end
1122
1123   # POST /source/<project>/<package>?cmd=diff
1124   def index_package_diff
1125     valid_http_methods :post
1126     path = request.path
1127     path << build_query_from_hash(params, [:cmd, :rev, :oproject, :opackage, :orev, :expand, :unified, :linkrev, :olinkrev, :missingok])
1128     pass_to_backend path
1129   end
1130
1131   # POST /source/<project>/<package>?cmd=linkdiff
1132   def index_package_linkdiff
1133     valid_http_methods :post
1134     path = request.path
1135     path << build_query_from_hash(params, [:rev, :unified, :linkrev])
1136     pass_to_backend path
1137   end
1138
1139   # POST /source/<project>/<package>?cmd=copy
1140   def index_package_copy
1141     valid_http_methods :post
1142     params[:user] = @http_user.login
1143
1144     pack = DbPackage.find_by_project_and_name(params[:project], params[:package])
1145     if pack.nil? 
1146       render_error :status => 404, :errorcode => 'unknown_package',
1147         :message => "Unknown package #{params[:package]} in project #{params[:project]}"
1148       return
1149     end
1150
1151     path = request.path
1152     path << build_query_from_hash(params, [:cmd, :rev, :user, :comment, :oproject, :opackage, :orev, :expand, :keeplink, :repairlink, :linkrev, :olinkrev, :requestid, :dontupdatesource])
1153     
1154     pass_to_backend path
1155   end
1156
1157   # POST /source/<project>/<package>?cmd=runservice
1158   def index_package_runservice
1159     valid_http_methods :post
1160     params[:user] = @http_user.login
1161
1162     path = request.path
1163     path << build_query_from_hash(params, [:cmd, :comment])
1164     pass_to_backend path
1165   end
1166
1167   # POST /source/<project>/<package>?cmd=deleteuploadrev
1168   def index_package_deleteuploadrev
1169     valid_http_methods :post
1170     params[:user] = @http_user.login
1171
1172     path = request.path
1173     path << build_query_from_hash(params, [:cmd])
1174     pass_to_backend path
1175   end
1176
1177   # POST /source/<project>/<package>?cmd=linktobranch
1178   def index_package_linktobranch
1179     valid_http_methods :post
1180     params[:user] = @http_user.login
1181     prj_name = params[:project]
1182     pkg_name = params[:package]
1183     pkg_rev = params[:rev]
1184     pkg_linkrev = params[:linkrev]
1185
1186     prj = DbProject.find_by_name prj_name
1187     pkg = prj.db_packages.find_by_name(pkg_name)
1188     if pkg.nil?
1189       render_error :status => 404, :errorcode => 'unknown_package',
1190         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1191       return
1192     end
1193
1194     #convert link to branch
1195     rev = ""
1196     if not pkg_rev.nil? and not pkg_rev.empty?
1197       rev = "&orev=#{pkg_rev}"
1198     end
1199     linkrev = ""
1200     if not pkg_linkrev.nil? and not pkg_linkrev.empty?
1201       linkrev = "&linkrev=#{pkg_linkrev}"
1202     end
1203     Suse::Backend.post "/source/#{prj_name}/#{pkg_name}?cmd=linktobranch&user=#{CGI.escape(params[:user])}#{rev}#{linkrev}", nil
1204
1205     render_ok
1206   end
1207
1208   # POST /source/<project>/<package>?cmd=branch&target_project="optional_project"&target_package="optional_package"&update_project_attribute="alternative_attribute"&comment="message"
1209   def index_package_branch
1210     valid_http_methods :post
1211     params[:user] = @http_user.login
1212     prj_name = params[:project]
1213     pkg_name = params[:package]
1214     pkg_rev = params[:rev]
1215     target_project = params[:target_project]
1216     target_package = params[:target_package]
1217     if not params[:update_project_attribute]
1218       params[:update_project_attribute] = "OBS:UpdateProject"
1219     end
1220     logger.debug "branch call of #{prj_name} #{pkg_name}"
1221
1222     prj = DbProject.find_by_name prj_name
1223     if prj.nil?
1224       render_error :status => 404, :errorcode => 'unknown_project',
1225         :message => "Unknown project #{prj_name}"
1226       return
1227     end
1228     begin
1229       pkg = prj.find_package( pkg_name )
1230     rescue DbProject::CycleError => e
1231       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1232       return
1233     end
1234     if pkg.nil?
1235       render_error :status => 404, :errorcode => 'unknown_package',
1236         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1237       return
1238     end
1239
1240     # is a update project defined and a package there ?
1241     aname = params[:update_project_attribute]
1242     name_parts = aname.split(/:/)
1243     if name_parts.length != 2
1244       raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
1245     end
1246
1247     if a = prj.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
1248       if pa = DbPackage.find_by_project_and_name( a.values[0].value, pkg.name )
1249         # We have a package in the update project already, take that
1250         pkg = pa
1251         pkg_name = pkg.name
1252         prj = pkg.db_project
1253         prj_name = prj.name
1254         logger.debug "branch call found package in update project #{prj_name}"
1255       else
1256         update_prj = DbProject.find_by_name( a.values[0].value )
1257         update_pkg = update_prj.find_package( pkg_name )
1258         if update_pkg
1259           # We have no package in the update project yet, but sources a reachable via project link
1260           pkg = update_pkg
1261           pkg_name = pkg.name
1262           prj = update_prj
1263           prj_name = prj.name
1264         end
1265       end
1266     end
1267
1268     # validate and resolve devel package or devel project definitions
1269     if not params[:ignoredevel] and ( pkg.develproject or pkg.develpackage )
1270       pkg = pkg.resolve_devel_package
1271       pkg_name = pkg.name
1272       prj = pkg.db_project
1273       prj_name = prj.name
1274       logger.debug "devel project is #{prj_name} #{pkg_name}"
1275     end
1276
1277     # link against srcmd5 instead of plain revision
1278     unless pkg_rev.nil?
1279       path = "/source/#{params[:project]}/#{params[:package]}" + build_query_from_hash(params, [:rev])
1280       files = Suse::Backend.get(path)
1281       # get srcmd5 from the xml data
1282       match = files.body.match(/<directory['"=\w\s]+srcmd5=['"](\w{32})['"]['"=\w\s]*>/)
1283       if match
1284         pkg_rev = match[1]
1285       else
1286         # this should not happen
1287         render_error :status => 400, :errorcode => 'invalid_filelist',
1288           :message => "Unable parse filelist from backend"
1289         return
1290       end
1291     end
1292  
1293     oprj_name = "home:#{@http_user.login}:branches:#{prj_name}"
1294     opkg_name = pkg_name
1295     oprj_name = target_project unless target_project.nil?
1296     opkg_name = target_package unless target_package.nil?
1297
1298     #create branch container
1299     oprj = DbProject.find_by_name oprj_name
1300     if oprj.nil?
1301       unless @http_user.can_create_project?(oprj_name)
1302         render_error :status => 403, :errorcode => "create_project_no_permission",
1303           :message => "no permission to create project '#{oprj_name}' while executing branch command"
1304         return
1305       end
1306
1307       DbProject.transaction do
1308         oprj = DbProject.new :name => oprj_name, :title => "Branch of #{prj.title}", :description => prj.description
1309         oprj.add_user @http_user, "maintainer"
1310         oprj.publish_flags << PublishFlag.new( :status => "disable", :position => 1 )
1311         prj.repositories.each do |repo|
1312           orepo = oprj.repositories.create :name => repo.name
1313           orepo.architectures = repo.architectures
1314           orepo.path_elements << PathElement.new(:link => repo, :position => 1)
1315         end
1316         oprj.store
1317       end
1318     end
1319
1320     #create branch package
1321     if opkg = oprj.db_packages.find_by_name(opkg_name)
1322       if params[:force]
1323         # shall we clean all files here ?
1324       else
1325         render_error :status => 400, :errorcode => "double_branch_package",
1326           :message => "branch target package already exists: #{oprj_name}/#{opkg_name}"
1327         return
1328       end
1329
1330       unless @http_user.can_modify_package?(opkg)
1331         render_error :status => 403, :errorcode => "create_package_no_permission",
1332           :message => "no permission to create package '#{opkg_name}' for project '#{oprj_name}' while executing branch command"
1333         return
1334       end
1335     else
1336       unless @http_user.can_create_package_in?(oprj)
1337         render_error :status => 403, :errorcode => "create_package_no_permission",
1338           :message => "no permission to create package '#{opkg_name}' for project '#{oprj_name}' while executing branch command"
1339         return
1340       end
1341
1342       opkg = oprj.db_packages.create(:name => opkg_name, :title => pkg.title, :description => params.has_key?(:comment) ? params[:comment] : pkg.description)
1343       opkg.add_user @http_user, "maintainer"
1344       opkg.store
1345     end
1346
1347     #create branch of sources in backend
1348     rev = ""
1349     if not pkg_rev.nil? and not pkg_rev.empty?
1350       rev = "&orev=#{pkg_rev}"
1351     end
1352     comment = params.has_key?(:comment) ? "&comment=#{CGI.escape(params[:comment])}" : ""
1353     Suse::Backend.post "/source/#{oprj_name}/#{opkg_name}?cmd=branch&oproject=#{CGI.escape(prj_name)}&opackage=#{CGI.escape(pkg_name)}#{rev}&user=#{CGI.escape(@http_user.login)}#{comment}", nil
1354
1355     render_ok :data => {:targetproject => oprj_name, :targetpackage => opkg_name, :sourceproject => prj_name, :sourcepackage => pkg_name}
1356   end
1357
1358   # POST /source/<project>/<package>?cmd=set_flag&repository=:opt&arch=:opt&flag=flag&status=status
1359   def index_package_set_flag
1360     valid_http_methods :post
1361
1362     prj_name = params[:project]
1363     pkg_name = params[:package]
1364
1365     prj = DbProject.find_by_name prj_name
1366     if prj.nil?
1367       render_error :status => 404, :errorcode => 'unknown_project',
1368         :message => "Unknown project #{prj_name}"
1369       return
1370     end
1371     begin
1372       pkg = prj.find_package( pkg_name )
1373     rescue DbProject::CycleError => e
1374       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1375       return
1376     end
1377     # first remove former flags of the same class
1378     pkg.remove_flag(params[:flag], params[:repository], params[:arch])
1379     pkg.add_flag(params[:flag], params[:status], params[:repository], params[:arch])
1380     pkg.store
1381     render_ok
1382   end
1383
1384   # POST /source/<project>?cmd=set_flag&repository=:opt&arch=:opt&flag=flag&status=status
1385   def index_project_set_flag
1386     valid_http_methods :post
1387
1388     prj_name = params[:project]
1389
1390     prj = DbProject.find_by_name prj_name
1391     if prj.nil?
1392       render_error :status => 404, :errorcode => 'unknown_project',
1393         :message => "Unknown project #{prj_name}"
1394       return
1395     end
1396     # first remove former flags of the same class
1397     prj.remove_flag(params[:flag], params[:repository], params[:arch])
1398     prj.add_flag(params[:flag], params[:status], params[:repository], params[:arch])
1399     prj.store
1400     render_ok
1401   end
1402
1403   # POST /source/<project>/<package>?cmd=remove_flag&repository=:opt&arch=:opt&flag=flag
1404   def index_package_remove_flag
1405     valid_http_methods :post
1406     
1407     prj_name = params[:project]
1408     pkg_name = params[:package]
1409
1410     prj = DbProject.find_by_name prj_name
1411     if prj.nil?
1412       render_error :status => 404, :errorcode => 'unknown_project',
1413         :message => "Unknown project #{prj_name}"
1414       return
1415     end
1416     begin
1417       pkg = prj.find_package( pkg_name )
1418     rescue DbProject::CycleError => e
1419       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1420       return
1421     end
1422     if pkg.nil?
1423       render_error :status => 404, :errorcode => "unknown_package",
1424         :message => "unknown package '#{pkg_name}' in project '#{prj_name}'"
1425       return
1426     end
1427     pkg.remove_flag(params[:flag], params[:repository], params[:arch])
1428     pkg.store
1429     render_ok
1430   end
1431
1432   # POST /source/<project>?cmd=remove_flag&repository=:opt&arch=:opt&flag=flag
1433   def index_project_remove_flag
1434     valid_http_methods :post
1435
1436     prj_name = params[:project]
1437
1438     prj = DbProject.find_by_name prj_name
1439     if prj.nil?
1440       render_error :status => 404, :errorcode => 'unknown_project',
1441         :message => "Unknown project #{prj_name}"
1442       return
1443     end
1444     prj.remove_flag(params[:flag], params[:repository], params[:arch])
1445     prj.store
1446     render_ok
1447   end
1448
1449   def valid_project_name? name
1450     name =~ /^\w[-_+\w\.:]*$/
1451   end
1452
1453   def valid_package_name? name
1454     return true if name == "_pattern"
1455     return true if name == "_project"
1456     return true if name == "_product"
1457     return true if name =~ /^_product:[-_+\w\.:]*$/
1458     name =~ /^\w[-_+\w\.:]*$/
1459   end
1460
1461 end