Skip to content

Commit a4bacd0

Browse files
[SoundCloud] Validate http response code in SoundcloudParsingHelper
1 parent 7cf4379 commit a4bacd0

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;
@@ -103,8 +103,8 @@ public static synchronized String clientId() throws ExtractionException, IOExcep
103103

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

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

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

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

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

151+
// CHECKSTYLE:OFF
150152
/**
151-
* Call the endpoint "/resolve" of the API.<p>
153+
* Call the endpoint "/resolve" of the API.
152154
* <p>
153-
* See https://developers.soundcloud.com/docs/api/reference#resolve
155+
* See https://web.archive.org/web/20170804051146/https://developers.soundcloud.com/docs/api/reference#resolve
154156
*/
157+
// CHECKSTYLE:ON
155158
public static JsonObject resolveFor(@Nonnull final Downloader downloader, final String url)
156159
throws IOException, ExtractionException {
157160
final String apiUrl = SOUNDCLOUD_API_V2_URL + "resolve"
@@ -176,10 +179,11 @@ public static JsonObject resolveFor(@Nonnull final Downloader downloader, final
176179
public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOException,
177180
ReCaptchaException {
178181

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

@@ -188,6 +192,7 @@ public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOExc
188192
*
189193
* @return the resolved id
190194
*/
195+
// TODO: what makes this method different from the others? Don' they all return the same?
191196
public static String resolveIdWithWidgetApi(final String urlString) throws IOException,
192197
ParsingException {
193198
String fixedUrl = urlString;
@@ -223,9 +228,12 @@ public static String resolveIdWithWidgetApi(final String urlString) throws IOExc
223228
final String widgetUrl = "https://api-widget.soundcloud.com/resolve?url="
224229
+ Utils.encodeUrlUtf8(url.toString())
225230
+ "&format=json&client_id=" + SoundcloudParsingHelper.clientId();
226-
final String response = NewPipe.getDownloader().get(widgetUrl,
227-
SoundCloud.getLocalization()).responseBody();
228-
final JsonObject o = JsonParser.object().from(response);
231+
232+
final var response = NewPipe.getDownloader().get(widgetUrl,
233+
SoundCloud.getLocalization());
234+
235+
final var responseBody = response.validateResponseCode().responseBody();
236+
final JsonObject o = JsonParser.object().from(responseBody);
229237
return String.valueOf(JsonUtils.getValue(o, "id"));
230238
} catch (final JsonParserException e) {
231239
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)