Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/storage/storage-blob/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 12.2.0 (Unreleased)

- Added RehydratePriority to BlobProperties and BlobItemProperties.
- Added custom domain support.

## 12.2.0-preview.1 (2020.07)

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 16 additions & 4 deletions sdk/storage/storage-blob/src/Clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,11 @@ import {
ETagNone
} from "./utils/constants";
import {
setURLParameter,
extractConnectionStringParts,
appendToURLPath,
extractConnectionStringParts,
generateBlockID,
isIpEndpointStyle,
setURLParameter,
toBlobTagsString,
toQuerySerialization,
truncatedISO8061Date,
Expand Down Expand Up @@ -2196,13 +2197,19 @@ export class BlobClient extends StorageClient {
const pathComponents = parsedUrl.getPath()!.match("/([^/]*)(/(.*))?");
containerName = pathComponents![1];
blobName = pathComponents![3];
} else {
} else if (isIpEndpointStyle(parsedUrl)) {
// IPv4/IPv6 address hosts... Example - http://192.0.0.10:10001/devstoreaccount1/containername/blob
// Single word domain without a [dot] in the endpoint... Example - http://localhost:10001/devstoreaccount1/containername/blob
// .getPath() -> /devstoreaccount1/containername/blob
const pathComponents = parsedUrl.getPath()!.match("/([^/]*)/([^/]*)(/(.*))?");
containerName = pathComponents![2];
blobName = pathComponents![4];
} else {
// "https://customdomain.com/containername/blob".
// .getPath() -> /containername/blob
const pathComponents = parsedUrl.getPath()!.match("/([^/]*)(/(.*))?");
containerName = pathComponents![1];
blobName = pathComponents![3];
}

// decode the encoded blobName, containerName - to get all the special characters that might be present in them
Expand Down Expand Up @@ -7910,13 +7917,18 @@ export class ContainerClient extends StorageClient {

if (parsedUrl.getHost()!.split(".")[1] === "blob") {
// "https://myaccount.blob.core.windows.net/containername".
// "https://customdomain.com/containername".
// .getPath() -> /containername
containerName = parsedUrl.getPath()!.split("/")[1];
} else {
} else if (isIpEndpointStyle(parsedUrl)) {
// IPv4/IPv6 address hosts... Example - http://192.0.0.10:10001/devstoreaccount1/containername
// Single word domain without a [dot] in the endpoint... Example - http://localhost:10001/devstoreaccount1/containername
// .getPath() -> /devstoreaccount1/containername
containerName = parsedUrl.getPath()!.split("/")[2];
} else {
// "https://customdomain.com/containername".
// .getPath() -> /containername
containerName = parsedUrl.getPath()!.split("/")[1];
}

// decode the encoded containerName - to get all the special characters that might be present in it
Expand Down
27 changes: 19 additions & 8 deletions sdk/storage/storage-blob/src/utils/utils.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export function extractConnectionStringParts(connectionString: string): Connecti
url: blobEndpoint,
accountName,
accountKey,
proxyUri
proxyUri,
};
} else {
// SAS connection string
Expand All @@ -197,8 +197,6 @@ export function extractConnectionStringParts(connectionString: string): Connecti
throw new Error("Invalid BlobEndpoint in the provided SAS Connection String");
} else if (!accountSas) {
throw new Error("Invalid SharedAccessSignature in the provided SAS Connection String");
} else if (!accountName) {
throw new Error("Invalid AccountName in the provided SAS Connection String");
}

return { kind: "SASConnString", url: blobEndpoint, accountName, accountSas };
Expand Down Expand Up @@ -541,22 +539,35 @@ export function getAccountNameFromUrl(url: string): string {
if (parsedUrl.getHost()!.split(".")[1] === "blob") {
// `${defaultEndpointsProtocol}://${accountName}.blob.${endpointSuffix}`;
accountName = parsedUrl.getHost()!.split(".")[0];
} else {
} else if (isIpEndpointStyle(parsedUrl)) {
// IPv4/IPv6 address hosts... Example - http://192.0.0.10:10001/devstoreaccount1/
// Single word domain without a [dot] in the endpoint... Example - http://localhost:10001/devstoreaccount1/
// .getPath() -> /devstoreaccount1/
accountName = parsedUrl.getPath()!.split("/")[1];
}

if (!accountName) {
throw new Error("Provided accountName is invalid.");
} else {
// Custom domain case: "https://customdomain.com/containername/blob".
accountName = '';
}
return accountName;
} catch (error) {
throw new Error("Unable to extract accountName with provided information.");
}
}

