Skip to content

Commit 7828a2a

Browse files
[SoundCloud] Validate http response code in SoundcloudParsingHelper
1 parent 13755f7 commit 7828a2a

4 files changed

Lines changed: 92 additions & 13 deletions

File tree

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

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

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

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

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

120-
for (final Element element : possibleScripts) {
120+
for (final var element : possibleScripts) {
121121
final String srcUrl = element.attr("src");
122122
if (!isNullOrEmpty(srcUrl)) {
123123
try {
124124
clientId = Parser.matchGroup1(clientIdPattern, dl.get(srcUrl, headers)
125+
.validateResponseCode()
125126
.responseBody());
126127
return clientId;
127128
} catch (final RegexException ignored) {
@@ -149,11 +150,13 @@ public static DateWrapper parseDate(final String uploadDate) throws ParsingExcep
149150
}
150151
}
151152

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

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

@@ -190,6 +194,7 @@ public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOExc
190194
*
191195
* @return the resolved id
192196
*/
197+
// TODO: what makes this method different from the others? Don' they all return the same?
193198
public static String resolveIdWithWidgetApi(final String urlString) throws IOException,
194199
ParsingException {
195200
String fixedUrl = urlString;
@@ -225,9 +230,12 @@ public static String resolveIdWithWidgetApi(final String urlString) throws IOExc
225230
final String widgetUrl = "https://api-widget.soundcloud.com/resolve?url="
226231
+ Utils.encodeUrlUtf8(url.toString())
227232
+ "&format=json&client_id=" + SoundcloudParsingHelper.clientId();
228-
final String response = NewPipe.getDownloader().get(widgetUrl,
229-
SoundCloud.getLocalization()).responseBody();
230-
final JsonObject o = JsonParser.object().from(response);
233+
234+
final var response = NewPipe.getDownloader().get(widgetUrl,
235+
SoundCloud.getLocalization());
236+
237+
final var responseBody = response.validateResponseCode().responseBody();
238+
final JsonObject o = JsonParser.object().from(responseBody);
231239
return String.valueOf(JsonUtils.getValue(o, "id"));
232240
} catch (final JsonParserException e) {
233241
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)