Skip to content

Commit da893da

Browse files
committed
Merged in support of xml-source only drafts submissions from personal/henrik/submitxml/.
- Legacy-Id: 9859
2 parents c8ac65a + d5f3691 commit da893da

16 files changed

Lines changed: 399 additions & 195 deletions

ietf/settings.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,18 +432,23 @@ def skip_unreadable_post(record):
432432
IDSUBMIT_DEFAULT_CUTOFF_TIME_UTC = datetime.timedelta(hours=23, minutes=59, seconds=59)
433433
IDSUBMIT_DEFAULT_CUTOFF_WARNING_DAYS = datetime.timedelta(days=21)
434434

435-
MEETING_MATERIALS_SUBMISSION_START_DAYS = -90
436-
MEETING_MATERIALS_SUBMISSION_CUTOFF_DAYS = 26
437-
MEETING_MATERIALS_SUBMISSION_CORRECTION_DAYS = 50
438-
439-
INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
440-
441435
IDSUBMIT_REPOSITORY_PATH = INTERNET_DRAFT_PATH
442436
IDSUBMIT_STAGING_PATH = '/a/www/www6s/staging/'
443437
IDSUBMIT_STAGING_URL = '//www.ietf.org/staging/'
444438
IDSUBMIT_IDNITS_BINARY = '/a/www/ietf-datatracker/scripts/idnits'
445439

446-
IDSUBMIT_MAX_PLAIN_DRAFT_SIZE = 6291456 # Max size of the txt draft in bytes
440+
IDSUBMIT_FILE_TYPES = (
441+
'txt',
442+
'xml',
443+
'pdf',
444+
'ps',
445+
)
446+
IDSUBMIT_MAX_DRAFT_SIZE = {
447+
'txt': 6*1024*1024, # Max size of txt draft file in bytes
448+
'xml': 10*1024*1024, # Max size of xml draft file in bytes
449+
'pdf': 10*1024*1024,
450+
'ps' : 10*1024*1024,
451+
}
447452

448453
IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME = 20
449454
IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME_SIZE = 50 # in MB
@@ -454,6 +459,14 @@ def skip_unreadable_post(record):
454459
IDSUBMIT_MAX_DAILY_SUBMISSIONS = 1000
455460
IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE = 2000 # in MB
456461

462+
XML_LIBRARY = "/www/tools.ietf.org/tools/xml2rfc/web/public/rfc/"
463+
464+
MEETING_MATERIALS_SUBMISSION_START_DAYS = -90
465+
MEETING_MATERIALS_SUBMISSION_CUTOFF_DAYS = 26
466+
MEETING_MATERIALS_SUBMISSION_CORRECTION_DAYS = 50
467+
468+
INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
469+
457470
DOT_BINARY = '/usr/bin/dot'
458471
UNFLATTEN_BINARY= '/usr/bin/unflatten'
459472
PS2PDF_BINARY = '/usr/bin/ps2pdf'

ietf/submit/forms.py

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
2+
import re
23
import datetime
34
import pytz
5+
import xml2rfc
6+
import tempfile
47

58
from django import forms
69
from django.conf import settings
@@ -23,14 +26,14 @@
2326
from ietf.utils.draft import Draft
2427

2528

26-
class UploadForm(forms.Form):
27-
txt = forms.FileField(label=u'.txt format', required=True)
29+
class SubmissionUploadForm(forms.Form):
30+
txt = forms.FileField(label=u'.txt format', required=False)
2831
xml = forms.FileField(label=u'.xml format', required=False)
2932
pdf = forms.FileField(label=u'.pdf format', required=False)
30-
ps = forms.FileField(label=u'.ps format', required=False)
33+
ps = forms.FileField(label=u'.ps format', required=False)
3134

3235
def __init__(self, request, *args, **kwargs):
33-
super(UploadForm, self).__init__(*args, **kwargs)
36+
super(SubmissionUploadForm, self).__init__(*args, **kwargs)
3437

3538
self.remote_ip = request.META.get('REMOTE_ADDR', None)
3639

@@ -41,7 +44,13 @@ def __init__(self, request, *args, **kwargs):
4144
self.set_cutoff_warnings()
4245