export function isIpEndpointStyle(parsedUrl: URLBuilder): boolean {
Comment thread
jiacfan marked this conversation as resolved.
if (parsedUrl.getHost() == undefined) {
return false;
}

const host = parsedUrl.getHost()! + (parsedUrl.getPort() == undefined ? '' : ':' + parsedUrl.getPort());

// Case 1: Ipv6, use a broad regex to find out candidates whose host contains two ':'.
// Case 2: localhost(:port), use broad regex to match port part.
// Case 3: Ipv4, use broad regex which just check if host contains Ipv4.
// For valid host please refer to https://man7.org/linux/man-pages/man7/hostname.7.html.
return /^.*:.*:.*$|^localhost(:[0-9]+)?$|^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])){3}(:[0-9]+)?$/.test(host);
}

/**
* Convert Tags to encoded string.
*
Expand Down
7 changes: 7 additions & 0 deletions sdk/storage/storage-blob/test/blobclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,4 +844,11 @@ describe("BlobClient - Verify Name Properties", () => {
it("verify endpoint without dots", async () => {
verifyNameProperties(`https://localhost:80/${accountName}/${containerName}/${blobName}`);
});

it("verify custom endpoint without valid accountName", async () => {
const newClient = new BlobClient(`https://customdomain.com/${containerName}/${blobName}`);
assert.equal(newClient.accountName, "", "Account name is not the same as expected.");
assert.equal(newClient.containerName, containerName, "Container name is not the same as the one provided.");
assert.equal(newClient.name, blobName, "Blob name is not the same as the one provided.");
});
});
5 changes: 5 additions & 0 deletions sdk/storage/storage-blob/test/blobserviceclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,4 +537,9 @@ describe("BlobServiceClient", () => {

await containerClient.delete();
});

it("verify custom endpoint without valid accountName", async () => {
const newClient = new BlobServiceClient(`https://customdomain.com`);
assert.equal(newClient.accountName, "", "Account name is not the same as expected.");
});
});
6 changes: 6 additions & 0 deletions sdk/storage/storage-blob/test/containerclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,4 +827,10 @@ describe("ContainerClient - Verify Name Properties", () => {
it("verify endpoint without dots", async () => {
verifyNameProperties(`https://localhost:80/${accountName}/${containerName}`);
});

it("verify custom endpoint without valid accountName", async () => {
const newClient = new ContainerClient(`https://customdomain.com/${containerName}`);
assert.equal(newClient.accountName, "", "Account name is not the same as expected.");
assert.equal(newClient.containerName, containerName, "Container name is not the same as the one provided.");
});
});
74 changes: 73 additions & 1 deletion sdk/storage/storage-blob/test/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { HttpHeaders } from "../src";
import {
sanitizeHeaders,
sanitizeURL,
extractConnectionStringParts
extractConnectionStringParts,
isIpEndpointStyle
} from "../src/utils/utils.common";
import { URLBuilder } from "@azure/core-http";
dotenv.config();

describe("Utility Helpers", () => {
const protocol = "https";
const endpointSuffix = "core.windows.net";
const accountName = "myaccount";
const blobEndpoint = `${protocol}://${accountName}.blob.${endpointSuffix}`;
const customDomainBlobEndpoint = `${protocol}://customdomain.com`;
const sharedAccessSignature = "sasToken";

function verifySASConnectionString(sasConnectionString: string) {
Expand All @@ -27,6 +30,11 @@ describe("Utility Helpers", () => {
connectionStringParts.url,
"extractConnectionStringParts().url is different than expected."
);
assert.equal(
accountName,
connectionStringParts.accountName,
"extractConnectionStringParts().accountName is different than expected."
);
}

it("sanitizeURL redacts SAS token", () => {
Expand Down Expand Up @@ -78,4 +86,68 @@ describe("Utility Helpers", () => {
SharedAccessSignature=${sharedAccessSignature}`
);
});

it("extractConnectionStringParts parses sas connection string with custom domain", async () => {
const sasConnectionString = `BlobEndpoint=${customDomainBlobEndpoint};
SharedAccessSignature=${sharedAccessSignature}`
const connectionStringParts = extractConnectionStringParts(sasConnectionString);
assert.equal(
"SASConnString",
connectionStringParts.kind,
"extractConnectionStringParts().kind is different than expected."
);
assert.equal(
customDomainBlobEndpoint,
connectionStringParts.url,
"extractConnectionStringParts().url is different than expected."
);
assert.equal(
'',
connectionStringParts.accountName,
"extractConnectionStringParts().accountName is different than expected."
);
});

it("isIpEndpointStyle", async () => {
assert.equal(
isIpEndpointStyle(
URLBuilder.parse("https://192.0.0.10:1900/accountName/containerName/blobName")
),
true
);
assert.equal(
isIpEndpointStyle(
URLBuilder.parse(
"https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/accountName/containerName/blobName"
)
),
true
);
assert.equal(
isIpEndpointStyle(
URLBuilder.parse("https://localhost:80/accountName/containerName/blobName")
),
true
);

assert.equal(isIpEndpointStyle(URLBuilder.parse("https://192.0.0.10:1900/")), true);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://192.0.0.10")), true);

assert.equal(
isIpEndpointStyle(
URLBuilder.parse("https://2001:db8:85a3:8d3:1319:8a2e:370:7348/accountName/containerName")
),
true
);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://2001::1")), true);
// assert.equal(isIpEndpointStyle(URLBuilder.parse('https://::1')), true); currently not working due to http url.ts's issue. uncomment after core lib fixed.

assert.equal(isIpEndpointStyle(URLBuilder.parse("https://255")), false);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://255.255")), false);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://a.b.c.d")), false);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://256.1.1.1")), false);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://255.256.1.1")), false);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://255.255.256.1")), false);
assert.equal(isIpEndpointStyle(URLBuilder.parse("https://255.255.255.256")), false);
});
});
24 changes: 18 additions & 6 deletions sdk/storage/storage-file-datalake/src/utils/utils.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,6 @@ export function extractConnectionStringParts(connectionString: string): Connecti
throw new Error("Invalid BlobEndpoint in the provided SAS Connection String");
} else if (!accountSas) {
throw new Error("Invalid SharedAccessSignature in the provided SAS Connection String");
} else if (!accountName) {
throw new Error("Invalid AccountName in the provided SAS Connection String");
}

return { kind: "SASConnString", url: blobEndpoint, accountName, accountSas };
Expand Down Expand Up @@ -551,18 +549,32 @@ export function getAccountNameFromUrl(blobEndpointUrl: string): string {
if (parsedUrl.getHost()!.split(".")[1] === "blob") {
// `${defaultEndpointsProtocol}://${accountName}.blob.${endpointSuffix}`;
accountName = parsedUrl.getHost()!.split(".")[0];
} else {
} else if (isIpEndpointStyle(parsedUrl)) {
// IPv4/IPv6 address hosts... Example - http://192.0.0.10:10001/devstoreaccount1/
// Single word domain without a [dot] in the endpoint... Example - http://localhost:10001/devstoreaccount1/
// .getPath() -> /devstoreaccount1/
accountName = parsedUrl.getPath()!.split("/")[1];
} else {
// Custom domain case: "https://customdomain.com/containername/blob".
accountName = '';
}

if (!accountName) {
throw new Error("Provided accountName is invalid.");
}
return accountName;
} catch (error) {
throw new Error("Unable to extract accountName with provided information.");
}
}

export function isIpEndpointStyle(parsedUrl: URLBuilder): boolean {
if (parsedUrl.getHost() == undefined) {
return false;
}

const host = parsedUrl.getHost()! + (parsedUrl.getPort() == undefined ? '' : ':' + parsedUrl.getPort());

// Case 1: Ipv6, use a broad regex to find out candidates whose host contains two ':'.
// Case 2: localhost(:port), use broad regex to match port part.
// Case 3: Ipv4, use broad regex which just check if host contains Ipv4.
// For valid host please refer to https://man7.org/linux/man-pages/man7/hostname.7.html.
return /^.*:.*:.*$|^localhost(:[0-9]+)?$|^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])){3}(:[0-9]+)?$/.test(host);
}
5 changes: 5 additions & 0 deletions sdk/storage/storage-file-datalake/test/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ describe("Utility Helpers", () => {
connectionStringParts.url,
"extractConnectionStringParts().url is different than expected."
);
assert.equal(
accountName,
connectionStringParts.accountName,
"extractConnectionStringParts().accountName is different than expected."
);
}

beforeEach(function() {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading