|
1 | 1 | from functools import wraps |
| 2 | +import re |
2 | 3 | import json |
| 4 | +import bleach |
3 | 5 | import markdown2 |
4 | 6 | import pycmarkgfm |
5 | 7 | from allauth.account.views import LoginView as AllAuthLoginView |
@@ -183,27 +185,63 @@ def find_articles(request): |
183 | 185 | form = QueryIndexForm(data=None, datatypes=datatypes) |
184 | 186 | return TemplateResponse(request, "core/index.html", {'form': form, 'datatypes': datatypes}) |
185 | 187 |
|
| 188 | +def break_url(url_text): |
| 189 | + """ |
| 190 | + Break down URLs into segments to avoid linkification |
| 191 | + and then wrap them in double quotes. |
| 192 | +
|
| 193 | + Examples: |
| 194 | + "http://example.com" -> "http:// example .com" |
| 195 | + "example.com" -> "example .com" |
| 196 | + |
| 197 | + The pattern captures: |
| 198 | + 1. Optional protocol (http:// or https://) |
| 199 | + 2. Followed by a domain name (including letters, digits, dots, hyphens) |
| 200 | + 3. A period, and one of the listed TLDs |
| 201 | + 4. Followed by zero or more non-space characters (e.g., /path) |
| 202 | + """ |
| 203 | + pattern = r'((https?:\/\/)?[A-Za-z0-9.-]+\.(?:com|net|org|io|gov|edu)\b\S*)' |
| 204 | + |
| 205 | + def replacer(match): |
| 206 | + original_url = match.group(1) |
| 207 | + # Insert a space after protocol (if present) |
| 208 | + spaced_protocol = re.sub(r'(https?:\/\/)', r'\1 ', original_url) |
| 209 | + # Insert a space before the TLD |
| 210 | + space_tld = re.sub(r'\.(com|net|org|io|gov|edu)', r' .\1', spaced_protocol) |
| 211 | + # Wrap result in double quotes |
| 212 | + return f'"{space_tld}"' |
| 213 | + |
| 214 | + # Replace all occurences of the pattern with the split/wrapped version |
| 215 | + return re.sub(pattern, replacer, url_text) |
| 216 | + |
186 | 217 |
|
187 | 218 | @ux_requires_post |
188 | 219 | def article_feedback(request, article_name): |
189 | 220 | form = ArticleFeedbackForm(data=request.POST) |
190 | | - Feedback.objects.create(article=Article.objects.get(name=article_name), |
191 | | - reaction=form.data['reaction'], |
192 | | - explanation=form.data['explanation']) |
193 | | - message = "*New article feedback for \"" + article_name + "\"*:\n\nReaction: " + form.data['reaction'] + "\n\n" + form.data['explanation'] |
194 | | - notify(message) |
| 221 | + if form.is_valid(): |
| 222 | + # Sanitize for database storage |
| 223 | + sanitized_explanation = bleach.clean(form.cleaned_data['explanation'], tags=[], attributes={}, strip=True) |
| 224 | + Feedback.objects.create(article=Article.objects.get(name=article_name), |
| 225 | + reaction=form.cleaned_data['reaction'], |
| 226 | + explanation=sanitized_explanation) |
| 227 | + message = f"*Article feedback for \"{article_name}\"*:\n\nReaction: {form.cleaned_data['reaction']}" |
| 228 | + notify(message) |
195 | 229 | referer = request.META.get('HTTP_REFERER', '/') |
196 | 230 | return TemplateResponse(request, "core/thankyou.html", {'referer': referer}) |
197 | 231 |
|
198 | 232 |
|
199 | 233 | @ux_requires_post |
200 | 234 | def usecase_feedback(request): |
201 | | - feedback = UseCaseFeedbackForm(data=request.POST); |
| 235 | + feedback = UseCaseFeedbackForm(data=request.POST) |
202 | 236 | if feedback.is_valid(): |
| 237 | + # Sanitize for database storage |
| 238 | + sanitized_explanation = bleach.clean(feedback.cleaned_data['explanation'], tags=[], attributes={}, strip=True) |
| 239 | + feedback.instance.explanation = sanitized_explanation |
203 | 240 | feedback.save() |
204 | | - message = "*New use case feedback:*\n\n" + feedback.data['explanation'] |
| 241 | + # Break URLs into segments and wrap in double quotes |
| 242 | + safe_for_slack = break_url(sanitized_explanation) |
| 243 | + message = f"*New use case feedback:*\n\n{safe_for_slack}" |
205 | 244 | notify(message) |
206 | | - |
207 | 245 | referer = request.META.get('HTTP_REFERER', '/') |
208 | 246 | return TemplateResponse(request, "core/thankyou.html", {'referer': referer}) |
209 | 247 |
|
|
0 commit comments