OLD | NEW |
| (Empty) |
1 # Copyright 2016 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 # TODO: In the new layout, this should move to the ./services or | |
6 # ./services/waterfall_app directories, since it is only used by Waterfall. | |
7 | |
8 # TODO: we ought to abstract over the HTTP_CLIENT member (which is only | |
9 # used by the Post method) by passing it to the constructor. That way | |
10 # things are more losely coupled, improving modularity and reducing | |
11 # fragility. In addition, for easier mocking, we may want to just have | |
12 # the thing passed for HTTP_CLIENT to be ``callable``, rather than giving | |
13 # a name to the method we use on that object. | |
14 | |
15 import logging | |
16 import re | |
17 import urlparse | |
18 | |
19 from common.codereview import CodeReview | |
20 from common.http_client_appengine import HttpClientAppengine | |
21 | |
22 | |
23 _RIETVELD_ISSUE_NUMBER_RE = re.compile('^/(\d+)/?.*') | |
24 | |
25 | |
26 class Rietveld(CodeReview): | |
27 """The implementation of CodeReview interface for Rietveld.""" | |
28 HTTP_CLIENT = HttpClientAppengine(follow_redirects=False) | |
29 | |
30 def _GetXsrfToken(self, rietveld_url): | |
31 """Returns the xsrf token for follow-up requests.""" | |
32 headers = { | |
33 'X-Requesting-XSRF-Token': '1', | |
34 'Accept': 'text/plain', | |
35 } | |
36 url = '%s/xsrf_token' % rietveld_url | |
37 status_code, xsrf_token = self.HTTP_CLIENT.Post( | |
38 url, data=None, headers=headers) | |
39 if status_code != 200: | |
40 logging.error('Failed to get xsrf token from %s', rietveld_url) | |
41 xsrf_token = None | |
42 return xsrf_token | |
43 | |
44 def _GetRietveldUrlAndIssueNumber(self, issue_url): | |
45 """Parses the given Rietveld issue url. | |
46 | |
47 Args: | |
48 issue_url(str): The url to an issue on Rietveld. | |
49 Returns: | |
50 (rietveld_url, issue_number) | |
51 rietveld_url(str): The root url of the Rietveld app. | |
52 issue_number(str): The issue number. | |
53 """ | |
54 u = urlparse.urlparse(issue_url) | |
55 rietveld_url = 'https://%s' % u.netloc # Enforces https. | |
56 issue_number = _RIETVELD_ISSUE_NUMBER_RE.match(u.path).group(1) | |
57 return rietveld_url, issue_number | |
58 | |
59 def _EncodeMultipartFormData(self, fields): | |
60 """Encodes form fields for multipart/form-data""" | |
61 BOUNDARY = '-F-I-N-D-I-T-M-E-S-S-A-G-E-' | |
62 CRLF = '\r\n' | |
63 lines = [] | |
64 for key, value in fields.iteritems(): | |
65 lines.append('--' + BOUNDARY) | |
66 lines.append('Content-Disposition: form-data; name="%s"' % key) | |
67 lines.append('') | |
68 lines.append(str(value)) | |
69 lines.append('--' + BOUNDARY + '--') | |
70 lines.append('') | |
71 body = CRLF.join(lines) | |
72 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY | |
73 return content_type, body | |
74 | |
75 def PostMessage(self, issue_url, message): | |
76 rietveld_url, issue_number = self._GetRietveldUrlAndIssueNumber(issue_url) | |
77 url = '%s/%s/publish' % (rietveld_url, issue_number) | |
78 xsrf_token = self._GetXsrfToken(rietveld_url) | |
79 if not xsrf_token: | |
80 return False | |
81 form_fields = { | |
82 'xsrf_token': xsrf_token, | |
83 'message': message, | |
84 'message_only': 'True', | |
85 'add_as_reviewer': 'False', | |
86 'send_mail': 'True', | |
87 'no_redirect': 'True', | |
88 } | |
89 content_type, body = self._EncodeMultipartFormData(form_fields) | |
90 headers = { | |
91 'Content-Type': content_type, | |
92 'Accept': 'text/plain', | |
93 } | |
94 status_code, content = self.HTTP_CLIENT.Post( | |
95 url, data=body, headers=headers) | |
96 return status_code == 200 and content == 'OK' | |
OLD | NEW |