##// END OF EJS Templates
Fixed issue with tripcode
neko259 -
r1564:dd8d22d4 default
parent child Browse files
Show More
@@ -1,280 +1,284 b''
1 1 import xml.etree.ElementTree as et
2 2
3 3 from boards.models.attachment.downloaders import download
4 4 from boards.utils import get_file_mimetype, get_file_hash
5 5 from django.db import transaction
6 6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7 7
8 8 EXCEPTION_NODE = 'Sync node returned an error: {}'
9 9 EXCEPTION_OP = 'Load the OP first'
10 10 EXCEPTION_DOWNLOAD = 'File was not downloaded'
11 11 EXCEPTION_HASH = 'File hash does not match attachment hash'
12 12 EXCEPTION_SIGNATURE = 'Invalid model signature for {}'
13 13 ENCODING_UNICODE = 'unicode'
14 14
15 15 TAG_MODEL = 'model'
16 16 TAG_REQUEST = 'request'
17 17 TAG_RESPONSE = 'response'
18 18 TAG_ID = 'id'
19 19 TAG_STATUS = 'status'
20 20 TAG_MODELS = 'models'
21 21 TAG_TITLE = 'title'
22 22 TAG_TEXT = 'text'
23 23 TAG_THREAD = 'thread'
24 24 TAG_PUB_TIME = 'pub-time'
25 25 TAG_SIGNATURES = 'signatures'
26 26 TAG_SIGNATURE = 'signature'
27 27 TAG_CONTENT = 'content'
28 28 TAG_ATTACHMENTS = 'attachments'
29 29 TAG_ATTACHMENT = 'attachment'
30 30 TAG_TAGS = 'tags'
31 31 TAG_TAG = 'tag'
32 32 TAG_ATTACHMENT_REFS = 'attachment-refs'
33 33 TAG_ATTACHMENT_REF = 'attachment-ref'
34 34 TAG_TRIPCODE = 'tripcode'
35 35
36 36 TYPE_GET = 'get'
37 37
38 38 ATTR_VERSION = 'version'
39 39 ATTR_TYPE = 'type'
40 40 ATTR_NAME = 'name'
41 41 ATTR_VALUE = 'value'
42 42 ATTR_MIMETYPE = 'mimetype'
43 43 ATTR_KEY = 'key'
44 44 ATTR_REF = 'ref'
45 45 ATTR_URL = 'url'
46 46 ATTR_ID_TYPE = 'id-type'
47 47
48 48 ID_TYPE_MD5 = 'md5'
49 49
50 50 STATUS_SUCCESS = 'success'
51 51
52 52
53 53 class SyncException(Exception):
54 54 pass
55 55
56 56
57 57 class SyncManager:
58 58 @staticmethod
59 59 def generate_response_get(model_list: list):
60 60 response = et.Element(TAG_RESPONSE)
61 61
62 62 status = et.SubElement(response, TAG_STATUS)
63 63 status.text = STATUS_SUCCESS
64 64
65 65 models = et.SubElement(response, TAG_MODELS)
66 66
67 67 for post in model_list:
68 68 model = et.SubElement(models, TAG_MODEL)
69 69 model.set(ATTR_NAME, 'post')
70 70
71 71 global_id = post.global_id
72 72
73 73 images = post.images.all()
74 74 attachments = post.attachments.all()
75 75 if global_id.content:
76 76 model.append(et.fromstring(global_id.content))
77 77 if len(images) > 0 or len(attachments) > 0:
78 78 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
79 79 for image in images:
80 80 SyncManager._attachment_to_xml(
81 81 None, attachment_refs, image.image.file,
82 82 image.hash, image.image.url)
83 83 for file in attachments:
84 84 SyncManager._attachment_to_xml(
85 85 None, attachment_refs, file.file.file,
86 86 file.hash, file.file.url)
87 87 else:
88 88 content_tag = et.SubElement(model, TAG_CONTENT)
89 89
90 90 tag_id = et.SubElement(content_tag, TAG_ID)
91 91 global_id.to_xml_element(tag_id)
92 92
93 93 title = et.SubElement(content_tag, TAG_TITLE)
94 94 title.text = post.title
95 95
96 96 text = et.SubElement(content_tag, TAG_TEXT)
97 97 text.text = post.get_sync_text()
98 98
99 99 thread = post.get_thread()
100 100 if post.is_opening():
101 101 tag_tags = et.SubElement(content_tag, TAG_TAGS)
102 102 for tag in thread.get_tags():
103 103 tag_tag = et.SubElement(tag_tags, TAG_TAG)
104 104 tag_tag.text = tag.name
105 105 else:
106 106 tag_thread = et.SubElement(content_tag, TAG_THREAD)
107 107 thread_id = et.SubElement(tag_thread, TAG_ID)
108 108 thread.get_opening_post().global_id.to_xml_element(thread_id)
109 109
110 110 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
111 111 pub_time.text = str(post.get_pub_time_str())
112 112
113 113 if post.tripcode:
114 114 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
115 115 tripcode.text = post.tripcode
116 116
117 117 if len(images) > 0 or len(attachments) > 0:
118 118 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
119 119 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
120 120
121 121 for image in images:
122 122 SyncManager._attachment_to_xml(
123 123 attachments_tag, attachment_refs, image.image.file,
124 124 image.hash, image.image.url)
125 125 for file in attachments:
126 126 SyncManager._attachment_to_xml(
127 127 attachments_tag, attachment_refs, file.file.file,
128 128 file.hash, file.file.url)
129 129
130 130 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
131 131 global_id.save()
132 132
133 133 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
134 134 post_signatures = global_id.signature_set.all()
135 135 if post_signatures:
136 136 signatures = post_signatures
137 137 else:
138 138 key = KeyPair.objects.get(public_key=global_id.key)
139 139 signature = Signature(
140 140 key_type=key.key_type,
141 141 key=key.public_key,
142 142 signature=key.sign(global_id.content),
143 143 global_id=global_id,
144 144 )
145 145 signature.save()
146 146 signatures = [signature]
147 147 for signature in signatures:
148 148 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
149 149 signature_tag.set(ATTR_TYPE, signature.key_type)
150 150 signature_tag.set(ATTR_VALUE, signature.signature)
151 151 signature_tag.set(ATTR_KEY, signature.key)
152 152
153 153 return et.tostring(response, ENCODING_UNICODE)
154 154
155 155 @staticmethod
156 156 @transaction.atomic
157 157 def parse_response_get(response_xml, hostname):
158 158 tag_root = et.fromstring(response_xml)
159 159 tag_status = tag_root.find(TAG_STATUS)
160 160 if STATUS_SUCCESS == tag_status.text:
161 161 tag_models = tag_root.find(TAG_MODELS)
162 162 for tag_model in tag_models:
163 163 tag_content = tag_model.find(TAG_CONTENT)
164 164
165 165 content_str = et.tostring(tag_content, ENCODING_UNICODE)
166 166 signatures = SyncManager._verify_model(content_str, tag_model)
167 167
168 168 tag_id = tag_content.find(TAG_ID)
169 169 global_id, exists = GlobalId.from_xml_element(tag_id)
170 170
171 171 if exists:
172 172 print('Post with same ID already exists')
173 173 else:
174 174 global_id.content = content_str
175 175 global_id.save()
176 176 for signature in signatures:
177 177 signature.global_id = global_id
178 178 signature.save()
179 179
180 180 title = tag_content.find(TAG_TITLE).text or ''
181 181 text = tag_content.find(TAG_TEXT).text or ''
182 182 pub_time = tag_content.find(TAG_PUB_TIME).text
183 tripcode = tag_content.find(TAG_TRIPCODE).text
183 tripcode_tag = tag_content.find(TAG_TRIPCODE)
184 if tripcode_tag is not None:
185 tripcode = tripcode_tag.text or ''
186 else:
187 tripcode = ''
184 188
185 189 thread = tag_content.find(TAG_THREAD)
186 190 tags = []
187 191 if thread:
188 192 thread_id = thread.find(TAG_ID)
189 193 op_global_id, exists = GlobalId.from_xml_element(thread_id)
190 194 if exists:
191 195 opening_post = Post.objects.get(global_id=op_global_id)
192 196 else:
193 197 raise SyncException(EXCEPTION_OP)
194 198 else:
195 199 opening_post = None
196 200 tag_tags = tag_content.find(TAG_TAGS)
197 201 for tag_tag in tag_tags:
198 202 tag, created = Tag.objects.get_or_create(
199 203 name=tag_tag.text)
200 204 tags.append(tag)
201 205
202 206 # TODO Check that the replied posts are already present
203 207 # before adding new ones
204 208
205 209 files = []
206 210 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
207 211 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
208 212 for attachment in tag_attachments:
209 213 tag_ref = tag_refs.find("{}[@ref='{}']".format(
210 214 TAG_ATTACHMENT_REF, attachment.text))
211 215 url = tag_ref.get(ATTR_URL)
212 216 attached_file = download(hostname + url)
213 217 if attached_file is None:
214 218 raise SyncException(EXCEPTION_DOWNLOAD)
215 219
216 220 hash = get_file_hash(attached_file)
217 221 if hash != attachment.text:
218 222 raise SyncException(EXCEPTION_HASH)
219 223
220 224 files.append(attached_file)
221 225
222 226 Post.objects.import_post(
223 227 title=title, text=text, pub_time=pub_time,
224 228 opening_post=opening_post, tags=tags,
225 229 global_id=global_id, files=files, tripcode=tripcode)
226 230 else:
227 231 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
228 232
229 233 @staticmethod
230 234 def generate_response_pull():
231 235 response = et.Element(TAG_RESPONSE)
232 236
233 237 status = et.SubElement(response, TAG_STATUS)
234 238 status.text = STATUS_SUCCESS
235 239
236 240 models = et.SubElement(response, TAG_MODELS)
237 241
238 242 for post in Post.objects.prefetch_related('global_id').all():
239 243 tag_id = et.SubElement(models, TAG_ID)
240 244 post.global_id.to_xml_element(tag_id)
241 245
242 246 return et.tostring(response, ENCODING_UNICODE)
243 247
244 248 @staticmethod
245 249 def _verify_model(content_str, tag_model):
246 250 """
247 251 Verifies all signatures for a single model.
248 252 """
249 253
250 254 signatures = []
251 255
252 256 tag_signatures = tag_model.find(TAG_SIGNATURES)
253 257 for tag_signature in tag_signatures:
254 258 signature_type = tag_signature.get(ATTR_TYPE)
255 259 signature_value = tag_signature.get(ATTR_VALUE)
256 260 signature_key = tag_signature.get(ATTR_KEY)
257 261
258 262 signature = Signature(key_type=signature_type,
259 263 key=signature_key,
260 264 signature=signature_value)
261 265
262 266 if not KeyPair.objects.verify(signature, content_str):
263 267 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
264 268
265 269 signatures.append(signature)
266 270
267 271 return signatures
268 272
269 273 @staticmethod
270 274 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
271 275 if tag_attachments is not None:
272 276 mimetype = get_file_mimetype(file)
273 277 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
274 278 attachment.set(ATTR_MIMETYPE, mimetype)
275 279 attachment.set(ATTR_ID_TYPE, ID_TYPE_MD5)
276 280 attachment.text = hash
277 281
278 282 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
279 283 attachment_ref.set(ATTR_REF, hash)
280 284 attachment_ref.set(ATTR_URL, url)
General Comments 0
You need to be logged in to leave comments. Login now