Skip to content

Commit 401d5ef

Browse files
chore: url parse replaced by native url + multiple fixes and rewrites
1 parent a839c84 commit 401d5ef

File tree

17 files changed

+242
-116
lines changed

17 files changed

+242
-116
lines changed

lib/api_client/execute_request.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
const config = require("../config");
33
const https = /^http:/.test(config().upload_prefix) ? require('http') : require('https');
44
const querystring = require("querystring");
5-
const url = require('url');
65
const utils = require("../utils");
76
const ensureOption = require('../utils/ensureOption').defaults(config());
7+
const { URL } = require('url');
88

99
const { extend, includes, isEmpty } = utils;
1010

@@ -31,7 +31,19 @@ function execute_request(method, params, auth, api_url, callback, options = {})
3131
api_url += "?" + query_params;
3232
}
3333

34-
let request_options = url.parse(api_url);
34+
const parsedUrl = new URL(api_url);
35+
let request_options = {
36+
protocol: parsedUrl.protocol,
37+
hostname: parsedUrl.hostname,
38+
path: parsedUrl.pathname + parsedUrl.search,
39+
pathname: parsedUrl.pathname,
40+
query: parsedUrl.search ? parsedUrl.search.substring(1) : ''
41+
};
42+
43+
// Only add port if not default (to match url.parse() behavior)
44+
if (parsedUrl.port) {
45+
request_options.port = parsedUrl.port;
46+
}
3547

