Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
29 changes: 20 additions & 9 deletions sdk/storage/storage-blob/src/utils/utils.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,19 +186,17 @@ export function extractConnectionStringParts(connectionString: string): Connecti
url: blobEndpoint,
accountName,
accountKey,
proxyUri
proxyUri,
};
} else {
// SAS connection string

let accountSas = getValueInConnString(connectionString, "SharedAccessSignature");
let accountName = getAccountNameFromUrl(blobEndpoint);
let accountName = getValueInConnString(connectionString, "AccountName");
Comment thread
jiacfan marked this conversation as resolved.
Outdated
if (!blobEndpoint) {
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.");
});
});
91 changes: 90 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 Down Expand Up @@ -78,4 +81,90 @@ 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("extractConnectionStringParts parses sas connection string with custom domain", async () => {
const sasConnectionString = `BlobEndpoint=${customDomainBlobEndpoint};
SharedAccessSignature=${sharedAccessSignature};
AccountName=${accountName}`
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(
accountName,
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);
});
});
26 changes: 19 additions & 7 deletions sdk/storage/storage-file-datalake/src/utils/utils.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,11 @@ export function extractConnectionStringParts(connectionString: string): Connecti
// SAS connection string

let accountSas = getValueInConnString(connectionString, "SharedAccessSignature");
let accountName = getAccountNameFromUrl(blobEndpoint);
let accountName = getValueInConnString(connectionString, "AccountName");
if (!blobEndpoint) {
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);
}
Loading