Skip to content

Commit

Permalink
Display error messages from GCS
Browse files Browse the repository at this point in the history
  • Loading branch information
sigurdm committed Sep 14, 2023
1 parent 4ace350 commit 9457b2c
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 11 deletions.
7 changes: 2 additions & 5 deletions lib/src/authentication/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:http_parser/http_parser.dart';
import '../http.dart';
import '../log.dart' as log;
import '../system_cache.dart';
import '../utils.dart';
import 'credential.dart';

/// This client authenticates requests by injecting `Authentication` header to
Expand Down Expand Up @@ -83,11 +84,7 @@ class _AuthenticatedClient extends http.BaseClient {
}
}
if (serverMessage != null) {
// Only allow printable ASCII, map anything else to whitespace, take
// at-most 1024 characters.
serverMessage = String.fromCharCodes(
serverMessage.runes.map((r) => 32 <= r && r <= 127 ? r : 32).take(1024),
);
serverMessage = sanitizeForTerminal(serverMessage);
}
throw AuthenticationException(response.statusCode, serverMessage);
}
Expand Down
4 changes: 1 addition & 3 deletions lib/src/command/lish.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,7 @@ class LishCommand extends PubCommand {
} on PubHttpResponseException catch (error) {
var url = error.response.request!.url;
if (url == cloudStorageUrl) {
// TODO(nweiz): the response may have XML-formatted information about
// the error. Try to parse that out once we have an easily-accessible
// XML parser.
handleGCSError(error.response);
fail(log.red('Failed to upload the package.'));
} else if (Uri.parse(url.origin) == Uri.parse(host.origin)) {
handleJsonError(error.response);
Expand Down
34 changes: 34 additions & 0 deletions lib/src/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,40 @@ void handleJsonError(http.BaseResponse response) {
fail(log.red(error['message'] as String));
}

/// Handles an unsuccessful XML-formatted response from google cloud storage.
///
/// Assumes messages are of the form in
/// https://cloud.google.com/storage/docs/xml-api/reference-status.
///
/// This is a poor person's XML parsing with regexps, but this should be
/// sufficient for the specified messages.
void handleGCSError(http.BaseResponse response) {
if (response is http.Response) {
final responseBody = response.body;
if (responseBody.contains('<?xml')) {
String? getTagText(String tag) {
final result = RegExp('<$tag>(.*)</$tag>').firstMatch(responseBody)?[1];
if (result == null) return null;
return sanitizeForTerminal(result);
}

final code = getTagText('Code');
final message = getTagText('Message');
// `Details` are not specified in the doc above, but have been observed in actual responses.
final details = getTagText('Details');
if (code != null) {
log.error('Server error code: $code');
}
if (message != null) {
log.error('Server message: $message');
}
if (details != null) {
log.error('Server details: $details');
}
}
}
}

/// Parses a response body, assuming it's JSON-formatted.
///
/// Throws a user-friendly error if the response body is invalid JSON, or if
Expand Down
10 changes: 10 additions & 0 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,13 @@ extension RetrieveFlags on ArgResults {
String option(String name) => this[name] as String;
String? optionWithoutDefault(String name) => this[name] as String?;
}

/// Limits the range of characters and length.
///
/// Useful for displaying externally provided strings.
///
/// Only allowd printable ASCII, map anything else to whitespace, take at-most
/// 1024 characters.
String sanitizeForTerminal(String input) => String.fromCharCodes(
input.runes.map((r) => 32 <= r && r <= 127 ? r : 32).take(1024),
);
22 changes: 19 additions & 3 deletions test/lish/cloud_storage_upload_provides_an_error_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,30 @@ void main() {
globalServer.expect('POST', '/upload', (request) {
return request.read().drain().then((_) {
return shelf.Response.notFound(
'<Error><Message>Your request sucked.</Message></Error>',
// Actual example of an error code we get from GCS
"<?xml version='1.0' encoding='UTF-8'?><Error><Code>EntityTooLarge</Code><Message>Your proposed upload is larger than the maximum object size specified in your Policy Document.</Message><Details>Content-length exceeds upper bound on range</Details></Error>",
headers: {'content-type': 'application/xml'},
);
});
});

// TODO(nweiz): This should use the server's error message once the client
// can parse the XML.
expect(
pub.stderr,
emits(
'Server error code: EntityTooLarge',
),
);
expect(
pub.stderr,
emits(
'Server message: Your proposed upload is larger than the maximum object size specified in your Policy Document.',
),
);
expect(
pub.stderr,
emits('Server details: Content-length exceeds upper bound on range'),
);

expect(pub.stderr, emits('Failed to upload the package.'));
await pub.shouldExit(1);
});
Expand Down

0 comments on commit 9457b2c

Please sign in to comment.