4346
self.group = None
47+
self.filename = None
48+
self.revision = None
49+
self.title = None
50+
self.abstract = None
51+
self.authors = []
4452
self.parsed_draft = None
53+
self.file_types = []
4554

4655
def set_cutoff_warnings(self):
4756
now = datetime.datetime.now(pytz.utc)
@@ -93,15 +102,14 @@ def clean_file(self, field_name, parser_class):
93102

94103
return f
95104

96-
97105
def clean_txt(self):
98106
return self.clean_file("txt", PlainParser)
99107

100108
def clean_pdf(self):
101109
return self.clean_file("pdf", PDFParser)
102110

103111
def clean_ps(self):
104-
return self.clean_file("ps", PSParser)
112+
return self.clean_file("ps", PSParser)
105113

106114
def clean_xml(self):
107115
return self.clean_file("xml", XMLParser)
@@ -116,37 +124,100 @@ def clean(self):
116124
if not os.path.exists(getattr(settings, s)):
117125
raise forms.ValidationError('%s defined in settings.py does not exist' % s)
118126

127+
for ext in ['txt', 'pdf', 'xml', 'ps']:
128+
f = self.cleaned_data.get(ext, None)
129+
if not f:
130+
continue
131+
self.file_types.append('.%s' % ext)
132+
if not ('.txt' in self.file_types or '.xml' in self.file_types):
133+
raise forms.ValidationError('You must submit either a .txt or an .xml file; didn\'t find either.')
134+
135+
#debug.show('self.cleaned_data["xml"]')
136+
if self.cleaned_data.get('xml'):
137+
#if not self.cleaned_data.get('txt'):
138+
xml_file = self.cleaned_data.get('xml')
139+
tfh, tfn = tempfile.mkstemp(suffix='.xml')
140+
try:
141+
# We need to write the xml file to disk in order to hand it
142+
# over to the xml parser. XXX FIXME: investigate updating
143+
# xml2rfc to be able to work with file handles to in-memory
144+
# files.
145+
with open(tfn, 'wb+') as tf:
146+
for chunk in xml_file.chunks():
147+
tf.write(chunk)
148+
os.environ["XML_LIBRARY"] = settings.XML_LIBRARY
149+
parser = xml2rfc.XmlRfcParser(tfn, quiet=True)
150+
self.xmltree = parser.parse()
151+
ok, errors = self.xmltree.validate()
152+
if not ok:
153+
raise forms.ValidationError(errors)
154+
self.xmlroot = self.xmltree.getroot()
155+
draftname = self.xmlroot.attrib.get('docName')
156+
revmatch = re.search("-[0-9][0-9]$", draftname)
157+
if revmatch:
158+
self.revision = draftname[-2:]
159+
self.filename = draftname[:-3]
160+
else:
161+
self.revision = None
162+
self.filename = draftname
163+
self.title = self.xmlroot.find('front/title').text
164+
self.abstract = self.xmlroot.find('front/abstract').text
165+
self.author_list = []
166+
author_info = self.xmlroot.findall('front/author')
167+
for author in author_info:
168+
author_dict = dict(
169+
company = author.find('organization').text,
170+
last_name = author.attrib.get('surname'),
171+
full_name = author.attrib.get('fullname'),
172+
email = author.find('address/email').text,
173+
)
174+
self.author_list.append(author_dict)
175+
line = "%(full_name)s <%(email)s>" % author_dict
176+
self.authors.append(line)
177+
except Exception as e:
178+
raise forms.ValidationError("Exception: %s" % e)
179+
finally:
180+
os.close(tfh)
181+
os.unlink(tfn)
182+
119183
if self.cleaned_data.get('txt'):
120184
# try to parse it
121185
txt_file = self.cleaned_data['txt']
122186
txt_file.seek(0)
123187
self.parsed_draft = Draft(txt_file.read(), txt_file.name)
188+
self.filename = self.parsed_draft.filename
189+
self.revision = self.parsed_draft.revision
190+
self.title = self.parsed_draft.get_title()
124191
txt_file.seek(0)
125192

