From 58094d8605be986350ed5d44f023a89611571c84 Mon Sep 17 00:00:00 2001 From: David Asunmo <22662897+davidasunmo@users.noreply.github.com.> Date: Sun, 8 Jun 2025 11:14:50 +0100 Subject: [PATCH 1/3] [SoundCloud] Validate http response code in SoundcloudParsingHelper --- .../extractor/downloader/Response.java | 20 +++++++++++ .../exceptions/HttpResponseException.java | 15 ++++++++ .../soundcloud/SoundcloudParsingHelper.java | 34 +++++++++++------- .../newpipe/extractor/utils/HttpUtils.java | 36 +++++++++++++++++++ 4 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java b/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java index ac792dc756..87c3577ef4 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java @@ -6,6 +6,9 @@ import java.util.List; import java.util.Map; +import org.schabi.newpipe.extractor.exceptions.HttpResponseException; +import org.schabi.newpipe.extractor.utils.HttpUtils; + /** * A Data class used to hold the results from requests made by the Downloader implementation. */ @@ -80,4 +83,21 @@ public String getHeader(final String name) { return null; } + // CHECKSTYLE:OFF + /** + * Helper function simply to make it easier to validate response code inline + * before getting the code/body/latestUrl/etc. + * Validates the response codes for the given {@link Response}, and throws a {@link HttpResponseException} if the code is invalid + * @see HttpUtils#validateResponseCode(Response, int...) + * @param validResponseCodes Expected valid response codes + * @return {@link this} response + * @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes}, + * or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error. + */ + // CHECKSTYLE:ON + public Response validateResponseCode(final int... validResponseCodes) + throws HttpResponseException { + HttpUtils.validateResponseCode(this, validResponseCodes); + return this; + } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java b/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java new file mode 100644 index 0000000000..c07850a9d3 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java @@ -0,0 +1,15 @@ +package org.schabi.newpipe.extractor.exceptions; + +import java.io.IOException; +import org.schabi.newpipe.extractor.downloader.Response; + +public class HttpResponseException extends IOException { + public HttpResponseException(final Response response) { + this("Error in HTTP Response for " + response.latestUrl() + "\n\t" + + response.responseCode() + " - " + response.responseMessage()); + } + + public HttpResponseException(final String message) { + super(message); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java index ae8fd77d6d..30b8ecd7e1 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java @@ -5,6 +5,7 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; +import static org.schabi.newpipe.extractor.utils.HttpUtils.validateResponseCode; import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; @@ -12,7 +13,6 @@ import com.grack.nanojson.JsonParserException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.schabi.newpipe.extractor.MultiInfoItemsCollector; import org.schabi.newpipe.extractor.Image; @@ -104,8 +104,8 @@ public static synchronized String clientId() throws ExtractionException, IOExcep final Downloader dl = NewPipe.getDownloader(); - final Response download = dl.get("https://soundcloud.com"); - final String responseBody = download.responseBody(); + final Response downloadResponse = dl.get("https://soundcloud.com").validateResponseCode(); + final String responseBody = downloadResponse.responseBody(); final String clientIdPattern = ",client_id:\"(.*?)\""; final Document doc = Jsoup.parse(responseBody); @@ -116,11 +116,12 @@ public static synchronized String clientId() throws ExtractionException, IOExcep final var headers = Map.of("Range", List.of("bytes=0-50000")); - for (final Element element : possibleScripts) { + for (final var element : possibleScripts) { final String srcUrl = element.attr("src"); if (!isNullOrEmpty(srcUrl)) { try { clientId = Parser.matchGroup1(clientIdPattern, dl.get(srcUrl, headers) + .validateResponseCode() .responseBody()); return clientId; } catch (final RegexException ignored) { @@ -148,11 +149,13 @@ public static OffsetDateTime parseDateFrom(final String textualUploadDate) } } + // CHECKSTYLE:OFF /** - * Call the endpoint "/resolve" of the API.

+ * Call the endpoint "/resolve" of the API. *