3648
request_options = extend(request_options, {
3749
method: method,
@@ -65,10 +77,10 @@ function execute_request(method, params, auth, api_url, callback, options = {})
6577
request_options.headers['Content-Length'] = Buffer.byteLength(query_params);
6678
}
6779
handle_response = function (res) {
68-
const {hide_sensitive = false} = config();
69-
const sanitizedOptions = {...request_options};
80+
const { hide_sensitive = false } = config();
81+
const sanitizedOptions = { ...request_options };
7082

71-
if (hide_sensitive === true){
83+
if (hide_sensitive === true) {
7284
if ("auth" in sanitizedOptions) { delete sanitizedOptions.auth; }
7385
if ("Authorization" in sanitizedOptions.headers) { delete sanitizedOptions.headers.Authorization; }
7486
}

lib/config.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
* @param value the value to assign
77
* @return the modified params object
88
*/
9-
const url = require('url');
109
const extend = require("lodash/extend");
1110
const isObject = require("lodash/isObject");
1211
const isString = require("lodash/isString");
1312
const isUndefined = require("lodash/isUndefined");
1413
const isEmpty = require("lodash/isEmpty");
1514
const entries = require('./utils/entries');
15+
const { URL } = require('url');
1616

1717
let cloudinary_config = void 0;
1818

@@ -48,31 +48,39 @@ function putNestedValue(params, key, value) {
4848
function parseCloudinaryConfigFromEnvURL(ENV_STR) {
4949
let conf = {};
5050

51-
let uri = url.parse(ENV_STR, true);
51+
const uri = new URL(ENV_STR);
52+
53+
const auth = uri.username && uri.password
54+
? `${uri.username}:${uri.password}`
55+
: (uri.username || null);
5256

5357
if (uri.protocol === 'cloudinary:') {
5458
conf = Object.assign({}, conf, {
55-
cloud_name: uri.host,
56-
api_key: uri.auth && uri.auth.split(":")[0],
57-
api_secret: uri.auth && uri.auth.split(":")[1],
58-
private_cdn: uri.pathname != null,
59-
secure_distribution: uri.pathname && uri.pathname.substring(1)
59+
cloud_name: uri.hostname,
60+
api_key: uri.username || (auth && auth.split(":")[0]),
61+
api_secret: uri.password || (auth && auth.split(":")[1]),
62+
private_cdn: uri.pathname != null && uri.pathname !== '' && uri.pathname !== '/',
63+
secure_distribution: uri.pathname && uri.pathname !== '/' ? uri.pathname.substring(1) : undefined
6064
});
6165
} else if (uri.protocol === 'account:') {
6266
conf = Object.assign({}, conf, {
63-
account_id: uri.host,
64-
provisioning_api_key: uri.auth && uri.auth.split(":")[0],
65-
provisioning_api_secret: uri.auth && uri.auth.split(":")[1]
67+
account_id: uri.hostname,
68+
provisioning_api_key: uri.username || (auth && auth.split(":")[0]),
69+
provisioning_api_secret: uri.password || (auth && auth.split(":")[1])
6670
});
6771
}
6872

6973
return conf;
7074
}
7175

7276
function extendCloudinaryConfigFromQuery(ENV_URL, confToExtend = {}) {
73-
let uri = url.parse(ENV_URL, true);
74-
if (uri.query != null) {
75-
entries(uri.query).forEach(([key, value]) => putNestedValue(confToExtend, key, value));
77+
const uri = new URL(ENV_URL);
78+
if (uri.search) {
79+
const query = {};
80+
uri.searchParams.forEach((value, key) => {
81+
query[key] = value;
82+
});
83+
entries(query).forEach(([key, value]) => putNestedValue(confToExtend, key, value));
7684
}
7785
}
7886

lib/uploader.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
const fs = require('fs');
2-
const { extname, basename } = require('path');
2+
const {
3+
extname,
4+
basename
5+
} = require('path');
36
const Writable = require("stream").Writable;
4-
const urlLib = require('url');
57

68
// eslint-disable-next-line import/order
79
const { upload_prefix } = require("./config")();
810

911
const isSecure = !(upload_prefix && upload_prefix.slice(0, 5) === 'http:');
1012
const https = isSecure ? require('https') : require('http');
13+
const { URL } = require('url');
1114

1215
const Cache = require('./cache');
1316
const utils = require("./utils");
@@ -425,14 +428,24 @@ function call_context_api(context, command, public_ids = [], callback, options =
425428
* @param {string} options.type
426429
* @param {string} options.resource_type
427430
*/
428-
function cacheResults(result, { type, resource_type }) {
431+
function cacheResults(result, {
432+
type,
433+
resource_type
434+
}) {
429435
if (result.responsive_breakpoints) {
430436
result.responsive_breakpoints.forEach(
431-
({ transformation,
437+
({
438+
transformation,
432439
url,
433-
breakpoints }) => Cache.set(
440+
breakpoints
441+
}) => Cache.set(
434442
result.public_id,
435-
{ type, resource_type, raw_transformation: transformation, format: extname(breakpoints[0].url).slice(1) },
443+
{
444+
type,
445+
resource_type,
446+
raw_transformation: transformation,
447+
format: extname(breakpoints[0].url).slice(1)
448+
},
436449
breakpoints.map(i => i.width)
437450
)
438451
);
@@ -460,7 +473,8 @@ function parseResult(buffer, res) {
460473

461474
function call_api(action, callback, options, get_params) {
462475
if (typeof callback !== "function") {
463-
callback = function () {};
476+
callback = function () {
477+
};
464478
}
465479

466480
const USE_PROMISES = !options.disable_promises;
@@ -469,6 +483,7 @@ function call_api(action, callback, options, get_params) {
469483
if (options == null) {
470484
options = {};
471485
}
486+
472487
let [params, unsigned_params, file] = get_params.call();
473488
params = utils.process_request_params(params, options);
474489
params = extend(params, unsigned_params);
@@ -555,7 +570,21 @@ function post(url, post_data, boundary, file, callback, options) {
555570
let filename = options.stream ? options.filename ? options.filename : "file" : basename(file);
556571
file_header = Buffer.from(encodeFilePart(boundary, 'application/octet-stream', 'file', filename), 'binary');
557572
}
558-
let post_options = urlLib.parse(url);
573+
// Use WHATWG URL API instead of deprecated url.parse()
574+
const parsedUrl = new URL(url);
575+
let post_options = {
576+
protocol: parsedUrl.protocol,
577+
hostname: parsedUrl.hostname,
578+
path: parsedUrl.pathname + parsedUrl.search,
579+
pathname: parsedUrl.pathname,
580+
query: parsedUrl.search ? parsedUrl.search.substring(1) : ''
581+
};
582+
583+
// Only add port if not default (to match url.parse() behavior)
584+
if (parsedUrl.port) {
585+
post_options.port = parsedUrl.port;
586+
}
587+
559588
let headers = {
560589
'Content-Type': `multipart/form-data; boundary=${boundary}`,
561590
'User-Agent': utils.getUserAgent()

lib/utils/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
const crypto = require("crypto");
88
const querystring = require("querystring");
9-
const urlParse = require("url").parse;
9+
const {URL} = require("url");
1010

1111
// Functions used internally
1212
const compact = require("lodash/compact");
@@ -877,7 +877,10 @@ function url(public_id, options = {}) {
877877
return (part != null) && part !== '';
878878
}).join('/').replace(/ /g, '%20');
879879
if (sign_url && !isEmpty(auth_token)) {
880-
auth_token.url = urlParse(resultUrl).path;
880+
// Use WHATWG URL API instead of deprecated url.parse()
881+
// For relative URLs, provide a dummy base URL
882+
const parsedUrl = new URL(resultUrl, 'http://dummy');
883+
auth_token.url = parsedUrl.pathname + parsedUrl.search;
881884
let token = generate_token(auth_token);
882885
resultUrl += `?${token}`;
883886
}
@@ -1661,6 +1664,8 @@ function deferredPromise() {
16611664
reject = _reject;
16621665
});
16631666
applyQCompat(promise);
1667+
promise.catch(() => {
1668+
});
16641669
return {
16651670
resolve,
16661671
reject,

test/integration/api/authorization/oAuth_authorization_spec.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const testConstants = require('../../../testUtils/testConstants');
66
const { PUBLIC_IDS } = testConstants;
77
const { PUBLIC_ID } = PUBLIC_IDS;
88

9-
describe("oauth_token", function(){
10-
it("should send the oauth_token option to the server (admin_api)", function() {
9+
describe("oauth_token", function () {
10+
it("should send the oauth_token option to the server (admin_api)", function () {
1111
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
1212
await cloudinary.v2.api.resource(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }).catch(helper.ignoreApiFailure);
1313
sinon.assert.calledWith(requestSpy,
@@ -17,7 +17,7 @@ describe("oauth_token", function(){
1717
});
1818
});
1919

20-
it("should send the oauth_token config to the server (admin_api)", function() {
20+
it("should send the oauth_token config to the server (admin_api)", function () {
2121
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
2222
cloudinary.config({
2323
api_key: undefined,
@@ -32,19 +32,19 @@ describe("oauth_token", function(){
3232
});
3333
});
3434

35-
it("should not fail when only providing api_key and secret (admin_api)", function() {
35+
it("should not fail when only providing api_key and secret (admin_api)", function () {
3636
cloudinary.config({
3737
api_key: "1234",
3838
api_secret: "1234",
3939
oauth_token: undefined
4040
});
4141
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
4242
await cloudinary.v2.api.resource(PUBLIC_ID).catch(helper.ignoreApiFailure);
43-
sinon.assert.calledWith(requestSpy, sinon.match({ auth: "1234:1234" }));
43+
sinon.assert.calledWith(requestSpy, sinon.match.has("auth", "1234:1234"));
4444
});
4545
});
4646

47-
it("should fail when missing all credentials (admin_api)", function() {
47+
it("should fail when missing all credentials (admin_api)", function () {
4848
cloudinary.config({
4949
api_key: undefined,
5050
api_secret: undefined,
@@ -55,18 +55,18 @@ describe("oauth_token", function(){
5555
}).to.throwError(/Must supply api_key/);
5656
});
5757

58-
it("oauth_token as option should take priority with secret and key (admin_api)", function() {
58+
it("oauth_token as option should take priority with secret and key (admin_api)", function () {
5959
cloudinary.config({
6060
api_key: '1234',
6161
api_secret: '1234'
6262
});
63-
return cloudinary.v2.api.resource(PUBLIC_ID, {oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4'})
63+
return cloudinary.v2.api.resource(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' })
6464
.then(
6565
() => expect().fail()
6666
).catch(({ error }) => expect(error.message).to.contain("Invalid token"));
6767
});
6868

69-
it("should send the oauth_token option to the server (upload_api)", function() {
69+
it("should send the oauth_token option to the server (upload_api)", function () {
7070
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
7171
await cloudinary.v2.uploader.upload(PUBLIC_ID, { oauth_token: 'MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4' }).catch(helper.ignoreApiFailure);
7272
sinon.assert.calledWith(requestSpy,
@@ -76,7 +76,7 @@ describe("oauth_token", function(){
7676
});
7777
});
7878

79-
it("should send the oauth_token config to the server (upload_api)", function() {
79+
it("should send the oauth_token config to the server (upload_api)", function () {
8080
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
8181
cloudinary.config({
8282
api_key: undefined,
@@ -91,19 +91,20 @@ describe("oauth_token", function(){
9191
});
9292
});
9393

94-
it("should not fail when only providing api_key and secret (upload_api)", function() {
94+
it("should not fail when only providing api_key and secret (upload_api)", function () {
9595
cloudinary.config({
9696
api_key: "1234",
9797
api_secret: "1234",
9898
oauth_token: undefined
9999
});
100100
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
101101
await cloudinary.v2.uploader.upload(PUBLIC_ID).catch(helper.ignoreApiFailure);
102-
sinon.assert.calledWith(requestSpy, sinon.match({ auth: null }));
102+
const call = requestSpy.getCall(0);
103+
expect(call.args[0]).not.to.have.property('auth');
103104
});
104105
});
105106

106-
it("should fail when missing all credentials (upload_api)", function() {
107+
it("should fail when missing all credentials (upload_api)", function () {
107108
cloudinary.config({
108109
api_key: undefined,
109110
api_secret: undefined,
@@ -114,15 +115,16 @@ describe("oauth_token", function(){
114115
}).to.throwError(/Must supply api_key/);
115116
});
116117

117-
it("should not need credentials for unsigned upload", function() {
118+
it("should not need credentials for unsigned upload", function () {
118119
cloudinary.config({
119120
api_key: undefined,
120121
api_secret: undefined,
121122
oauth_token: undefined
122123
});
123124
return helper.provideMockObjects(async (mockXHR, writeSpy, requestSpy) => {
124125
await cloudinary.v2.uploader.unsigned_upload(PUBLIC_ID, 'preset').catch(helper.ignoreApiFailure);
125-
sinon.assert.calledWith(requestSpy, sinon.match({ auth: null }));
126+
const call = requestSpy.getCall(0);
127+
expect(call.args[0]).not.to.have.property('auth');
126128
});
127129
});
128130
});

0 commit comments

Comments
 (0)