Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: URL handling and validation logic #42

Merged
merged 1 commit into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 0 additions & 43 deletions .run/Tests in 'n2rss.test'.run.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package fr.nicopico.n2rss.controller.home
import fr.nicopico.n2rss.config.N2RssProperties
import fr.nicopico.n2rss.service.NewsletterService
import fr.nicopico.n2rss.service.ReCaptchaService
import fr.nicopico.n2rss.utils.Url
import fr.nicopico.n2rss.utils.url.Url
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.ConstraintViolationException
import jakarta.validation.constraints.NotEmpty
Expand Down Expand Up @@ -79,7 +79,10 @@ class HomeController(
} else true

return if (isCaptchaValid) {
newsletterService.saveRequest(URL(newsletterUrl))
val url = with(newsletterUrl) {
if (contains("://")) newsletterUrl else "https://$newsletterUrl"
}.let { URL(it) }
newsletterService.saveRequest(url)
ResponseEntity.ok().build()
} else {
ResponseEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package fr.nicopico.n2rss.mail.newsletter
import fr.nicopico.n2rss.models.Article
import fr.nicopico.n2rss.models.Email
import fr.nicopico.n2rss.models.Newsletter
import fr.nicopico.n2rss.utils.toUrlOrNull
import fr.nicopico.n2rss.utils.url.toUrlOrNull
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import fr.nicopico.n2rss.mail.newsletter.jsoup.process
import fr.nicopico.n2rss.models.Article
import fr.nicopico.n2rss.models.Email
import fr.nicopico.n2rss.models.Newsletter
import fr.nicopico.n2rss.utils.toUrlOrNull
import fr.nicopico.n2rss.utils.url.toUrlOrNull
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.safety.Safelist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package fr.nicopico.n2rss.mail.newsletter
import fr.nicopico.n2rss.models.Article
import fr.nicopico.n2rss.models.Email
import fr.nicopico.n2rss.models.Newsletter
import fr.nicopico.n2rss.utils.toUrlOrNull
import fr.nicopico.n2rss.utils.url.toUrlOrNull
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package fr.nicopico.n2rss.utils
package fr.nicopico.n2rss.utils.url

import jakarta.validation.Constraint
import jakarta.validation.ConstraintValidator
import jakarta.validation.ConstraintValidatorContext
import jakarta.validation.Payload
import kotlin.reflect.KClass

Expand All @@ -39,19 +37,3 @@ annotation class Url(
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)

private val urlRegex = Regex(
"^((http|https):\\/\\/)?(www\\.)?([A-z]+)\\.([A-z]{2,})"
)

internal class UrlValidator : ConstraintValidator<Url, String> {
override fun isValid(
url: String?,
context: ConstraintValidatorContext?
): Boolean {
if (url.isNullOrBlank()) {
return true
}
return urlRegex.matches(url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package fr.nicopico.n2rss.utils
package fr.nicopico.n2rss.utils.url

import java.net.MalformedURLException
import java.net.URL

fun String.toUrlOrNull(): URL? = try {
URL(this)
URL(this)
} catch (_: MalformedURLException) {
null
}
38 changes: 38 additions & 0 deletions src/main/kotlin/fr/nicopico/n2rss/utils/url/UrlValidator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Nicolas PICON
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package fr.nicopico.n2rss.utils.url

import jakarta.validation.ConstraintValidator
import jakarta.validation.ConstraintValidatorContext

internal class UrlValidator : ConstraintValidator<Url, String> {
override fun isValid(
url: String?,
context: ConstraintValidatorContext?
): Boolean {
if (url.isNullOrBlank()) {
return true
}
return urlRegex.matches(url)
}

companion object {
@Suppress("RegExpRedundantEscape")
private val urlRegex = Regex("^((http|https):\\/\\/)?[A-Za-z0-9.\\-_]+(/.*)?")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
package fr.nicopico.n2rss.controller

import fr.nicopico.n2rss.utils.UrlValidator
import fr.nicopico.n2rss.utils.url.UrlValidator
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest
Expand Down Expand Up @@ -47,10 +47,15 @@ class UrlValidatorTest {
// Urls without scheme can be valid
Arguments.of("www.google.com", true),
Arguments.of("nirvana.com", true),
// Complete url are valid
Arguments.of(
"https://kotlinweekly.us12.list-manage.com/track/click?u=21&id=42",
true
),
// Invalid values
Arguments.of("mail://[email protected]", false),
Arguments.of("[email protected]", false),
Arguments.of("Invalid_URL", false),
Arguments.of("Some random text", false),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,15 @@
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

package fr.nicopico.n2rss.controller.home

import fr.nicopico.n2rss.config.N2RssProperties
import fr.nicopico.n2rss.models.Newsletter
import fr.nicopico.n2rss.models.NewsletterInfo
import fr.nicopico.n2rss.service.NewsletterService
import fr.nicopico.n2rss.service.ReCaptchaService
import io.kotest.assertions.throwables.shouldThrowAny
import io.kotest.matchers.collections.shouldContainOnly
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.beOfType
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
Expand All @@ -42,7 +38,6 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.http.HttpStatus
import org.springframework.ui.Model
import java.net.MalformedURLException
import java.net.URL

class HomeControllerTest {
Expand Down Expand Up @@ -199,26 +194,22 @@ class HomeControllerTest {
}

@Test
fun `sendRequest should fail if newsletterUrl is not a valid url`() {
fun `sendRequest should use assume https protocol if none are passed`() {
// GIVEN
val newsletterUrl = "something"
val newsletterUrl = "www.google.com"
val captchaResponse = "captchaResponse"
val captchaSecretKey = "captchaSecretKey"

// SETUP
every { reCaptchaProperties.enabled } returns true
every { reCaptchaProperties.secretKey } returns captchaSecretKey
every { reCaptchaProperties.enabled } returns false
every { newsletterService.saveRequest(any()) } just Runs
every { reCaptchaService.isCaptchaValid(any(), any()) } returns true

// WHEN
val error = shouldThrowAny {
homeController.requestNewsletter(newsletterUrl, captchaResponse)
}
homeController.requestNewsletter(newsletterUrl, captchaResponse)

// THEN
verify(exactly = 0) { newsletterService.saveRequest(any()) }
error should beOfType<MalformedURLException>()
val slotUrl = slot<URL>()
verify { newsletterService.saveRequest(capture(slotUrl)) }
slotUrl.captured.toString() shouldBe "https://www.google.com"
}
}
}
3 changes: 2 additions & 1 deletion src/test/kotlin/fr/nicopico/n2rss/utils/UrlExtKtTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package fr.nicopico.n2rss.utils

import fr.nicopico.n2rss.utils.url.toUrlOrNull
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
Expand Down Expand Up @@ -45,7 +46,7 @@ class UrlExtKtTest {
@Test
fun `Invalid url should return null`() {
// GIVEN
val urlString = "invalid_url"
val urlString = ""

// WHEN
val result: URL? = urlString.toUrlOrNull()
Expand Down
Loading