diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index 1c324736..fab3707c 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -7,7 +7,7 @@ from wtforms.fields import ( HiddenField, ) from wtforms.fields.html5 import DateField -from wtforms.validators import Required, Length, NumberRange, ValidationError +from wtforms.validators import Required, Length, NumberRange, ValidationError, Regexp from flask_wtf import FlaskForm from numbers import Number @@ -15,6 +15,7 @@ from .data import JEDI_CLIN_TYPES from .fields import SelectField from .forms import BaseForm, remove_empty_string from atst.utils.localization import translate +from .validators import REGEX_ALPHA_NUMERIC from flask import current_app as app MAX_CLIN_AMOUNT = 1000000000 @@ -116,7 +117,10 @@ class AttachmentForm(BaseForm): filename = HiddenField( id="attachment_filename", validators=[ - Length(max=100, message=translate("forms.attachment.filename.length_error")) + Length( + max=100, message=translate("forms.attachment.filename.length_error") + ), + Regexp(regex=REGEX_ALPHA_NUMERIC), ], ) object_name = HiddenField( @@ -124,7 +128,8 @@ class AttachmentForm(BaseForm): validators=[ Length( max=40, message=translate("forms.attachment.object_name.length_error") - ) + ), + Regexp(regex=REGEX_ALPHA_NUMERIC), ], ) accept = ".pdf,application/pdf" diff --git a/atst/forms/validators.py b/atst/forms/validators.py index a16bf4f5..ebf29a2a 100644 --- a/atst/forms/validators.py +++ b/atst/forms/validators.py @@ -8,6 +8,9 @@ import pendulum from atst.utils.localization import translate +REGEX_ALPHA_NUMERIC = "^[A-Za-z0-9\-_ \.]*$" + + def DateRange(lower_bound=None, upper_bound=None, message=None): def _date_range(form, field): if field.data is None: diff --git a/js/components/__tests__/upload_input.test.js b/js/components/__tests__/upload_input.test.js index 43ae48a8..5d613b14 100644 --- a/js/components/__tests__/upload_input.test.js +++ b/js/components/__tests__/upload_input.test.js @@ -70,7 +70,7 @@ describe('UploadInput Test', () => { }) const component = wrapper.find(uploadinput) - const event = { target: { value: '', files: [{ name: '' }] } } + const event = { target: { value: '', files: [{ name: 'sample.pdf' }] } } component.setMethods({ getUploader: async () => new MockUploader('token', 'objectName'), diff --git a/js/components/upload_input.js b/js/components/upload_input.js index cdd9b15e..d7c90bf5 100644 --- a/js/components/upload_input.js +++ b/js/components/upload_input.js @@ -1,5 +1,6 @@ import { buildUploader } from '../lib/upload' import { emitFieldChange } from '../lib/emitters' +import inputValidations from '../lib/input_validations' export default { name: 'uploadinput', @@ -28,6 +29,7 @@ export default { changed: false, uploadError: false, sizeError: false, + filenameError: false, downloadLink: '', } }, @@ -50,6 +52,10 @@ export default { this.sizeError = true return } + if (!this.validateFileName(file.name)) { + this.filenameError = true + return + } const uploader = await this.getUploader() const response = await uploader.upload(file) @@ -71,6 +77,10 @@ export default { this.uploadError = true } }, + validateFileName: function(name) { + const regex = inputValidations.restrictedFileName.match + return regex.test(name) + }, removeAttachment: function(e) { e.preventDefault() this.attachment = null @@ -118,7 +128,8 @@ export default { return ( (!this.changed && this.initialErrors) || this.uploadError || - this.sizeError + this.sizeError || + this.filenameError ) }, valid: function() { diff --git a/js/lib/input_validations.js b/js/lib/input_validations.js index af74f211..80c16a7e 100644 --- a/js/lib/input_validations.js +++ b/js/lib/input_validations.js @@ -104,4 +104,11 @@ export default { unmask: ['(', ')', '-', ' '], validationError: 'Please enter a 10-digit phone number', }, + restrictedFileName: { + mask: false, + match: /^[A-Za-z0-9\-_ \.]+$/, + unmask: [], + validationError: + 'File names can only contain the characters A-Z, 0-9, space, hyphen, underscore, and period.', + }, } diff --git a/templates/components/upload_input.html b/templates/components/upload_input.html index e9183f9c..c86b6ab6 100644 --- a/templates/components/upload_input.html +++ b/templates/components/upload_input.html @@ -49,6 +49,9 @@ + {% for error, error_messages in field.errors.items() %} {{error_messages[0]}} {% endfor %} diff --git a/translations.yaml b/translations.yaml index 310a88ba..99e2d3b0 100644 --- a/translations.yaml +++ b/translations.yaml @@ -292,6 +292,7 @@ forms: task_order: upload_error: There was an error uploading your file. Please try again. If you encounter repeated problems uploading this file, please contact CCPO. size_error: The file you have selected is too large. Please choose a file no larger than 64MB. + filename_error: File names can only contain the characters A-Z, 0-9, space, hyphen, underscore, and period. defense_component_label: Select DoD component(s) funding your Portfolio file_format_not_allowed: Only PDF or PNG files can be uploaded. number_description: Task order number (13 digits)