Skip to content

Commit 58094d8

Browse files
[SoundCloud] Validate http response code in SoundcloudParsingHelper
1 parent 8dfb0d3 commit 58094d8

File tree

4 files changed

+92
-13
lines changed

4 files changed

+92
-13
lines changed

extractor/src/main/java/org/schabi/newpipe/extractor/downloader/Response.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import java.util.List;
77
import java.util.Map;
88

9+
import org.schabi.newpipe.extractor.exceptions.HttpResponseException;
10+
import org.schabi.newpipe.extractor.utils.HttpUtils;
11+
912
/**
1013
* A Data class used to hold the results from requests made by the Downloader implementation.
1114
*/
@@ -80,4 +83,21 @@ public String getHeader(final String name) {
8083

8184
return null;
8285
}
86+
// CHECKSTYLE:OFF
87+
/**
88+
* Helper function simply to make it easier to validate response code inline
89+
* before getting the code/body/latestUrl/etc.
90+
* Validates the response codes for the given {@link Response}, and throws a {@link HttpResponseException} if the code is invalid
91+
* @see HttpUtils#validateResponseCode(Response, int...)
92+
* @param validResponseCodes Expected valid response codes
93+
* @return {@link this} response
94+
* @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes},
95+
* or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error.
96+
*/
97+
// CHECKSTYLE:ON
98+
public Response validateResponseCode(final int... validResponseCodes)
99+
throws HttpResponseException {
100+
HttpUtils.validateResponseCode(this, validResponseCodes);
101+
return this;
102+
}
83103
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.schabi.newpipe.extractor.exceptions;
2+
3+
import java.io.IOException;
4+
import org.schabi.newpipe.extractor.downloader.Response;
5+
6+
public class HttpResponseException extends IOException {
7+
public HttpResponseException(final Response response) {
8+
this("Error in HTTP Response for " + response.latestUrl() + "\n\t"
9+
+ response.responseCode() + " - " + response.responseMessage());
10+
}
11+
12+
public HttpResponseException(final String message) {
13+
super(message);
14+
}
15+
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
66
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
77
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
8+
import static org.schabi.newpipe.extractor.utils.HttpUtils.validateResponseCode;
89

910
import com.grack.nanojson.JsonArray;
1011
import com.grack.nanojson.JsonObject;
1112
import com.grack.nanojson.JsonParser;
1213
import com.grack.nanojson.JsonParserException;
1314
import org.jsoup.Jsoup;
1415
import org.jsoup.nodes.Document;
15-
import org.jsoup.nodes.Element;
1616
import org.jsoup.select.Elements;
1717
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
1818
import org.schabi.newpipe.extractor.Image;
@@ -104,8 +104,8 @@ public static synchronized String clientId() throws ExtractionException, IOExcep
104104

105105
final Downloader dl = NewPipe.getDownloader();
106106

107-
final Response download = dl.get("https://soundcloud.com");
108-
final String responseBody = download.responseBody();
107+
final Response downloadResponse = dl.get("https://soundcloud.com").validateResponseCode();
108+
final String responseBody = downloadResponse.responseBody();
109109
final String clientIdPattern = ",client_id:\"(.*?)\"";
110110

111111
final Document doc = Jsoup.parse(responseBody);
@@ -116,11 +116,12 @@ public static synchronized String clientId() throws ExtractionException, IOExcep
116116

117117
final var headers = Map.of("Range", List.of("bytes=0-50000"));
118118

119-
for (final Element element : possibleScripts) {
119+
for (final var element : possibleScripts) {
120120
final String srcUrl = element.attr("src");
121121
if (!isNullOrEmpty(srcUrl)) {
122122
try {
123123
clientId = Parser.matchGroup1(clientIdPattern, dl.get(srcUrl, headers)
124+
.validateResponseCode()
124125
.responseBody());
125126
return clientId;
126127
} catch (final RegexException ignored) {
@@ -148,11 +149,13 @@ public static OffsetDateTime parseDateFrom(final String textualUploadDate)
148149
}
149150
}
150151

152+
// CHECKSTYLE:OFF
151153
/**
152-
* Call the endpoint "/resolve" of the API.<p>
154+
* Call the endpoint "/resolve" of the API.
153155
* <p>
154-
* See https://developers.soundcloud.com/docs/api/reference#resolve
156+
* See https://web.archive.org/web/20170804051146/https://developers.soundcloud.com/docs/api/reference#resolve
155157
*/
158+
// CHECKSTYLE:ON
156159
public static JsonObject resolveFor(@Nonnull final Downloader downloader, final String url)
157160
throws IOException, ExtractionException {
158161
final String apiUrl = SOUNDCLOUD_API_V2_URL + "resolve"
@@ -177,10 +180,11 @@ public static JsonObject resolveFor(@Nonnull final Downloader downloader, final
177180
public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOException,
178181
ReCaptchaException {
179182

180-
final String response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url="
181-
+ Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization()).responseBody();
182-
183-
return Jsoup.parse(response).select("link[rel=\"canonical\"]").first()
183+
final var response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url="
184+
+ Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization());
185+
validateResponseCode(response);
186+
final var responseBody = response.responseBody();
187+
return Jsoup.parse(responseBody).select("link[rel=\"canonical\"]").first()
184188
.attr("abs:href");
185189
}
186190

@@ -189,6 +193,7 @@ public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOExc
189193
*
190194
* @return the resolved id
191195
*/
196+
// TODO: what makes this method different from the others? Don' they all return the same?
192197
public static String resolveIdWithWidgetApi(final String urlString) throws IOException,
193198
ParsingException {
194199
String fixedUrl = urlString;
@@ -224,9 +229,12 @@ public static String resolveIdWithWidgetApi(final String urlString) throws IOExc
224229
final String widgetUrl = "https://api-widget.soundcloud.com/resolve?url="
225230
+ Utils.encodeUrlUtf8(url.toString())
226231
+ "&format=json&client_id=" + SoundcloudParsingHelper.clientId();
227-
final String response = NewPipe.getDownloader().get(widgetUrl,
228-
SoundCloud.getLocalization()).responseBody();
229-
final JsonObject o = JsonParser.object().from(response);
232+
233+
final var response = NewPipe.getDownloader().get(widgetUrl,
234+
SoundCloud.getLocalization());
235+
236+
final var responseBody = response.validateResponseCode().responseBody();
237+
final JsonObject o = JsonParser.object().from(responseBody);
230238
return String.valueOf(JsonUtils.getValue(o, "id"));
231239
} catch (final JsonParserException e) {
232240
throw new ParsingException("Could not parse JSON response", e);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.schabi.newpipe.extractor.utils;
2+
3+
import java.util.Arrays;
4+
5+
import org.schabi.newpipe.extractor.downloader.Response;
6+
import org.schabi.newpipe.extractor.exceptions.HttpResponseException;
7+
8+
public final class HttpUtils {
9+
10+
private HttpUtils() {
11+
// Utility class, no instances allowed
12+
}
13+
14+
// CHECKSTYLE:OFF
15+
/**
16+
* Validates the response codes for the given {@link Response}, and throws
17+
* a {@link HttpResponseException} if the code is invalid
18+
* @param response The response to validate
19+
* @param validResponseCodes Expected valid response codes
20+
* @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes},
21+
* or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error.
22+
*/
23+
// CHECKSTYLE:ON
24+
public static void validateResponseCode(final Response response,
25+
final int... validResponseCodes)
26+
throws HttpResponseException {
27+
final int code = response.responseCode();
28+
final var throwError = (validResponseCodes == null || validResponseCodes.length == 0)
29+
? code >= 400 && code <= 599
30+
: Arrays.stream(validResponseCodes).noneMatch(c -> c == code);
31+
32+
if (throwError) {
33+
throw new HttpResponseException(response);
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)