- * See https://developers.soundcloud.com/docs/api/reference#resolve + * See https://web.archive.org/web/20170804051146/https://developers.soundcloud.com/docs/api/reference#resolve */ + // CHECKSTYLE:ON public static JsonObject resolveFor(@Nonnull final Downloader downloader, final String url) throws IOException, ExtractionException { final String apiUrl = SOUNDCLOUD_API_V2_URL + "resolve" @@ -177,10 +180,11 @@ public static JsonObject resolveFor(@Nonnull final Downloader downloader, final public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOException, ReCaptchaException { - final String response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url=" - + Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization()).responseBody(); - - return Jsoup.parse(response).select("link[rel=\"canonical\"]").first() + final var response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url=" + + Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization()); + validateResponseCode(response); + final var responseBody = response.responseBody(); + return Jsoup.parse(responseBody).select("link[rel=\"canonical\"]").first() .attr("abs:href"); } @@ -189,6 +193,7 @@ public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOExc * * @return the resolved id */ + // TODO: what makes this method different from the others? Don' they all return the same? public static String resolveIdWithWidgetApi(final String urlString) throws IOException, ParsingException { String fixedUrl = urlString; @@ -224,9 +229,12 @@ public static String resolveIdWithWidgetApi(final String urlString) throws IOExc final String widgetUrl = "https://api-widget.soundcloud.com/resolve?url=" + Utils.encodeUrlUtf8(url.toString()) + "&format=json&client_id=" + SoundcloudParsingHelper.clientId(); - final String response = NewPipe.getDownloader().get(widgetUrl, - SoundCloud.getLocalization()).responseBody(); - final JsonObject o = JsonParser.object().from(response); + + final var response = NewPipe.getDownloader().get(widgetUrl, + SoundCloud.getLocalization()); + + final var responseBody = response.validateResponseCode().responseBody(); + final JsonObject o = JsonParser.object().from(responseBody); return String.valueOf(JsonUtils.getValue(o, "id")); } catch (final JsonParserException e) { throw new ParsingException("Could not parse JSON response", e); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java new file mode 100644 index 0000000000..31c937ea09 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java @@ -0,0 +1,36 @@ +package org.schabi.newpipe.extractor.utils; + +import java.util.Arrays; + +import org.schabi.newpipe.extractor.downloader.Response; +import org.schabi.newpipe.extractor.exceptions.HttpResponseException; + +public final class HttpUtils { + + private HttpUtils() { + // Utility class, no instances allowed + } + + // CHECKSTYLE:OFF + /** + * Validates the response codes for the given {@link Response}, and throws + * a {@link HttpResponseException} if the code is invalid + * @param response The response to validate + * @param validResponseCodes Expected valid response codes + * @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes}, + * or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error. + */ + // CHECKSTYLE:ON + public static void validateResponseCode(final Response response, + final int... validResponseCodes) + throws HttpResponseException { + final int code = response.responseCode(); + final var throwError = (validResponseCodes == null || validResponseCodes.length == 0) + ? code >= 400 && code <= 599 + : Arrays.stream(validResponseCodes).noneMatch(c -> c == code); + + if (throwError) { + throw new HttpResponseException(response); + } + } +} From ae4e8fc8bec30053b08150ddb8995d7ba3752907 Mon Sep 17 00:00:00 2001 From: AbsurdlyLongUsername <22662897+absurdlylongusername@users.noreply.github.com> Date: Sun, 5 Oct 2025 18:29:57 +0100 Subject: [PATCH 2/3] Move validateResponseCode to Response Make HttpResponseException extend ExtractionException Remove HttpUtils --- .../extractor/downloader/Response.java | 29 +++++++++++++-- .../exceptions/HttpResponseException.java | 3 +- .../soundcloud/SoundcloudParsingHelper.java | 5 +-- .../newpipe/extractor/utils/HttpUtils.java | 36 ------------------- 4 files changed, 30 insertions(+), 43 deletions(-) delete mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java b/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java index 87c3577ef4..593b8a78e5 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java @@ -2,12 +2,12 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.schabi.newpipe.extractor.exceptions.HttpResponseException; -import org.schabi.newpipe.extractor.utils.HttpUtils; /** * A Data class used to hold the results from requests made by the Downloader implementation. @@ -33,6 +33,28 @@ public Response(final int responseCode, this.latestUrl = latestUrl; } + /** + * Validates the response codes for the given {@link Response}, and throws + * a {@link HttpResponseException} if the code is invalid + * @param response The response to validate + * @param validResponseCodes Expected valid response codes + * @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes}, + * or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error. + */ + // CHECKSTYLE:ON + public static void validateResponseCode(final Response response, + final int... validResponseCodes) + throws HttpResponseException { + final int code = response.responseCode(); + final var throwError = (validResponseCodes == null || validResponseCodes.length == 0) + ? code >= 400 && code <= 599 + : Arrays.stream(validResponseCodes).noneMatch(c -> c == code); + + if (throwError) { + throw new HttpResponseException(response); + } + } + public int responseCode() { return responseCode; } @@ -83,12 +105,13 @@ public String getHeader(final String name) { return null; } + // CHECKSTYLE:OFF /** * Helper function simply to make it easier to validate response code inline * before getting the code/body/latestUrl/etc. * Validates the response codes for the given {@link Response}, and throws a {@link HttpResponseException} if the code is invalid - * @see HttpUtils#validateResponseCode(Response, int...) + * @see Response#validateResponseCode(Response, int...) * @param validResponseCodes Expected valid response codes * @return {@link this} response * @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes}, @@ -97,7 +120,7 @@ public String getHeader(final String name) { // CHECKSTYLE:ON public Response validateResponseCode(final int... validResponseCodes) throws HttpResponseException { - HttpUtils.validateResponseCode(this, validResponseCodes); + validateResponseCode(this, validResponseCodes); return this; } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java b/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java index c07850a9d3..51b1f77c66 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/exceptions/HttpResponseException.java @@ -1,9 +1,8 @@ package org.schabi.newpipe.extractor.exceptions; -import java.io.IOException; import org.schabi.newpipe.extractor.downloader.Response; -public class HttpResponseException extends IOException { +public class HttpResponseException extends ExtractionException { public HttpResponseException(final Response response) { this("Error in HTTP Response for " + response.latestUrl() + "\n\t" + response.responseCode() + " - " + response.responseMessage()); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java index 30b8ecd7e1..5159f1c73a 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java @@ -5,7 +5,7 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps; -import static org.schabi.newpipe.extractor.utils.HttpUtils.validateResponseCode; +import static org.schabi.newpipe.extractor.downloader.Response.validateResponseCode; import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonObject; @@ -21,6 +21,7 @@ import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.HttpResponseException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudChannelInfoItemExtractor; @@ -178,7 +179,7 @@ public static JsonObject resolveFor(@Nonnull final Downloader downloader, final * @return the url resolved */ public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOException, - ReCaptchaException { + ReCaptchaException, HttpResponseException { final var response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url=" + Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization()); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java deleted file mode 100644 index 31c937ea09..0000000000 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/HttpUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.schabi.newpipe.extractor.utils; - -import java.util.Arrays; - -import org.schabi.newpipe.extractor.downloader.Response; -import org.schabi.newpipe.extractor.exceptions.HttpResponseException; - -public final class HttpUtils { - - private HttpUtils() { - // Utility class, no instances allowed - } - - // CHECKSTYLE:OFF - /** - * Validates the response codes for the given {@link Response}, and throws - * a {@link HttpResponseException} if the code is invalid - * @param response The response to validate - * @param validResponseCodes Expected valid response codes - * @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes}, - * or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error. - */ - // CHECKSTYLE:ON - public static void validateResponseCode(final Response response, - final int... validResponseCodes) - throws HttpResponseException { - final int code = response.responseCode(); - final var throwError = (validResponseCodes == null || validResponseCodes.length == 0) - ? code >= 400 && code <= 599 - : Arrays.stream(validResponseCodes).noneMatch(c -> c == code); - - if (throwError) { - throw new HttpResponseException(response); - } - } -} From e4c51175e4fc68a55877488281aace5b29dfc831 Mon Sep 17 00:00:00 2001 From: AbsurdlyLongUsername <22662897+absurdlylongusername@users.noreply.github.com> Date: Sun, 5 Oct 2025 18:49:55 +0100 Subject: [PATCH 3/3] Fix checkstyle off --- .../java/org/schabi/newpipe/extractor/downloader/Response.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java b/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java index 593b8a78e5..b759fac2cc 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java @@ -33,6 +33,7 @@ public Response(final int responseCode, this.latestUrl = latestUrl; } + // CHECKSTYLE:OFF /** * Validates the response codes for the given {@link Response}, and throws * a {@link HttpResponseException} if the code is invalid