126-
if not self.parsed_draft.filename:
127-
raise forms.ValidationError("Draft parser could not extract a valid draft name from the .txt file")
193+
if not self.filename:
194+
raise forms.ValidationError("Draft parser could not extract a valid draft name from the upload")
195+
196+
if not self.revision:
197+
raise forms.ValidationError("Draft parser could not extract a valid draft revision from the upload")
128198

129-
if not self.parsed_draft.get_title():
130-
raise forms.ValidationError("Draft parser could not extract a valid title from the .txt file")
199+
if not self.title:
200+
raise forms.ValidationError("Draft parser could not extract a valid title from the upload")
131201

202+
if self.cleaned_data.get('txt') or self.cleaned_data.get('xml'):
132203
# check group
133204
self.group = self.deduce_group()
134205

135206
# check existing
136-
existing = Submission.objects.filter(name=self.parsed_draft.filename, rev=self.parsed_draft.revision).exclude(state__in=("posted", "cancel"))
207+
existing = Submission.objects.filter(name=self.filename, rev=self.revision).exclude(state__in=("posted", "cancel"))
137208
if existing:
138209
raise forms.ValidationError(mark_safe('Submission with same name and revision is currently being processed. <a href="%s">Check the status here.</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
139210

140211
# cut-off
141-
if self.parsed_draft.revision == '00' and self.in_first_cut_off:
212+
if self.revision == '00' and self.in_first_cut_off:
142213
raise forms.ValidationError(mark_safe(self.cutoff_warning))
143214

144215
# check thresholds
145216
today = datetime.date.today()
146217

147218
self.check_submissions_tresholds(
148-
"for the draft %s" % self.parsed_draft.filename,
149-
dict(name=self.parsed_draft.filename, rev=self.parsed_draft.revision, submission_date=today),
219+
"for the draft %s" % self.filename,
220+
dict(name=self.filename, rev=self.revision, submission_date=today),
150221
settings.IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME, settings.IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME_SIZE,
151222
)
152223
self.check_submissions_tresholds(
@@ -166,19 +237,19 @@ def clean(self):
166237
settings.IDSUBMIT_MAX_DAILY_SUBMISSIONS, settings.IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE,
167238
)
168239

169-
return super(UploadForm, self).clean()
240+
return super(SubmissionUploadForm, self).clean()
170241

171242
def check_submissions_tresholds(self, which, filter_kwargs, max_amount, max_size):
172243
submissions = Submission.objects.filter(**filter_kwargs)
173244

174245
if len(submissions) > max_amount:
175246
raise forms.ValidationError("Max submissions %s has been reached for today (maximum is %s submissions)." % (which, max_amount))
176-
if sum(s.file_size for s in submissions) > max_size * 1024 * 1024:
247+
if sum(s.file_size for s in submissions if s.file_size) > max_size * 1024 * 1024:
177248
raise forms.ValidationError("Max uploaded amount %s has been reached for today (maximum is %s MB)." % (which, max_size))
178249

179250
def deduce_group(self):
180251
"""Figure out group from name or previously submitted draft, returns None if individual."""
181-
name = self.parsed_draft.filename
252+
name = self.filename
182253
existing_draft = Document.objects.filter(name=name, type="draft")
183254
if existing_draft:
184255
group = existing_draft[0].group

ietf/submit/parsers/base.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1+
import os
12
import re
3+
import magic
4+
import datetime
5+
import debug # pyflakes:ignore
6+
7+
from django.conf import settings
8+
from django.template.defaultfilters import filesizeformat
29

310
class MetaData(object):
411
rev = None
@@ -42,6 +49,8 @@ def __init__(self, fd):
4249
# no other file parsing is recommended
4350
def critical_parse(self):
4451
self.parse_invalid_chars_in_filename()
52+
self.parse_max_size();
53+
self.parsed_info.metadata.submission_date = datetime.date.today()
4554
return self.parsed_info
4655

4756
def parse_invalid_chars_in_filename(self):
@@ -50,3 +59,22 @@ def parse_invalid_chars_in_filename(self):
5059
chars = regexp.findall(name)
5160
if chars:
5261
self.parsed_info.add_error('Invalid characters were found in the name of the file which was just submitted: %s' % ', '.join(set(chars)))
62+
63+
def parse_max_size(self):
64+
__, ext = os.path.splitext(self.fd.name)
65+
ext = ext.lstrip('.')
66+
max_size = settings.IDSUBMIT_MAX_DRAFT_SIZE[ext]
67+
if self.fd.size > max_size:
68+
self.parsed_info.add_error('File size is larger than the permitted maximum of %s' % filesizeformat(max_size))
69+
self.parsed_info.metadata.file_size = self.fd.size
70+
71+
def parse_filename_extension(self, ext):
72+
if not self.fd.name.lower().endswith('.'+ext):
73+
self.parsed_info.add_error('Expected the %s file to have extension ".%s", found "%s"' % (ext.upper(), ext, self.fd.name))
74+
75+
def parse_file_type(self, ext, expected):
76+
self.fd.file.seek(0)
77+
content = self.fd.file.read(4096)
78+
mimetype = magic.from_buffer(content, mime=True)
79+
if not mimetype == expected:
80+
self.parsed_info.add_error('Expected an %s file of type "%s", found one of type "%s"' % (expected, mimetype))

ietf/submit/parsers/pdf_parser.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ class PDFParser(FileParser):
77
# no other file parsing is recommended
88
def critical_parse(self):
99
super(PDFParser, self).critical_parse()
10-
self.parse_filename_extension()
10+
self.parse_filename_extension('pdf')
11+
self.parse_file_type('pdf', 'application/pdf')
1112
return self.parsed_info
12-
13-
def parse_filename_extension(self):
14-
if not self.fd.name.endswith('.pdf'):
15-
self.parsed_info.add_error('Format of this document must be PDF')

ietf/submit/parsers/plain_parser.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import datetime
21
import re
32

4-
from django.conf import settings
5-
from django.template.defaultfilters import filesizeformat
63
from ietf.submit.parsers.base import FileParser
74

85

@@ -15,17 +12,12 @@ def __init__(self, fd):
1512
# no other file parsing is recommended
1613
def critical_parse(self):
1714
super(PlainParser, self).critical_parse()
18-
self.parse_max_size()
15+
self.parse_filename_extension('txt')
16+
self.parse_file_type('txt', 'text/plain')
1917
self.parse_file_charset()
2018
self.parse_name()
2119
return self.parsed_info
2220

23-
def parse_max_size(self):
24-
if self.fd.size > settings.IDSUBMIT_MAX_PLAIN_DRAFT_SIZE:
25-
self.parsed_info.add_error('File size is larger than %s' % filesizeformat(settings.IDSUBMIT_MAX_PLAIN_DRAFT_SIZE))
26-
self.parsed_info.metadata.file_size = self.fd.size
27-
self.parsed_info.metadata.submission_date = datetime.date.today()
28-
2921
def parse_file_charset(self):
3022
import magic
3123
self.fd.file.seek(0)

ietf/submit/parsers/ps_parser.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ class PSParser(FileParser):
77
# no other file parsing is recommended
88
def critical_parse(self):
99
super(PSParser, self).critical_parse()
10-
self.parse_filename_extension()
10+
self.parse_filename_extension('ps')
11+
self.parse_file_type('ps', 'application/postscript')
1112
return self.parsed_info
12-
13-
def parse_filename_extension(self):
14-
if not self.fd.name.endswith('.ps'):
15-
self.parsed_info.add_error('Format of this document must be PS')

ietf/submit/parsers/xml_parser.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ class XMLParser(FileParser):
77
# no other file parsing is recommended
88
def critical_parse(self):
99
super(XMLParser, self).critical_parse()
10-
self.parse_filename_extension()
10+
self.parse_filename_extension('xml')
11+
self.parse_file_type('xml', 'application/xml')
1112
return self.parsed_info
12-
13-
def parse_filename_extension(self):
14-
if not self.fd.name.endswith('.xml'):
15-
self.parsed_info.add_error('Format of this document must be XML')
13+

ietf/submit/test_submission.pdf

22 Bytes
Binary file not shown.

ietf/submit/test_submission.ps

35 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)