Skip to content

Commit

Permalink
feat: add file.isPublic() function (#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
AVaksman committed May 30, 2019
1 parent b2f8a74 commit f86cadb
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 21 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"date-and-time": "^0.6.3",
"duplexify": "^3.5.0",
"extend": "^3.0.0",
"gaxios": "^2.0.1",
"gcs-resumable-upload": "^2.0.0",
"hash-stream-validation": "^0.2.1",
"mime": "^2.2.0",
Expand Down
74 changes: 73 additions & 1 deletion src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
} from '@google-cloud/common/build/src/util';
const duplexify: DuplexifyConstructor = require('duplexify');
import {normalize, objectEntries} from './util';
import {Headers} from 'gaxios';
import {GaxiosError, Headers, request as gaxiosRequest} from 'gaxios';

export type GetExpirationDateResponse = [Date];
export interface GetExpirationDateCallback {
Expand Down Expand Up @@ -211,6 +211,12 @@ export type MakeFilePrivateResponse = [Metadata];

export interface MakeFilePrivateCallback extends SetFileMetadataCallback {}

export interface IsPublicCallback {
(err: Error | null, resp?: boolean): void;
}

export type IsPublicResponse = [boolean];

export type MakeFilePublicResponse = [Metadata];

export interface MakeFilePublicCallback {
Expand Down Expand Up @@ -2595,6 +2601,72 @@ class File extends ServiceObject<File> {
});
}

isPublic(): Promise<IsPublicResponse>;
isPublic(callback: IsPublicCallback): void;
/**
* @callback IsPublicCallback
* @param {?Error} err Request error, if any.
* @param {boolean} resp Whether file is public or not.
*/
/**
* @typedef {array} IsPublicResponse
* @property {boolean} 0 Whether file is public or not.
*/
/**
* Check whether this file is public or not by sending
* a HEAD request without credentials.
* No errors from the server indicates that the current
* file is public.
* A 403-Forbidden error {@link https://cloud.google.com/storage/docs/json_api/v1/status-codes#403_Forbidden}
* indicates that file is private.
* Any other non 403 error is propagated to user.
*
* @param {IsPublicCallback} [callback] Callback function.
* @returns {Promise<IsPublicResponse>}
*
* @example
* const {Storage} = require('@google-cloud/storage');
* const storage = new Storage();
* const myBucket = storage.bucket('my-bucket');
*
* const file = myBucket.file('my-file');
*
* //-
* // Check whether the file is publicly accessible.
* //-
* file.isPublic(function(err, resp) {
* if (err) {
* console.error(err);
* return;
* }
* console.log(`the file ${file.id} is public: ${resp}`) ;
* })
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* file.isPublic().then(function(data) {
* const resp = data[0];
* });
*/

isPublic(callback?: IsPublicCallback): Promise<IsPublicResponse> | void {
gaxiosRequest({
method: 'HEAD',
url: `http://${
this.bucket.name
}.storage.googleapis.com/${encodeURIComponent(this.name)}`,
}).then(
() => callback!(null, true),
(err: GaxiosError) => {
if (err.code === '403') {
callback!(null, false);
} else {
callback!(err);
}
}
);
}

makePrivate(
options?: MakeFilePrivateOptions
): Promise<MakeFilePrivateResponse>;
Expand Down
38 changes: 18 additions & 20 deletions system-test/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,17 +209,12 @@ describe('storage', () => {
bucket = storageWithoutAuth.bucket('gcp-public-data-landsat');
});

it('should list and download a file', done => {
bucket.getFiles(
{
autoPaginate: false,
},
(err, files) => {
assert.ifError(err);
const file = files![0];
file.download(done);
}
);
it('should list and download a file', async () => {
const [files] = await bucket.getFiles({autoPaginate: false});
const file = files[0];
const [isPublic] = await file.isPublic();
assert.strictEqual(isPublic, true);
assert.doesNotReject(file.download());
});
});

Expand All @@ -232,13 +227,14 @@ describe('storage', () => {
file = bucket.file(privateFile.id!);
});

it('should not download a file', done => {
file.download(err => {
assert(
err!.message.indexOf('does not have storage.objects.get') > -1
);
done();
});
it('should not download a file', async () => {
const [isPublic] = await file.isPublic();
assert.strictEqual(isPublic, false);
assert.rejects(
file.download(),
(err: Error) =>
err.message.indexOf('does not have storage.objects.get') > -1
);
});

it('should not upload a file', done => {
Expand Down Expand Up @@ -390,7 +386,7 @@ describe('storage', () => {
const resps = await Promise.all(
files.map(file => isFilePublicAsync(file))
);
resps.forEach(resp => assert.ok(resp));
resps.forEach(resp => assert.strictEqual(resp, true));
await Promise.all([
bucket.acl.default.delete({entity: 'allUsers'}),
bucket.deleteFiles(),
Expand Down Expand Up @@ -422,7 +418,9 @@ describe('storage', () => {
const resps = await Promise.all(
files.map(file => isFilePublicAsync(file))
);
resps.forEach(resp => assert.ok(!resp));
resps.forEach(resp => {
assert.strictEqual(resp, false);
});
await bucket.deleteFiles();
});
});
Expand Down
56 changes: 56 additions & 0 deletions test/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import * as through from 'through2';
import * as tmp from 'tmp';
import * as url from 'url';
import * as zlib from 'zlib';
import * as gaxios from 'gaxios';

import {
Bucket,
Expand Down Expand Up @@ -3205,6 +3206,61 @@ describe('File', () => {
});
});

describe('isPublic', () => {
const sandbox = sinon.createSandbox();

afterEach(() => sandbox.restore());

it('should execute callback with `true` in response', done => {
sandbox.stub(gaxios, 'request').resolves();
file.isPublic((err: gaxios.GaxiosError, resp: boolean) => {
assert.ifError(err);
assert.strictEqual(resp, true);
done();
});
});

it('should execute callback with `false` in response', done => {
sandbox.stub(gaxios, 'request').rejects({code: '403'});
file.isPublic((err: gaxios.GaxiosError, resp: boolean) => {
assert.ifError(err);
assert.strictEqual(resp, false);
done();
});
});

it('should propagate non-403 errors to user', done => {
const error = {code: '400'};
sandbox.stub(gaxios, 'request').rejects(error as gaxios.GaxiosError);
file.isPublic((err: gaxios.GaxiosError) => {
assert.strictEqual(err, error);
done();
});
});

it('should correctly send a HEAD request', done => {
const spy = sandbox.spy(gaxios, 'request');
file.isPublic((err: gaxios.GaxiosError) => {
assert.ifError(err);
assert.strictEqual(spy.calledWithMatch({method: 'HEAD'}), true);
done();
});
});

it('should correctly format URL in the request', done => {
file = new File(BUCKET, 'my#file$.png');
const expecterURL = `http://${
BUCKET.name
}.storage.googleapis.com/${encodeURIComponent(file.name)}`;
const spy = sandbox.spy(gaxios, 'request');
file.isPublic((err: gaxios.GaxiosError) => {
assert.ifError(err);
assert.strictEqual(spy.calledWithMatch({url: expecterURL}), true);
done();
});
});
});

describe('move', () => {
describe('copy to destination', () => {
function assertCopyFile(
Expand Down

0 comments on commit f86cadb

Please sign in to comment.