Add URL validation pattern and MultiUrlField validator (#1901)

* Add single and multi URL form field validation

* Add MultiUrlField

* Add unit test of validate_url

* Add  comment about the url pattern.
This commit is contained in:
Daniel LaLiberte 2022-05-27 12:02:49 -04:00 коммит произвёл GitHub
Родитель 831042c266
Коммит aa69b9cf87
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 90 добавлений и 33 удалений

Просмотреть файл

@ -14,8 +14,12 @@
# limitations under the License.
import logging
import re
from django import forms
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.forms.widgets import Textarea
# from google.appengine.api import users
from framework import users
@ -38,6 +42,31 @@ class MultiEmailField(forms.Field):
for email in value:
validate_email(email.strip())
def validate_url(value):
"""Check that the value matches the single URL regex."""
if (re.match(URL_REGEX, value)):
pass
else:
raise ValidationError('Invalid URL', code=None, params={'value': value})
class MultiUrlField(forms.Field):
def to_python(self, value):
"""Normalize data to a list of strings."""
# Return an empty list if no input was given.
if not value:
return []
return value.split('\n')
def validate(self, value):
"""Check if value consists only of valid urls."""
# Use the parent's handling of required fields, etc.
super(MultiUrlField, self).validate(value)
for url in value:
validate_url(url.strip())
SHIPPED_HELP_TXT = (
'First milestone to ship with this status. Applies to: Enabled by '
'default, Browser Intervention, Deprecated and Removed.')
@ -71,6 +100,25 @@ MULTI_EMAIL_FIELD_ATTRS = {
'pattern': EMAIL_ADDRESSES_REGEX
}
# From https://rodneyrehm.de/t/url-regex.html#imme_emosol+ht-%26f-tp%28s%29
# Using imme_emosol but without ftp, torrent, image, and irc
URL_REGEX = '[ ]*(https?)://(-\.)?([^\s/?\.#-]+\.?)+(/[^\s]*)?[ ]*'
# Multiple URLs, one per line
MULTI_URL_REGEX = URL_REGEX + '(\\n' + URL_REGEX + ')*'
URL_FIELD_ATTRS = {
'title': 'Enter a full URL https://...',
'placeholder': 'https://...',
'pattern': URL_REGEX
}
MULTI_URL_FIELD_ATTRS = {
'title': 'Enter one or more full URLs, one per line:\nhttps://...\nhttps://...',
'placeholder': 'https://...\nhttps://...',
'rows': 4, 'cols': 50, 'maxlength': 5000
# 'pattern': MULTI_URL_REGEX, # pattern is not yet used with textarea.
}
# We define all form fields here so that they can be include in one or more
# stage-specific fields without repeating the details and help text.
ALL_FIELDS = {
@ -152,11 +200,9 @@ ALL_FIELDS = {
'">Removal guidelines</a>.'
)),
'doc_links': forms.CharField(
'doc_links': MultiUrlField(
label='Doc link(s)', required=False,
widget=forms.Textarea(
attrs={'rows': 4, 'cols': 50, 'maxlength': 500,
'placeholder': 'https://\nhttps://'}),
widget=forms.Textarea(attrs=MULTI_URL_FIELD_ATTRS),
help_text=('Links to design doc(s) (one URL per line), if and when '
'available. [This is not required to send out an Intent '
'to Prototype. Please update the intent thread with the '
@ -192,7 +238,7 @@ ALL_FIELDS = {
'spec_link': forms.URLField(
required=False, label='Spec link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('Link to spec, if and when available. Please update the '
'chromestatus.com entry and the intent thread(s) with the '
'spec link when available.')),
@ -211,11 +257,9 @@ ALL_FIELDS = {
'spec mentors</a> are available to help you improve your '
'feature spec.')),
'explainer_links': forms.CharField(
'explainer_links': MultiUrlField(
label='Explainer link(s)', required=False,
widget=forms.Textarea(
attrs={'rows': 4, 'cols': 50, 'maxlength': 500,
'placeholder': 'https://\nhttps://'}),
widget=forms.Textarea(attrs=MULTI_URL_FIELD_ATTRS),
help_text=('Link to explainer(s) (one URL per line). You should have '
'at least an explainer in hand and have shared it on a '
'public forum before sending an Intent to Prototype in '
@ -248,37 +292,37 @@ ALL_FIELDS = {
'intent_to_implement_url': forms.URLField(
required=False, label='Intent to Prototype link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('After you have started the "Intent to Prototype" '
' discussion thread, link to it here.')),
'intent_to_ship_url': forms.URLField(
required=False, label='Intent to Ship link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('After you have started the "Intent to Ship" discussion '
'thread, link to it here.')),
'ready_for_trial_url': forms.URLField(
required=False, label='Ready for Trial link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('After you have started the "Ready for Trial" discussion '
'thread, link to it here.')),
'intent_to_experiment_url': forms.URLField(
required=False, label='Intent to Experiment link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('After you have started the "Intent to Experiment" '
' discussion thread, link to it here.')),
'intent_to_extend_experiment_url': forms.URLField(
required=False, label='Intent to Extend Experiment link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('If this feature has an "Intent to Extend Experiment" '
' discussion thread, link to it here.')),
'r4dt_url': forms.URLField( # Sets intent_to_experiment_url in DB
required=False, label='Request for Deprecation Trial link',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=('After you have started the "Request for Deprecation Trial" '
'discussion thread, link to it here.')),
@ -316,7 +360,7 @@ ALL_FIELDS = {
'safari_views_link': forms.URLField(
required=False, label='',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text='Citation link.'),
'safari_views_notes': forms.CharField(
@ -335,7 +379,7 @@ ALL_FIELDS = {
'ff_views_link': forms.URLField(
required=False, label='',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text='Citation link.'),
'ff_views_notes': forms.CharField(
@ -355,7 +399,7 @@ ALL_FIELDS = {
'web_dev_views_link': forms.URLField(
required=False, label='',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text='Citation link.'),
'web_dev_views_notes': forms.CharField(
@ -508,16 +552,14 @@ ALL_FIELDS = {
'origin_trial_feedback_url': forms.URLField(
required=False, label='Origin trial feedback summary',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=
('If your feature was available as an origin trial, link to a summary '
'of usage and developer feedback. If not, leave this empty.')),
'anticipated_spec_changes': forms.CharField(
'anticipated_spec_changes': MultiUrlField(
required=False, label='Anticipated spec changes',
widget=forms.Textarea(
attrs={'rows': 4, 'cols': 50, 'maxlength': 500,
'placeholder': 'https://\nhttps://'}),
widget=forms.Textarea(attrs=MULTI_URL_FIELD_ATTRS),
help_text=
('Open questions about a feature may be a source of future web compat '
'or interop issues. Please list open issues (e.g. links to known '
@ -528,7 +570,7 @@ ALL_FIELDS = {
'finch_url': forms.URLField(
required=False, label='Finch experiment',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=
('If your feature will roll out gradually via a '
'<a href="go/finch" targe="_blank">Finch experiment</a>, '
@ -596,11 +638,9 @@ ALL_FIELDS = {
'https://bugs.chromium.org/p/chromium/issues/detail?id=695486">'
'example</a>).')),
'sample_links': forms.CharField(
'sample_links': MultiUrlField(
label='Samples links', required=False,
widget=forms.Textarea(
attrs={'cols': 50, 'maxlength': 500,
'placeholder': 'https://\nhttps://'}),
widget=forms.Textarea(attrs=MULTI_URL_FIELD_ATTRS),
help_text='Links to samples (one URL per line).'),
'non_oss_deps': forms.CharField(
@ -616,7 +656,7 @@ ALL_FIELDS = {
'bug_url': forms.URLField(
required=False, label='Tracking bug URL',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=
('Tracking bug url (https://bugs.chromium.org/...). This bug '
'should have "Type=Feature" set and be world readable. '
@ -626,7 +666,7 @@ ALL_FIELDS = {
# or a deep link that has some feature details filled in.
'launch_bug_url': forms.URLField(
required=False, label='Launch bug URL',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=(
'Launch bug url (https://bugs.chromium.org/...) to track launch '
'approvals. '
@ -637,7 +677,7 @@ ALL_FIELDS = {
'initial_public_proposal_url': forms.URLField(
required=False, label='Initial public proposal URL',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=(
'Link to the first public proposal to create this feature, e.g., '
'a WICG discourse post.')),
@ -694,7 +734,7 @@ ALL_FIELDS = {
'devtrial_instructions': forms.URLField(
required=False, label='DevTrial instructions',
widget=forms.URLInput(attrs={'placeholder': 'https://'}),
widget=forms.URLInput(attrs=URL_FIELD_ATTRS),
help_text=(
'Link to a HOWTO or FAQ describing how developers can get started '
'using this feature in a DevTrial. <a target="_blank" href="'

Просмотреть файл

@ -17,6 +17,8 @@ import unittest
from unittest import mock
from django.core.exceptions import ValidationError
from pages import guideforms
from internals import models
@ -71,4 +73,19 @@ class DisplayFieldsTest(unittest.TestCase):
for field_name in list(guideforms.ALL_FIELDS.keys()):
self.assertIn(
field_name, fields_seen,
msg='Field %r is missing in DISPLAY_FIELDS_IN_STAGES' % field_name)
msg='Field %r is missing in DISPLAY_FIELDS_IN_STAGES' % field_name)
def test_validate_url(self):
guideforms.validate_url('http://www.google.com')
guideforms.validate_url('https://www.google.com')
guideforms.validate_url('https://chromium.org')
with self.assertRaises(ValidationError):
# Disallow ftp URLs.
guideforms.validate_url('ftp://chromium.org')
with self.assertRaises(ValidationError):
# Disallow schema-only URLs.
guideforms.validate_url('http:')
with self.assertRaises(ValidationError):
# Disallow schema-less URLs.
guideforms.validate_url('www.google.com')