getpatch: use ssl context for ssl endpoints
[freebsd-maintainer-scripts/.git] / getpatch
CommitLineData
efd73192
SB
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2012 Sofian Brabez <sbz@FreeBSD.org>
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer
12# 2. Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in the
14# documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26#
27# $FreeBSD: head/Tools/scripts/getpatch 362667 2014-07-23 12:14:32Z sbz $
28#
29# MAINTAINER= sbz@FreeBSD.org
30
31import argparse
32import codecs
2b746d10 33import os
efd73192 34import re
2b746d10 35import ssl
efd73192
SB
36import sys
37if sys.version_info.major == 3:
38 import urllib.request as urllib2
39else:
40 import urllib2
41
42"""
43FreeBSD getpatch handles Gnats and Bugzilla patch attachments
44"""
45
2b746d10
SB
46
47def create_ssl_context(cafile):
48 if os.path.exists(cafile):
49 return ssl.create_default_context(cafile=cafile)
50 else:
51 return ssl._create_unverified_context()
52
53
efd73192
SB
54class GetPatch(object):
55
56 def __init__(self, pr, category='ports'):
57 self.pr = pr
58 self.category = category
59 self.patchs = []
60 self.url = ""
61 self.patch = ""
62 self.output_stdout = False
63 self.default_locale = sys.getdefaultencoding()
2b746d10 64 self.ssl_context = create_ssl_context('/usr/local/etc/ssl/cert.pem')
efd73192
SB
65
66 def fetch(self, *largs, **kwargs):
67 raise NotImplementedError()
68
69 def write(self, filename, data):
70 if filename.endswith(('.patch', '.txt')):
71 filename = filename[:filename.rindex('.')]+'.diff'
72 f=codecs.open(filename, encoding=self.default_locale, mode='w')
73 f.write(data.decode(self.default_locale))
74 f.close()
75 self.out("[+] %s created" % filename)
76
77 def get(self,only_last=False, output_stdout=False):
78 self.output_stdout = output_stdout
79 self.fetch(self.pr, category=self.category)
80
81 if len(self.patchs) == 0:
82 self.out("[-] No patch found")
83 sys.exit(1)
84
85 if only_last:
86 self.patchs = [self.patchs.pop()]
87
88 for patch in self.patchs:
89 url = patch['url']
90 p = patch['name']
91
2b746d10 92 data = urllib2.urlopen(url, context=self.ssl_context).read()
efd73192
SB
93
94 if self.output_stdout:
95 sys.stdout.write(data.decode(self.default_locale))
96 else:
97 self.write(p, data)
98
99 def add_patch(self, url, name):
100 self.patchs.append({'url': url, 'name': name})
101
102 def out(self, s):
103 if not self.output_stdout:
104 print(s)
105
106class GnatsGetPatch(GetPatch):
107
108 URL_BASE = 'https://www.freebsd.org/cgi'
109 URL = '%s/query-pr.cgi?pr=' % URL_BASE
110 REGEX = r'<b>Download <a href="([^"]*)">([^<]*)</a>'
111
112 def __init__(self, pr, category):
113 GetPatch.__init__(self, pr, category)
114
115 def fetch(self, *largs, **kwargs):
116 category = kwargs['category']
117 target = ("%s/%s" % (category, self.pr), "%s" % self.pr)[category=='']
118 self.out("[+] Fetching patch for pr %s" % target)
119 pattern = re.compile(self.REGEX)
2b746d10 120 u = urllib2.urlopen(self.URL+'%s' % target, context=self.ssl_context)
efd73192
SB
121 data = u.read()
122 if data == None:
123 self.out("[-] No patch found")
124 sys.exit(1)
125
126 for patchs in re.findall(pattern, str(data)):
127 self.add_patch(patchs[0], patchs[1])
128
129class BzGetPatch(GetPatch):
130
131 URL_BASE= 'https://bugs.freebsd.org/bugzilla/'
132 URL_SHOW = '%s/show_bug.cgi?id=' % URL_BASE
133 REGEX_URL = r'<a href="([^<]+)">Details</a>'
134 REGEX = r'<div class="details">([^ ]+) \(text/plain(?:; charset=[-\w]+)?\)'
135
136 def __init__(self, pr, category):
137 GetPatch.__init__(self, pr, category)
138
139 def _get_patch_name(self, url):
2b746d10 140 match = re.search(self.REGEX, urllib2.urlopen(url, context=self.ssl_context).read())
efd73192
SB
141 if match is None:
142 return None
143 return match.group(1)
144
145 def _get_patch_urls(self, data):
146 patch_urls = {}
147 for url in re.findall(self.REGEX_URL, data):
148 url = '%s/%s' % (self.URL_BASE, url)
149 file_name = self._get_patch_name(url)
150 if file_name is None:
151 self.out("[-] Could not determine the patch file name in %s. "
152 "Skipping." % url)
153 continue
154 download_url = url[:url.find('&')]
155 patch_urls[download_url] = file_name
156 return patch_urls
157
158 def fetch(self, *largs, **kwargs):
159 category = kwargs['category']
160 self.out("[+] Fetching patch for pr %s/%s" % (category, self.pr))
2b746d10 161 u = urllib2.urlopen(self.URL_SHOW+'%s' % self.pr, context=self.ssl_context)
efd73192
SB
162 data = u.read()
163
164 if data == None:
165 self.out("[-] No patch found")
166 sys.exit(1)
167
168 patch_urls = self._get_patch_urls(data)
169 if not patch_urls:
170 self.out("[-] No patch found")
171 sys.exit(1)
172
173 for url, file_name in patch_urls.iteritems():
174 self.add_patch(url, file_name)
175
176def main():
177
178 parser = argparse.ArgumentParser(
179 description='Gets patch attachments from a Bug Tracking System'
180 )
181 parser.add_argument('pr', metavar='pr', type=str, nargs=1,
182 help='Pr id number')
183 parser.add_argument('--mode', type=str, choices=['gnats','bz'],
184 default='bz', help='available modes to retrieve patch')
185 parser.add_argument('--last', action='store_true',
186 help='only retrieve the latest iteration of a patch')
187 parser.add_argument('--stdout', action='store_true',
188 help='dump patch on stdout')
189
190 if len(sys.argv) == 1:
191 parser.print_help()
192 sys.exit(1)
193
194 args = parser.parse_args()
195
196 category = ""
197 pr = str(args.pr[0])
198
199 if '/' in pr and pr is not None:
200 category, pr = pr.split('/')
201
202 Clazz = globals()['%sGetPatch' % args.mode.capitalize()]
203 gp = Clazz(pr, category)
204 gp.get(only_last=args.last, output_stdout=args.stdout)
205
206if __name__ == '__main__':
207 main()