From a129a685b6fc54441c1a779ce29ea4ec20c2e09f Mon Sep 17 00:00:00 2001 From: Viktor Kryshtal Date: Fri, 29 May 2026 19:17:49 +0300 Subject: [PATCH 1/3] Added validation to domain and path parameters --- .../bidder/acuityads/AcuityadsBidder.java | 2 +- .../server/bidder/adhese/AdheseBidder.java | 2 +- .../server/bidder/adtonos/AdtonosBidder.java | 2 +- .../server/bidder/adview/AdviewBidder.java | 2 +- .../server/bidder/axonix/AxonixBidder.java | 2 +- .../bidder/bidmachine/BidmachineBidder.java | 6 ++-- .../prebid/server/bidder/blis/BlisBidder.java | 2 +- .../server/bidder/clydo/ClydoBidder.java | 2 +- .../bidder/contxtful/ContxtfulBidder.java | 2 +- .../bidder/elementaltv/ElementalTVBidder.java | 2 +- .../bidder/eplanning/EplanningBidder.java | 7 +++- .../server/bidder/gamoshi/GamoshiBidder.java | 6 ++-- .../prebid/server/bidder/imds/ImdsBidder.java | 3 +- .../server/bidder/kayzen/KayzenBidder.java | 3 +- .../bidder/kueezrtb/KueezRtbBidder.java | 3 +- .../bidder/madvertise/MadvertiseBidder.java | 2 +- .../prebid/server/bidder/mgid/MgidBidder.java | 5 ++- .../server/bidder/onetag/OnetagBidder.java | 3 +- .../bidder/operaads/OperaadsBidder.java | 3 +- .../server/bidder/ownadx/OwnAdxBidder.java | 6 ++-- .../server/bidder/rediads/RediadsBidder.java | 3 +- .../RelevantDigitalBidder.java | 2 +- .../server/bidder/roulax/RoulaxBidder.java | 3 +- .../bidder/silvermob/SilvermobBidder.java | 4 +-- .../bidder/smartyads/SmartyAdsBidder.java | 2 +- .../bidder/smilewanted/SmileWantedBidder.java | 3 +- .../server/bidder/tappx/TappxBidder.java | 4 ++- .../thetradedesk/TheTradeDeskBidder.java | 3 +- .../bidder/tradplus/TradPlusBidder.java | 5 +-- .../bidder/trafficgate/TrafficGateBidder.java | 2 +- .../bidder/ucfunnel/UcfunnelBidder.java | 3 +- .../server/bidder/vidazoo/VidazooBidder.java | 3 +- .../bidder/yeahmobi/YeahmobiBidder.java | 2 +- .../bidder/yieldlab/YieldlabBidder.java | 9 ++--- .../zeroclickfraud/ZeroclickfraudBidder.java | 2 +- .../java/org/prebid/server/util/HttpUtil.java | 33 +++++++++++++++++-- src/main/resources/bidder-config/clydo.yaml | 2 +- 37 files changed, 103 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/acuityads/AcuityadsBidder.java b/src/main/java/org/prebid/server/bidder/acuityads/AcuityadsBidder.java index f44d3d6f934..5378e66b284 100644 --- a/src/main/java/org/prebid/server/bidder/acuityads/AcuityadsBidder.java +++ b/src/main/java/org/prebid/server/bidder/acuityads/AcuityadsBidder.java @@ -95,7 +95,7 @@ private ExtImpAcuityads parseImpExt(Imp imp) { private String resolveEndpoint(String host, String accountId) { return endpointUrl - .replace(URL_HOST_MACRO, StringUtils.stripToEmpty(host)) + .replace(URL_HOST_MACRO, HttpUtil.validateDomainName(StringUtils.stripToEmpty(host))) .replace(URL_ACCOUNT_ID_MACRO, StringUtils.stripToEmpty(accountId)); } diff --git a/src/main/java/org/prebid/server/bidder/adhese/AdheseBidder.java b/src/main/java/org/prebid/server/bidder/adhese/AdheseBidder.java index 4c72fd97760..ff59bc6d36d 100644 --- a/src/main/java/org/prebid/server/bidder/adhese/AdheseBidder.java +++ b/src/main/java/org/prebid/server/bidder/adhese/AdheseBidder.java @@ -78,7 +78,7 @@ private ExtImpAdhese parseImpExt(Imp imp) { } private String getUrl(ExtImpAdhese extImpAdhese) { - return endpointUrl.replace("{{AccountId}}", extImpAdhese.getAccount()); + return endpointUrl.replace("{{AccountId}}", HttpUtil.validateDomainName(extImpAdhese.getAccount())); } private BidRequest modifyBidRequest(BidRequest bidRequest, ExtImpAdhese extImpAdhese) { diff --git a/src/main/java/org/prebid/server/bidder/adtonos/AdtonosBidder.java b/src/main/java/org/prebid/server/bidder/adtonos/AdtonosBidder.java index f780a020732..fe0777dbc8e 100644 --- a/src/main/java/org/prebid/server/bidder/adtonos/AdtonosBidder.java +++ b/src/main/java/org/prebid/server/bidder/adtonos/AdtonosBidder.java @@ -63,7 +63,7 @@ private ExtImpAdtonos parseImpExt(Imp imp) { } private String makeUrl(ExtImpAdtonos extImp) { - return endpointUrl.replace(PUBLISHER_ID_MACRO, extImp.getSupplierId()); + return endpointUrl.replace(PUBLISHER_ID_MACRO, HttpUtil.validatePathSegment(extImp.getSupplierId())); } @Override diff --git a/src/main/java/org/prebid/server/bidder/adview/AdviewBidder.java b/src/main/java/org/prebid/server/bidder/adview/AdviewBidder.java index 8c1de16d03d..7b464785ad6 100644 --- a/src/main/java/org/prebid/server/bidder/adview/AdviewBidder.java +++ b/src/main/java/org/prebid/server/bidder/adview/AdviewBidder.java @@ -131,7 +131,7 @@ private static Banner resolveBanner(Banner banner) { } private String resolveEndpoint(String accountId) { - return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(accountId)); + return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(accountId))); } @Override diff --git a/src/main/java/org/prebid/server/bidder/axonix/AxonixBidder.java b/src/main/java/org/prebid/server/bidder/axonix/AxonixBidder.java index e7bbf291e38..464d27a8a8c 100644 --- a/src/main/java/org/prebid/server/bidder/axonix/AxonixBidder.java +++ b/src/main/java/org/prebid/server/bidder/axonix/AxonixBidder.java @@ -67,7 +67,7 @@ private ExtImpAxonix parseImpExt(Imp imp) { } private String resolveEndpoint(String supplyId) { - return endpointUrl.replace(URL_SUPPLY_ID_MACRO, HttpUtil.encodeUrl(supplyId)); + return endpointUrl.replace(URL_SUPPLY_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(supplyId))); } @Override diff --git a/src/main/java/org/prebid/server/bidder/bidmachine/BidmachineBidder.java b/src/main/java/org/prebid/server/bidder/bidmachine/BidmachineBidder.java index 26a2ae3b814..78a901d1dab 100644 --- a/src/main/java/org/prebid/server/bidder/bidmachine/BidmachineBidder.java +++ b/src/main/java/org/prebid/server/bidder/bidmachine/BidmachineBidder.java @@ -142,9 +142,9 @@ private static boolean isMissedRewardedBattr(List battr) { } private String buildEndpointUrl(ExtImpBidmachine extImpBidmachine) { - return endpointUrl.replace("{{HOST}}", extImpBidmachine.getHost()) - .replace("{{PATH}}", extImpBidmachine.getPath()) - .replace("{{SELLER_ID}}", extImpBidmachine.getSellerId()); + return endpointUrl.replace("{{HOST}}", HttpUtil.validateDomainName(extImpBidmachine.getHost())) + .replace("{{PATH}}", HttpUtil.validatePathSegment(extImpBidmachine.getPath())) + .replace("{{SELLER_ID}}", HttpUtil.validatePathSegment(extImpBidmachine.getSellerId())); } private ExtPrebid parseImpExt(Imp imp) { diff --git a/src/main/java/org/prebid/server/bidder/blis/BlisBidder.java b/src/main/java/org/prebid/server/bidder/blis/BlisBidder.java index 10e9ee547b4..c0c27bcbb8d 100644 --- a/src/main/java/org/prebid/server/bidder/blis/BlisBidder.java +++ b/src/main/java/org/prebid/server/bidder/blis/BlisBidder.java @@ -72,7 +72,7 @@ private static MultiMap makeHeaders(String supplyId) { } private String makeUrl(String supplyId) { - return endpointUrl.replace(SUPPLY_ID_MACRO, HttpUtil.encodeUrl(supplyId)); + return endpointUrl.replace(SUPPLY_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(supplyId))); } @Override diff --git a/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java b/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java index ca7b04ee32f..80f4107ead7 100644 --- a/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java +++ b/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java @@ -111,7 +111,7 @@ private static MultiMap constructHeaders(BidRequest bidRequest) { private static String resolveUrl(String endpoint, ExtImpClydo extImp) { return endpoint .replace(REGION_MACRO, getRegionInfo(extImp)) - .replace(PARTNER_ID_MACRO, HttpUtil.encodeUrl(extImp.getPartnerId())); + .replace(PARTNER_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(extImp.getPartnerId()))); } private static String getRegionInfo(ExtImpClydo extImp) { diff --git a/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java b/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java index cc05acadcd4..8a524b9b83f 100644 --- a/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java +++ b/src/main/java/org/prebid/server/bidder/contxtful/ContxtfulBidder.java @@ -129,7 +129,7 @@ private static User modifyUser(User user) { } private String makeUrl(String customerId) { - return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(customerId)); + return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(customerId))); } @Override diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java b/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java index 17cd2950b7a..0f96d30912f 100644 --- a/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java +++ b/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java @@ -86,7 +86,7 @@ private ExtImpElementalTV parseAndValidateImpExt(Imp imp) { private String resolveUrl(ExtImpElementalTV extImp) { try { return endpointTemplate - .replace("{{AdUnit}}", HttpUtil.encodeUrl(extImp.getAdunit())); + .replace("{{AdUnit}}", HttpUtil.encodeUrl(HttpUtil.validatePathSegment(extImp.getAdunit()))); } catch (Exception e) { throw new PreBidException(e.getMessage()); } diff --git a/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java b/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java index 99beb0538bf..5bc2049396d 100644 --- a/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java +++ b/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java @@ -232,7 +232,12 @@ private String resolveRequestUri(BidRequest request, List requestsString ? app.getBundle() : pageDomain; - final String uri = "%s/%s/%s/%s/%s".formatted(endpointUrl, clientId, DFP_CLIENT_ID, requestTarget, SEC); + final String uri = "%s/%s/%s/%s/%s".formatted( + endpointUrl, + HttpUtil.validatePathSegment(clientId), + DFP_CLIENT_ID, + HttpUtil.validatePathSegment(requestTarget), + SEC); final URIBuilder uriBuilder; try { diff --git a/src/main/java/org/prebid/server/bidder/gamoshi/GamoshiBidder.java b/src/main/java/org/prebid/server/bidder/gamoshi/GamoshiBidder.java index 6d85c4ff511..fb5932251e6 100644 --- a/src/main/java/org/prebid/server/bidder/gamoshi/GamoshiBidder.java +++ b/src/main/java/org/prebid/server/bidder/gamoshi/GamoshiBidder.java @@ -57,7 +57,7 @@ public Result>> makeHttpRequests(BidRequest request if (validImps.isEmpty()) { errors.add(BidderError.badInput("No valid impressions in the bid request")); - return Result.of(Collections.emptyList(), errors); + return Result.of(Collections.>emptyList(), errors); } final ExtImpGamoshi firstImpExt; @@ -69,7 +69,9 @@ public Result>> makeHttpRequests(BidRequest request final BidRequest outgoingRequest = request.toBuilder().imp(validImps).build(); - final String requestUrl = endpointUrl + "/r/" + firstImpExt.getSupplyPartnerId() + "/bidr?bidder=prebid-server"; + final String requestUrl = endpointUrl + "/r/" + + HttpUtil.validatePathSegment(firstImpExt.getSupplyPartnerId()) + + "/bidr?bidder=prebid-server"; final MultiMap headers = resolveHeaders(request.getDevice()); return Result.of(Collections.singletonList( diff --git a/src/main/java/org/prebid/server/bidder/imds/ImdsBidder.java b/src/main/java/org/prebid/server/bidder/imds/ImdsBidder.java index 6261679a753..05493b5cdbb 100644 --- a/src/main/java/org/prebid/server/bidder/imds/ImdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/imds/ImdsBidder.java @@ -95,7 +95,8 @@ public Result>> makeHttpRequests(BidRequest bidRequ } private String generateEndpointUrl(ExtImpImds firstExtImp) { - final String accountId = URLEncoder.encode(firstExtImp.getSeatId(), StandardCharsets.UTF_8); + final String accountId = URLEncoder.encode( + HttpUtil.validatePathSegment(firstExtImp.getSeatId()), StandardCharsets.UTF_8); final String sourceId = URLEncoder.encode(prebidVersion, StandardCharsets.UTF_8); return endpointUrl .replaceAll("\\{\\{AccountID}}", accountId) diff --git a/src/main/java/org/prebid/server/bidder/kayzen/KayzenBidder.java b/src/main/java/org/prebid/server/bidder/kayzen/KayzenBidder.java index 342c64dee21..73ffcdcb03f 100644 --- a/src/main/java/org/prebid/server/bidder/kayzen/KayzenBidder.java +++ b/src/main/java/org/prebid/server/bidder/kayzen/KayzenBidder.java @@ -71,7 +71,8 @@ private ExtImpKayzen parseImpExt(Imp imp) { } private HttpRequest createRequest(ExtImpKayzen extImpKayzen, BidRequest request, List imps) { - final String url = endpointUrl.replace(URL_ZONE_ID_MACRO, extImpKayzen.getZone()) + final String url = endpointUrl + .replace(URL_ZONE_ID_MACRO, HttpUtil.validateDomainName(extImpKayzen.getZone())) .replace(URL_ACCOUNT_ID_MACRO, extImpKayzen.getExchange()); final BidRequest outgoingRequest = request.toBuilder().imp(imps).build(); diff --git a/src/main/java/org/prebid/server/bidder/kueezrtb/KueezRtbBidder.java b/src/main/java/org/prebid/server/bidder/kueezrtb/KueezRtbBidder.java index 2cc1e3bfe5a..381adc5090a 100644 --- a/src/main/java/org/prebid/server/bidder/kueezrtb/KueezRtbBidder.java +++ b/src/main/java/org/prebid/server/bidder/kueezrtb/KueezRtbBidder.java @@ -67,7 +67,8 @@ private KueezRtbImpExt parseImpExt(Imp imp) throws PreBidException { private HttpRequest makeHttpRequest(BidRequest bidRequest, Imp imp, KueezRtbImpExt impExt) { final BidRequest modifiedBidRequest = bidRequest.toBuilder().imp(Collections.singletonList(imp)).build(); - final String uri = endpointUrl + HttpUtil.encodeUrl(StringUtils.defaultString(impExt.getConnectionId()).trim()); + final String uri = endpointUrl + HttpUtil.encodeUrl( + HttpUtil.validatePathSegment(StringUtils.defaultString(impExt.getConnectionId()).trim())); return BidderUtil.defaultRequest(modifiedBidRequest, uri, mapper); } diff --git a/src/main/java/org/prebid/server/bidder/madvertise/MadvertiseBidder.java b/src/main/java/org/prebid/server/bidder/madvertise/MadvertiseBidder.java index 896d7baecef..a3b2bfa5551 100644 --- a/src/main/java/org/prebid/server/bidder/madvertise/MadvertiseBidder.java +++ b/src/main/java/org/prebid/server/bidder/madvertise/MadvertiseBidder.java @@ -89,7 +89,7 @@ private ExtImpMadvertise parseImpExt(Imp imp) { } private HttpRequest createRequest(BidRequest request, String zoneID) { - final String url = endpointUrl.replace(ZONE_ID_MACRO, HttpUtil.encodeUrl(zoneID)); + final String url = endpointUrl.replace(ZONE_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(zoneID))); return HttpRequest.builder() .method(HttpMethod.POST) diff --git a/src/main/java/org/prebid/server/bidder/mgid/MgidBidder.java b/src/main/java/org/prebid/server/bidder/mgid/MgidBidder.java index 637a122bad2..3b11e4a79ce 100644 --- a/src/main/java/org/prebid/server/bidder/mgid/MgidBidder.java +++ b/src/main/java/org/prebid/server/bidder/mgid/MgidBidder.java @@ -69,7 +69,10 @@ public final Result>> makeHttpRequests(BidRequest b .imp(imps) .build(); - return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, endpointUrl + accountId, mapper)); + return Result.withValue(BidderUtil.defaultRequest( + outgoingRequest, + endpointUrl + HttpUtil.validatePathSegment(accountId), + mapper)); } private ExtImpMgid parseImpExt(Imp imp) { diff --git a/src/main/java/org/prebid/server/bidder/onetag/OnetagBidder.java b/src/main/java/org/prebid/server/bidder/onetag/OnetagBidder.java index ec6c9718ea5..077fe65bdd2 100644 --- a/src/main/java/org/prebid/server/bidder/onetag/OnetagBidder.java +++ b/src/main/java/org/prebid/server/bidder/onetag/OnetagBidder.java @@ -54,7 +54,8 @@ public Result>> makeHttpRequests(BidRequest request } } - final String url = endpointUrl.replace(URL_PUBLISHER_ID_MACRO, StringUtils.defaultString(requestPubId)); + final String url = endpointUrl.replace( + URL_PUBLISHER_ID_MACRO, HttpUtil.validatePathSegment(StringUtils.defaultString(requestPubId))); return Result.withValue(BidderUtil.defaultRequest(request, url, mapper)); } diff --git a/src/main/java/org/prebid/server/bidder/operaads/OperaadsBidder.java b/src/main/java/org/prebid/server/bidder/operaads/OperaadsBidder.java index ab93ea6d4fb..ece71f8b74d 100644 --- a/src/main/java/org/prebid/server/bidder/operaads/OperaadsBidder.java +++ b/src/main/java/org/prebid/server/bidder/operaads/OperaadsBidder.java @@ -179,7 +179,8 @@ private HttpRequest createRequest(BidRequest bidRequest, Imp imp, Ex private String resolveUrl(ExtImpOperaads extImpOperaads) { return endpointUrl - .replace(PUBLISHER_ID_MACRO, HttpUtil.encodeUrl(extImpOperaads.getPublisherId())) + .replace(PUBLISHER_ID_MACRO, + HttpUtil.encodeUrl(HttpUtil.validatePathSegment(extImpOperaads.getPublisherId()))) .replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(extImpOperaads.getEndpointId())); } diff --git a/src/main/java/org/prebid/server/bidder/ownadx/OwnAdxBidder.java b/src/main/java/org/prebid/server/bidder/ownadx/OwnAdxBidder.java index 226d956c8e1..6347c663d5c 100644 --- a/src/main/java/org/prebid/server/bidder/ownadx/OwnAdxBidder.java +++ b/src/main/java/org/prebid/server/bidder/ownadx/OwnAdxBidder.java @@ -88,8 +88,10 @@ private HttpRequest createHttpRequest(BidRequest bidRequest, ExtImpO private String makeUrl(ExtImpOwnAdx extImpOwnAdx) { final Optional ownAdx = Optional.ofNullable(extImpOwnAdx); return endpointUrl - .replace(SEAT_ID_MACROS_ENDPOINT, ownAdx.map(ExtImpOwnAdx::getSeatId).orElse(StringUtils.EMPTY)) - .replace(SSP_ID_MACROS_ENDPOINT, ownAdx.map(ExtImpOwnAdx::getSspId).orElse(StringUtils.EMPTY)) + .replace(SEAT_ID_MACROS_ENDPOINT, + HttpUtil.validatePathSegment(ownAdx.map(ExtImpOwnAdx::getSeatId).orElse(StringUtils.EMPTY))) + .replace(SSP_ID_MACROS_ENDPOINT, + HttpUtil.validatePathSegment(ownAdx.map(ExtImpOwnAdx::getSspId).orElse(StringUtils.EMPTY))) .replace(TOKEN_ID_MACROS_ENDPOINT, ownAdx.map(ExtImpOwnAdx::getTokenId).orElse(StringUtils.EMPTY)); } diff --git a/src/main/java/org/prebid/server/bidder/rediads/RediadsBidder.java b/src/main/java/org/prebid/server/bidder/rediads/RediadsBidder.java index c0dad6f8760..5e1ed90df49 100644 --- a/src/main/java/org/prebid/server/bidder/rediads/RediadsBidder.java +++ b/src/main/java/org/prebid/server/bidder/rediads/RediadsBidder.java @@ -125,7 +125,8 @@ private static App modifyApp(App app, String accountId) { } private String resolveEndpointUrl(String subdomain) { - return endpointUrl.replace(SUBDOMAIN_MACRO, StringUtils.defaultIfBlank(subdomain, defaultSubdomain)); + return endpointUrl.replace( + SUBDOMAIN_MACRO, HttpUtil.validateDomainName(StringUtils.defaultIfBlank(subdomain, defaultSubdomain))); } @Override diff --git a/src/main/java/org/prebid/server/bidder/relevantdigital/RelevantDigitalBidder.java b/src/main/java/org/prebid/server/bidder/relevantdigital/RelevantDigitalBidder.java index 2763512929f..69972f025dc 100644 --- a/src/main/java/org/prebid/server/bidder/relevantdigital/RelevantDigitalBidder.java +++ b/src/main/java/org/prebid/server/bidder/relevantdigital/RelevantDigitalBidder.java @@ -174,7 +174,7 @@ private String makeUrl(String host) { .replace("http://", "") .replace("https://", "") .replace(".relevant-digital.com", ""); - return endpointUrl.replace(HOST_MACRO, modifiedHost); + return endpointUrl.replace(HOST_MACRO, HttpUtil.validateDomainName(modifiedHost)); } private static MultiMap makeHeaders(BidRequest bidRequest) { diff --git a/src/main/java/org/prebid/server/bidder/roulax/RoulaxBidder.java b/src/main/java/org/prebid/server/bidder/roulax/RoulaxBidder.java index d21dd3f4b68..2c5c698d42a 100644 --- a/src/main/java/org/prebid/server/bidder/roulax/RoulaxBidder.java +++ b/src/main/java/org/prebid/server/bidder/roulax/RoulaxBidder.java @@ -66,7 +66,8 @@ private ExtImpRoulax parseImpExt(Imp imp) { private String resolveEndpoint(ExtImpRoulax extImpRoulax) { return endpointUrl - .replace(PUBLISHER_PATH_MACRO, StringUtils.defaultString(extImpRoulax.getPublisherPath()).trim()) + .replace(PUBLISHER_PATH_MACRO, + HttpUtil.validatePathSegment(StringUtils.defaultString(extImpRoulax.getPublisherPath()).trim())) .replace(ACCOUNT_ID_MACRO, StringUtils.defaultString(extImpRoulax.getPid()).trim()); } diff --git a/src/main/java/org/prebid/server/bidder/silvermob/SilvermobBidder.java b/src/main/java/org/prebid/server/bidder/silvermob/SilvermobBidder.java index 2ab8323d075..f647decf647 100644 --- a/src/main/java/org/prebid/server/bidder/silvermob/SilvermobBidder.java +++ b/src/main/java/org/prebid/server/bidder/silvermob/SilvermobBidder.java @@ -104,8 +104,8 @@ private static Boolean isInvalidHost(String host) { private String resolveEndpoint(ExtImpSilvermob extImp) { return endpointUrl - .replace(URL_HOST_MACRO, extImp.getHost()) - .replace(URL_ZONE_ID_MACRO, HttpUtil.encodeUrl(extImp.getZoneId())); + .replace(URL_HOST_MACRO, HttpUtil.validateDomainName(extImp.getHost())) + .replace(URL_ZONE_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(extImp.getZoneId()))); } private static MultiMap resolveHeaders(Device device) { diff --git a/src/main/java/org/prebid/server/bidder/smartyads/SmartyAdsBidder.java b/src/main/java/org/prebid/server/bidder/smartyads/SmartyAdsBidder.java index 9b466c5f296..92ff72c124e 100644 --- a/src/main/java/org/prebid/server/bidder/smartyads/SmartyAdsBidder.java +++ b/src/main/java/org/prebid/server/bidder/smartyads/SmartyAdsBidder.java @@ -97,7 +97,7 @@ private static Imp updateImp(Imp imp) { private String resolveUrl(ExtImpSmartyAds extImp) { return endpointUrl - .replace(URL_HOST_MACRO, extImp.getHost()) + .replace(URL_HOST_MACRO, HttpUtil.validateDomainName(extImp.getHost())) .replace(URL_SOURCE_ID_MACRO, HttpUtil.encodeUrl(extImp.getSourceId())) .replace(URL_ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(extImp.getAccountId())); } diff --git a/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java b/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java index 67bfec26571..0ec74d43e77 100644 --- a/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java +++ b/src/main/java/org/prebid/server/bidder/smilewanted/SmileWantedBidder.java @@ -56,7 +56,8 @@ public Result>> makeHttpRequests(BidRequest request } final BidRequest outgoingRequest = request.toBuilder().at(DEFAULT_AT).build(); - final String url = endpointUrl.replace(ZONE_ID_MACRO, HttpUtil.encodeUrl(extImpSmilewanted.getZoneId())); + final String url = endpointUrl.replace( + ZONE_ID_MACRO, HttpUtil.encodeUrl(HttpUtil.validatePathSegment(extImpSmilewanted.getZoneId()))); return Result.withValue(BidderUtil.defaultRequest( outgoingRequest, diff --git a/src/main/java/org/prebid/server/bidder/tappx/TappxBidder.java b/src/main/java/org/prebid/server/bidder/tappx/TappxBidder.java index 67224479693..e72e9b571bd 100644 --- a/src/main/java/org/prebid/server/bidder/tappx/TappxBidder.java +++ b/src/main/java/org/prebid/server/bidder/tappx/TappxBidder.java @@ -23,6 +23,7 @@ import org.prebid.server.proto.openrtb.ext.request.tappx.ExtImpTappx; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; import java.math.BigDecimal; import java.net.URISyntaxException; @@ -101,7 +102,8 @@ private String resolveUrl(ExtImpTappx extImpTappx, Integer test) { if (!isNewEndpoint) { final List pathSegments = uriBuilder.getPathSegments(); - uriBuilder.setPathSegments(ListUtils.union(pathSegments, Collections.singletonList(subdomain))); + uriBuilder.setPathSegments(ListUtils.union(pathSegments, + Collections.singletonList(HttpUtil.validatePathSegment(subdomain)))); } uriBuilder.addParameter("tappxkey", extImpTappx.getTappxkey()); diff --git a/src/main/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidder.java b/src/main/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidder.java index a2853b26d72..ed322e869c6 100644 --- a/src/main/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidder.java +++ b/src/main/java/org/prebid/server/bidder/thetradedesk/TheTradeDeskBidder.java @@ -175,7 +175,8 @@ private static App modifyApp(BidRequest request, String publisherId) { private String resolveEndpoint(String sourceSupplyId) { return endpointUrl.replace( SUPPLY_ID_MACRO, - HttpUtil.encodeUrl(StringUtils.defaultString(ObjectUtils.defaultIfNull(sourceSupplyId, supplyId)))); + HttpUtil.encodeUrl(HttpUtil.validatePathSegment( + StringUtils.defaultString(ObjectUtils.defaultIfNull(sourceSupplyId, supplyId))))); } @Override diff --git a/src/main/java/org/prebid/server/bidder/tradplus/TradPlusBidder.java b/src/main/java/org/prebid/server/bidder/tradplus/TradPlusBidder.java index 99d8bc8e74c..4ec4adb5dea 100644 --- a/src/main/java/org/prebid/server/bidder/tradplus/TradPlusBidder.java +++ b/src/main/java/org/prebid/server/bidder/tradplus/TradPlusBidder.java @@ -79,8 +79,9 @@ private void validateImpExt(ExtImpTradPlus extImpTradPlus) { private HttpRequest makeHttpRequest(ExtImpTradPlus extImpTradPlus, List imps, BidRequest bidRequest) { final String uri; - uri = endpointUrl.replace(ZONE_ID, extImpTradPlus.getZoneId()).replace(ACCOUNT_ID, - extImpTradPlus.getAccountId()); + uri = endpointUrl + .replace(ZONE_ID, HttpUtil.validateDomainName(extImpTradPlus.getZoneId())) + .replace(ACCOUNT_ID, HttpUtil.validatePathSegment(extImpTradPlus.getAccountId())); final BidRequest outgoingRequest = bidRequest.toBuilder().imp(removeImpsExt(imps)).build(); diff --git a/src/main/java/org/prebid/server/bidder/trafficgate/TrafficGateBidder.java b/src/main/java/org/prebid/server/bidder/trafficgate/TrafficGateBidder.java index 22809f0814c..190dc000ccc 100644 --- a/src/main/java/org/prebid/server/bidder/trafficgate/TrafficGateBidder.java +++ b/src/main/java/org/prebid/server/bidder/trafficgate/TrafficGateBidder.java @@ -65,7 +65,7 @@ private HttpRequest createSingleRequest(ExtImpTrafficGate extImpTraf } private String resolveHost(ExtImpTrafficGate extImpTrafficGate) { - return endpointUrl.replace(SUBDOMAIN_MACRO, extImpTrafficGate.getHost()); + return endpointUrl.replace(SUBDOMAIN_MACRO, HttpUtil.validateDomainName(extImpTrafficGate.getHost())); } private ExtImpTrafficGate parseImpExt(Imp imp) { diff --git a/src/main/java/org/prebid/server/bidder/ucfunnel/UcfunnelBidder.java b/src/main/java/org/prebid/server/bidder/ucfunnel/UcfunnelBidder.java index ae3e814c249..2f92ee3ff64 100644 --- a/src/main/java/org/prebid/server/bidder/ucfunnel/UcfunnelBidder.java +++ b/src/main/java/org/prebid/server/bidder/ucfunnel/UcfunnelBidder.java @@ -63,7 +63,8 @@ public Result>> makeHttpRequests(BidRequest request errors.add(BidderError.badInput(e.getMessage())); } - final String requestUrl = "%s/%s/request".formatted(endpointUrl, HttpUtil.encodeUrl(partnerId)); + final String requestUrl = "%s/%s/request".formatted(endpointUrl, + HttpUtil.encodeUrl(HttpUtil.validatePathSegment(partnerId))); return Result.of(Collections.singletonList(BidderUtil.defaultRequest(request, requestUrl, mapper)), errors); diff --git a/src/main/java/org/prebid/server/bidder/vidazoo/VidazooBidder.java b/src/main/java/org/prebid/server/bidder/vidazoo/VidazooBidder.java index eda63f53d2f..753165274e6 100644 --- a/src/main/java/org/prebid/server/bidder/vidazoo/VidazooBidder.java +++ b/src/main/java/org/prebid/server/bidder/vidazoo/VidazooBidder.java @@ -68,7 +68,8 @@ private VidazooImpExt parseImpExt(Imp imp) throws PreBidException { private HttpRequest makeHttpRequest(BidRequest bidRequest, Imp imp, VidazooImpExt impExt) { final BidRequest modifiedBidRequest = bidRequest.toBuilder().imp(Collections.singletonList(imp)).build(); - final String uri = endpointUrl + HttpUtil.encodeUrl(StringUtils.defaultString(impExt.getConnectionId()).trim()); + final String uri = endpointUrl + HttpUtil.encodeUrl( + HttpUtil.validatePathSegment(StringUtils.defaultString(impExt.getConnectionId()).trim())); return BidderUtil.defaultRequest(modifiedBidRequest, uri, mapper); } diff --git a/src/main/java/org/prebid/server/bidder/yeahmobi/YeahmobiBidder.java b/src/main/java/org/prebid/server/bidder/yeahmobi/YeahmobiBidder.java index c33de175781..25fe026a14f 100644 --- a/src/main/java/org/prebid/server/bidder/yeahmobi/YeahmobiBidder.java +++ b/src/main/java/org/prebid/server/bidder/yeahmobi/YeahmobiBidder.java @@ -120,7 +120,7 @@ private String resolveNativeRequest(String nativeRequest) { private HttpRequest makeHttpRequest(BidRequest request, String zoneId) { return BidderUtil.defaultRequest( request, - endpointUrl.replace(HOST_MACRO, HOST_PATTERN.formatted(zoneId)), + endpointUrl.replace(HOST_MACRO, HttpUtil.validateDomainName(HOST_PATTERN.formatted(zoneId))), mapper); } diff --git a/src/main/java/org/prebid/server/bidder/yieldlab/YieldlabBidder.java b/src/main/java/org/prebid/server/bidder/yieldlab/YieldlabBidder.java index 47f6cd44523..073e212c3ee 100644 --- a/src/main/java/org/prebid/server/bidder/yieldlab/YieldlabBidder.java +++ b/src/main/java/org/prebid/server/bidder/yieldlab/YieldlabBidder.java @@ -153,7 +153,8 @@ private ExtImpYieldlab parseImpExt(Imp imp) { } private String makeUrl(ExtImpYieldlab extImpYieldlab, BidRequest request, Map extImps) { - final String updatedPath = "%s/%s".formatted(endpointUrl, extImpYieldlab.getAdslotId()); + final String updatedPath = "%s/%s".formatted( + endpointUrl, HttpUtil.validatePathSegment(extImpYieldlab.getAdslotId())); final URIBuilder uriBuilder; try { @@ -546,9 +547,9 @@ private String makeNurl(BidRequest bidRequest, ExtImpYieldlab extImp, YieldlabBi } return AD_SOURCE_URL.formatted( - extImp.getAdslotId(), - extImp.getSupplyId(), - yieldlabBid.getAdSize(), + HttpUtil.validatePathSegment(extImp.getAdslotId()), + HttpUtil.validatePathSegment(extImp.getSupplyId()), + HttpUtil.validatePathSegment(yieldlabBid.getAdSize()), uriBuilder.toString().replace("?", "")); } diff --git a/src/main/java/org/prebid/server/bidder/zeroclickfraud/ZeroclickfraudBidder.java b/src/main/java/org/prebid/server/bidder/zeroclickfraud/ZeroclickfraudBidder.java index c405c0981ae..42c3d9b590e 100644 --- a/src/main/java/org/prebid/server/bidder/zeroclickfraud/ZeroclickfraudBidder.java +++ b/src/main/java/org/prebid/server/bidder/zeroclickfraud/ZeroclickfraudBidder.java @@ -90,7 +90,7 @@ private ExtImpZeroclickfraud parseAndValidateImpExt(ObjectNode extNode) { private HttpRequest makeHttpRequest(ExtImpZeroclickfraud extImpZeroclickfraud, List imps, BidRequest bidRequest) { final String uri = endpointTemplate - .replace(HOST, extImpZeroclickfraud.getHost()) + .replace(HOST, HttpUtil.validateDomainName(extImpZeroclickfraud.getHost())) .replace(SOURCE_ID, extImpZeroclickfraud.getSourceId().toString()); final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imps).build(); diff --git a/src/main/java/org/prebid/server/util/HttpUtil.java b/src/main/java/org/prebid/server/util/HttpUtil.java index e08a276c6fa..e05846374c9 100644 --- a/src/main/java/org/prebid/server/util/HttpUtil.java +++ b/src/main/java/org/prebid/server/util/HttpUtil.java @@ -9,6 +9,7 @@ import io.vertx.ext.web.RoutingContext; import org.apache.commons.lang3.StringUtils; import org.apache.commons.validator.routines.UrlValidator; +import org.prebid.server.exception.PreBidException; import org.prebid.server.log.ConditionalLogger; import org.prebid.server.log.Logger; import org.prebid.server.log.LoggerFactory; @@ -26,6 +27,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -85,6 +87,9 @@ public final class HttpUtil { public static final String MACROS_OPEN = "{{"; public static final String MACROS_CLOSE = "}}"; + private static final Pattern VALID_DOMAIN = Pattern.compile("^[a-zA-Z0-9.-]+(:[0-9]+)?$"); + private static final String INVALID_PATH_CHARACTERS = "?#\\"; + private static final UrlValidator URL_VALIDAROR = UrlValidator.getInstance(); private HttpUtil() { @@ -119,6 +124,30 @@ private static boolean containsMacrosses(String url) { return StringUtils.contains(url, MACROS_OPEN) && StringUtils.contains(url, MACROS_CLOSE); } + public static String validateDomainName(String domainName) { + if (!VALID_DOMAIN.matcher(domainName).matches()) { + throw new PreBidException("Domain name %s contains invalid characters".formatted(domainName)); + } + return domainName; + } + + public static String validatePathSegment(String pathSegment) { + for (char c : INVALID_PATH_CHARACTERS.toCharArray()) { + if (pathSegment.indexOf(c) != -1) { + throw new PreBidException("Path segment %s contains forbidden character %s".formatted(pathSegment, c)); + } + } + + if (pathSegment.contains("//")) { + throw new PreBidException("Path segment %s contains forbidden sequence %s".formatted(pathSegment, "//")); + } + if (Arrays.asList(pathSegment.split("/")).contains("..")) { + throw new PreBidException("Path segment %s contains forbidden segment %s".formatted(pathSegment, "..")); + } + + return pathSegment; + } + /** * Returns encoded URL for the given value. *

@@ -230,10 +259,10 @@ public static Map> toDebugHeaders(MultiMap headers) { .filter(entry -> !isSensitiveHeader(entry.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, entry -> StringUtils.isNotBlank(entry.getValue()) - ? Arrays.stream(entry.getValue().split(",")) + ? Arrays.stream(entry.getValue().split(",")) .map(String::trim) .toList() - : Collections.singletonList(entry.getValue()))) + : Collections.singletonList(entry.getValue()))) : null; } diff --git a/src/main/resources/bidder-config/clydo.yaml b/src/main/resources/bidder-config/clydo.yaml index aa90e3a4f07..cd1e1f48a81 100644 --- a/src/main/resources/bidder-config/clydo.yaml +++ b/src/main/resources/bidder-config/clydo.yaml @@ -1,6 +1,6 @@ adapters: clydo: - endpoint: http://region={{Region}}.clydo.io/partnerId={{PartnerId}} + endpoint: http://{{Region}}.clydo.io/{{PartnerId}} modifying-vast-xml-allowed: true endpoint-compression: gzip geoscope: From c3bd93c6ee28cf13b7f27c26a8ae835ebba37a61 Mon Sep 17 00:00:00 2001 From: Viktor Kryshtal Date: Fri, 29 May 2026 19:26:18 +0300 Subject: [PATCH 2/3] Added unit tests --- .../org/prebid/server/util/HttpUtilTest.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/test/java/org/prebid/server/util/HttpUtilTest.java b/src/test/java/org/prebid/server/util/HttpUtilTest.java index 7926e05ed46..f4152f75951 100644 --- a/src/test/java/org/prebid/server/util/HttpUtilTest.java +++ b/src/test/java/org/prebid/server/util/HttpUtilTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.exception.PreBidException; import org.prebid.server.model.CaseInsensitiveMultiMap; import org.prebid.server.model.HttpRequestContext; @@ -18,6 +19,7 @@ import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.tuple; @@ -62,6 +64,75 @@ public void validateUrlShouldReturnExpectedUrl() { assertThat(url).isEqualTo("http://domain.org/query-string?a=1"); } + @Test + public void validateDomainNameShouldReturnExpectedDomainName() { + // when and then + assertThat(HttpUtil.validateDomainName("example.com")).isEqualTo("example.com"); + assertThat(HttpUtil.validateDomainName("sub.domain-example.com")).isEqualTo("sub.domain-example.com"); + assertThat(HttpUtil.validateDomainName("127.0.0.1")).isEqualTo("127.0.0.1"); + assertThat(HttpUtil.validateDomainName("example.com:8080")).isEqualTo("example.com:8080"); + } + + @Test + public void validateDomainNameShouldFailOnInvalidCharacters() { + // when and then + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validateDomainName("example.com/path")) + .withMessage("Domain name example.com/path contains invalid characters"); + + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validateDomainName("example@com")) + .withMessage("Domain name example@com contains invalid characters"); + + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validateDomainName("example.com:port")) + .withMessage("Domain name example.com:port contains invalid characters"); + } + + @Test + public void validatePathSegmentShouldReturnExpectedPathSegment() { + // when and then + assertThat(HttpUtil.validatePathSegment("path")).isEqualTo("path"); + assertThat(HttpUtil.validatePathSegment("path/to/resource")).isEqualTo("path/to/resource"); + assertThat(HttpUtil.validatePathSegment(".")).isEqualTo("."); + } + + @Test + public void validatePathSegmentShouldFailOnForbiddenCharacter() { + // when and then + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validatePathSegment("path?query")) + .withMessage("Path segment path?query contains forbidden character ?"); + + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validatePathSegment("path#fragment")) + .withMessage("Path segment path#fragment contains forbidden character #"); + + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validatePathSegment("path\\segment")) + .withMessage("Path segment path\\segment contains forbidden character \\"); + } + + @Test + public void validatePathSegmentShouldFailOnDoubleSlash() { + // when and then + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validatePathSegment("path//segment")) + .withMessage("Path segment path//segment contains forbidden sequence //"); + } + + @Test + public void validatePathSegmentShouldFailOnSegmentContainingTwoDots() { + // when and then + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validatePathSegment("path/../segment")) + .withMessage("Path segment path/../segment contains forbidden segment .."); + + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validatePathSegment("..")) + .withMessage("Path segment .. contains forbidden segment .."); + } + @Test public void encodeUrlShouldReturnExpectedValue() { // when From a98e6468c0258d1f4ca193c6a8b63c010a0f2959 Mon Sep 17 00:00:00 2001 From: Viktor Kryshtal Date: Fri, 29 May 2026 19:42:14 +0300 Subject: [PATCH 3/3] Added validation for null and empty domain names --- src/main/java/org/prebid/server/util/HttpUtil.java | 6 ++++++ src/test/java/org/prebid/server/util/HttpUtilTest.java | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/org/prebid/server/util/HttpUtil.java b/src/main/java/org/prebid/server/util/HttpUtil.java index e05846374c9..49957c2621e 100644 --- a/src/main/java/org/prebid/server/util/HttpUtil.java +++ b/src/main/java/org/prebid/server/util/HttpUtil.java @@ -125,6 +125,12 @@ private static boolean containsMacrosses(String url) { } public static String validateDomainName(String domainName) { + if (domainName == null) { + throw new PreBidException("Domain name is null"); + } + if (domainName.isEmpty()) { + return domainName; + } if (!VALID_DOMAIN.matcher(domainName).matches()) { throw new PreBidException("Domain name %s contains invalid characters".formatted(domainName)); } diff --git a/src/test/java/org/prebid/server/util/HttpUtilTest.java b/src/test/java/org/prebid/server/util/HttpUtilTest.java index f4152f75951..b202665d515 100644 --- a/src/test/java/org/prebid/server/util/HttpUtilTest.java +++ b/src/test/java/org/prebid/server/util/HttpUtilTest.java @@ -71,6 +71,15 @@ public void validateDomainNameShouldReturnExpectedDomainName() { assertThat(HttpUtil.validateDomainName("sub.domain-example.com")).isEqualTo("sub.domain-example.com"); assertThat(HttpUtil.validateDomainName("127.0.0.1")).isEqualTo("127.0.0.1"); assertThat(HttpUtil.validateDomainName("example.com:8080")).isEqualTo("example.com:8080"); + assertThat(HttpUtil.validateDomainName("")).isEqualTo(""); + } + + @Test + public void validateDomainNameShouldFailOnNull() { + // when and then + assertThatExceptionOfType(PreBidException.class) + .isThrownBy(() -> HttpUtil.validateDomainName(null)) + .withMessage("Domain name is null"); } @Test