| Index: pkg/pubserver/lib/shelf_pubserver.dart
|
| diff --git a/pkg/pubserver/lib/shelf_pubserver.dart b/pkg/pubserver/lib/shelf_pubserver.dart
|
| deleted file mode 100644
|
| index c54f1cb382dc97d7be5edd44edbc0a0da2f4ac26..0000000000000000000000000000000000000000
|
| --- a/pkg/pubserver/lib/shelf_pubserver.dart
|
| +++ /dev/null
|
| @@ -1,401 +0,0 @@
|
| -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -library pubserver.shelf_pubserver;
|
| -
|
| -import 'dart:async';
|
| -import 'dart:convert';
|
| -
|
| -import 'package:logging/logging.dart';
|
| -import 'package:mime/mime.dart';
|
| -import 'package:pub_semver/pub_semver.dart';
|
| -import 'package:shelf/shelf.dart' as shelf;
|
| -import 'package:yaml/yaml.dart';
|
| -
|
| -import 'repository.dart';
|
| -
|
| -final Logger _logger = new Logger('pubserver.shelf_pubserver');
|
| -
|
| -// TODO: Error handling from [PackageRepo] class.
|
| -// Distinguish between:
|
| -// - Unauthorized Error
|
| -// - Version Already Exists Error
|
| -// - Internal Server Error
|
| -/// A shelf handler for serving a pub [PackageRepository].
|
| -///
|
| -/// The following API endpoints are provided by this shelf handler:
|
| -///
|
| -/// * Getting information about all versions of a package.
|
| -///
|
| -/// GET /api/packages/<package-name>
|
| -/// [200 OK] [Content-Type: application/json]
|
| -/// {
|
| -/// "name" : "<package-name>",
|
| -/// "latest" : { ...},
|
| -/// "versions" : [
|
| -/// {
|
| -/// "version" : "<version>",
|
| -/// "archive_url" : "<download-url tar.gz>",
|
| -/// "pubspec" : {
|
| -/// "author" : ...,
|
| -/// "dependencies" : { ... },
|
| -/// ...
|
| -/// },
|
| -/// },
|
| -/// ...
|
| -/// ],
|
| -/// }
|
| -/// or
|
| -/// [404 Not Found]
|
| -///
|
| -/// * Getting information about a specific (package, version) pair.
|
| -///
|
| -/// GET /api/packages/<package-name>/versions/<version-name>
|
| -/// [200 OK] [Content-Type: application/json]
|
| -/// {
|
| -/// "version" : "<version>",
|
| -/// "archive_url" : "<download-url tar.gz>",
|
| -/// "pubspec" : {
|
| -/// "author" : ...,
|
| -/// "dependencies" : { ... },
|
| -/// ...
|
| -/// },
|
| -/// }
|
| -/// or
|
| -/// [404 Not Found]
|
| -///
|
| -/// * Downloading package.
|
| -///
|
| -/// GET /api/packages/<package-name>/versions/<version-name>.tar.gz
|
| -/// [200 OK] [Content-Type: octet-stream ??? FIXME ???]
|
| -/// or
|
| -/// [302 Found / Temporary Redirect]
|
| -/// Location: <new-location>
|
| -/// or
|
| -/// [404 Not Found]
|
| -///
|
| -/// * Uploading
|
| -///
|
| -/// GET /api/packages/versions/new
|
| -/// Headers:
|
| -/// Authorization: Bearer <oauth2-token>
|
| -/// [200 OK]
|
| -/// {
|
| -/// "fields" : {
|
| -/// "a": "...",
|
| -/// "b": "...",
|
| -/// ...
|
| -/// },
|
| -/// "url" : "https://storage.googleapis.com"
|
| -/// }
|
| -///
|
| -/// POST "https://storage.googleapis.com"
|
| -/// Headers:
|
| -/// a: ...
|
| -/// b: ...
|
| -/// ...
|
| -/// <multipart> file package.tar.gz
|
| -/// [302 Found / Temporary Redirect]
|
| -/// Location: https://pub.dartlang.org/finishUploadUrl
|
| -///
|
| -/// GET https://pub.dartlang.org/finishUploadUrl
|
| -/// [200 OK]
|
| -/// {
|
| -/// "success" : {
|
| -/// "message": "Successfully uploaded package.",
|
| -/// },
|
| -/// }
|
| -///
|
| -/// It will use the pub [PackageRepository] given in the constructor to provide
|
| -/// this HTTP endpoint.
|
| -class ShelfPubServer {
|
| - static final RegExp _packageRegexp =
|
| - new RegExp(r'^/api/packages/([^/]+)$');
|
| -
|
| - static final RegExp _versionRegexp =
|
| - new RegExp(r'^/api/packages/([^/]+)/versions/([^/]+)$');
|
| -
|
| - static final RegExp _downloadRegexp =
|
| - new RegExp(r'^/packages/([^/]+)/versions/([^/]+)\.tar\.gz$');
|
| -
|
| - static final RegExp _boundaryRegExp = new RegExp(r'^.*boundary="([^"]+)"$');
|
| -
|
| -
|
| - final PackageRepository repository;
|
| -
|
| - ShelfPubServer(this.repository);
|
| -
|
| -
|
| - Future<shelf.Response> requestHandler(shelf.Request request) {
|
| - String path = request.requestedUri.path;
|
| - if (request.method == 'GET') {
|
| - var downloadMatch = _downloadRegexp.matchAsPrefix(path);
|
| - if (downloadMatch != null) {
|
| - var package = Uri.decodeComponent(downloadMatch.group(1));
|
| - var version = Uri.decodeComponent(downloadMatch.group(2));
|
| - return _download(request.requestedUri, package, version);
|
| - }
|
| -
|
| - var packageMatch = _packageRegexp.matchAsPrefix(path);
|
| - if (packageMatch != null) {
|
| - var package = Uri.decodeComponent(packageMatch.group(1));
|
| - return _listVersions(request.requestedUri, package);
|
| - }
|
| -
|
| - var versionMatch = _versionRegexp.matchAsPrefix(path);
|
| - if (versionMatch != null) {
|
| - var package = Uri.decodeComponent(versionMatch.group(1));
|
| - var version = Uri.decodeComponent(versionMatch.group(2));
|
| - return _showVersion(request.requestedUri, package, version);
|
| - }
|
| -
|
| - if (path == '/api/packages/versions/new') {
|
| - if (!repository.supportsUpload) {
|
| - return new Future.value(new shelf.Response.notFound(null));
|
| - }
|
| -
|
| - if (repository.supportsAsyncUpload) {
|
| - return _startUploadAsync(request.requestedUri);
|
| - } else {
|
| - return _startUploadSimple(request.requestedUri);
|
| - }
|
| - }
|
| -
|
| - if (path == '/api/packages/versions/newUploadFinish') {
|
| - if (!repository.supportsUpload) {
|
| - return new Future.value(new shelf.Response.notFound(null));
|
| - }
|
| -
|
| - if (repository.supportsAsyncUpload) {
|
| - return _finishUploadAsync(request.requestedUri);
|
| - } else {
|
| - return _finishUploadSimple(request.requestedUri);
|
| - }
|
| - }
|
| - } else if (request.method == 'POST') {
|
| - if (!repository.supportsUpload) {
|
| - return new Future.value(new shelf.Response.notFound(null));
|
| - }
|
| -
|
| - if (path == '/api/packages/versions/newUpload') {
|
| - return _uploadSimple(
|
| - request.requestedUri,
|
| - request.headers['content-type'],
|
| - request.read());
|
| - }
|
| - }
|
| - return new Future.value(new shelf.Response.notFound(null));
|
| - }
|
| -
|
| -
|
| - // Metadata handlers.
|
| -
|
| - Future<shelf.Response> _listVersions(Uri uri, String package) {
|
| - return repository.versions(package).toList()
|
| - .then((List<PackageVersion> packageVersions) {
|
| - if (packageVersions.length == 0) {
|
| - return new shelf.Response.notFound(null);
|
| - }
|
| -
|
| - packageVersions.sort((a, b) => a.version.compareTo(b.version));
|
| -
|
| - // TODO: Add legacy entries (if necessary), such as version_url.
|
| - Map packageVersion2Json(PackageVersion version) {
|
| - return {
|
| - 'archive_url':
|
| - '${_downloadUrl(
|
| - uri, version.packageName, version.versionString)}',
|
| - 'pubspec': loadYaml(version.pubspecYaml),
|
| - 'version': version.versionString,
|
| - };
|
| - }
|
| -
|
| - var latestVersion = packageVersions.last;
|
| - for (int i = packageVersions.length - 1; i >= 0; i--) {
|
| - if (!packageVersions[i].version.isPreRelease) {
|
| - latestVersion = packageVersions[i];
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // TODO: The 'latest' is something we should get rid of, since it's
|
| - // duplicated in 'versions'.
|
| - return _jsonResponse({
|
| - 'name' : package,
|
| - 'latest' : packageVersion2Json(latestVersion),
|
| - 'versions' : packageVersions.map(packageVersion2Json).toList(),
|
| - });
|
| - });
|
| - }
|
| -
|
| - Future<shelf.Response> _showVersion(Uri uri, String package, String version) {
|
| - return repository
|
| - .lookupVersion(package, version).then((PackageVersion version) {
|
| - if (version == null) {
|
| - return new shelf.Response.notFound('');
|
| - }
|
| -
|
| - // TODO: Add legacy entries (if necessary), such as version_url.
|
| - return _jsonResponse({
|
| - 'archive_url':
|
| - '${_downloadUrl(
|
| - uri, version.packageName, version.versionString)}',
|
| - 'pubspec': loadYaml(version.pubspecYaml),
|
| - 'version': version.versionString,
|
| - });
|
| - });
|
| - }
|
| -
|
| -
|
| - // Download handlers.
|
| -
|
| - Future<shelf.Response> _download(Uri uri, String package, String version) {
|
| - if (repository.supportsDownloadUrl) {
|
| - return repository.downloadUrl(package, version).then((Uri url) {
|
| - // This is a redirect to [url]
|
| - return new shelf.Response.seeOther(url);
|
| - });
|
| - }
|
| - return repository.download(package, version).then((stream) {
|
| - return new shelf.Response.ok(stream);
|
| - });
|
| - }
|
| -
|
| -
|
| - // Upload async handlers.
|
| -
|
| - Future<shelf.Response> _startUploadAsync(Uri uri) {
|
| - return repository.startAsyncUpload(_finishUploadAsyncUrl(uri))
|
| - .then((AsyncUploadInfo info) {
|
| - return _jsonResponse({
|
| - 'url' : '${info.uri}',
|
| - 'fields' : info.fields,
|
| - });
|
| - });
|
| - }
|
| -
|
| - Future<shelf.Response> _finishUploadAsync(Uri uri) {
|
| - return repository.finishAsyncUpload(uri).then((_) {
|
| - return _jsonResponse({
|
| - 'success' : {
|
| - 'message' : 'Successfully uploaded package.',
|
| - },
|
| - });
|
| - }).catchError((error, stack) {
|
| - return _jsonResponse({
|
| - 'error' : {
|
| - 'message' : '$error.',
|
| - },
|
| - }, status: 400);
|
| - });
|
| - }
|
| -
|
| -
|
| - // Upload custom handlers.
|
| -
|
| - Future<shelf.Response> _startUploadSimple(Uri url) {
|
| - _logger.info('Start simple upload.');
|
| - return _jsonResponse({
|
| - 'url' : '${_uploadSimpleUrl(url)}',
|
| - 'fields' : {},
|
| - });
|
| - }
|
| -
|
| - Future<shelf.Response> _uploadSimple(
|
| - Uri uri, String contentType, Stream<List<int>> stream) {
|
| - _logger.info('Perform simple upload.');
|
| - if (contentType.startsWith('multipart/form-data')) {
|
| - var match = _boundaryRegExp.matchAsPrefix(contentType);
|
| - if (match != null) {
|
| - var boundary = match.group(1);
|
| - return stream
|
| - .transform(new MimeMultipartTransformer(boundary))
|
| - .first.then((MimeMultipart part) {
|
| - // TODO: Ensure that `part.headers['content-disposition']` is
|
| - // `form-data; name="file"; filename="package.tar.gz`
|
| - return repository.upload(part).then((_) {
|
| - return new shelf.Response.found(_finishUploadSimpleUrl(uri));
|
| - }).catchError((error, stack) {
|
| - // TODO: Do error checking and return error codes?
|
| - return new shelf.Response.found(
|
| - _finishUploadSimpleUrl(uri, error: error));
|
| - });
|
| - });
|
| - }
|
| - }
|
| - return
|
| - _badRequest('Upload must contain a multipart/form-data content type.');
|
| - }
|
| -
|
| - Future<shelf.Response> _finishUploadSimple(Uri uri) {
|
| - var error = uri.queryParameters['error'];
|
| - _logger.info('Finish simple upload (error: $error).');
|
| - if (error != null) {
|
| - return _jsonResponse(
|
| - { 'error' : { 'message' : error } }, status: 400);
|
| - }
|
| - return _jsonResponse(
|
| - { 'success' : { 'message' : 'Successfully uploaded package.' } });
|
| - }
|
| -
|
| -
|
| - // Helper functions.
|
| -
|
| - Future<shelf.Response> _badRequest(String message) {
|
| - return new Future.value(new shelf.Response(
|
| - 400,
|
| - body: JSON.encode({ 'error' : message }),
|
| - headers: {'content-type': 'application/json'}));
|
| - }
|
| -
|
| - Future<shelf.Response> _jsonResponse(Map json, {int status: 200}) {
|
| - return new Future.sync(() {
|
| - return new shelf.Response(status,
|
| - body: JSON.encode(json),
|
| - headers: {'content-type': 'application/json'});
|
| - });
|
| - }
|
| -
|
| -
|
| - // Metadata urls.
|
| -
|
| - Uri _packageUrl(Uri url, String package) {
|
| - var encode = Uri.encodeComponent;
|
| - return url.resolve('/api/packages/${encode(package)}');
|
| - }
|
| -
|
| -
|
| - // Download urls.
|
| -
|
| - Uri _downloadUrl(Uri url, String package, String version) {
|
| - var encode = Uri.encodeComponent;
|
| - return url.resolve(
|
| - '/packages/${encode(package)}/versions/${encode(version)}.tar.gz');
|
| - }
|
| -
|
| -
|
| - // Upload async urls.
|
| -
|
| - Uri _startUploadAsyncUrl(Uri url) {
|
| - var encode = Uri.encodeComponent;
|
| - return url.resolve('/api/packages/versions/new');
|
| - }
|
| -
|
| - Uri _finishUploadAsyncUrl(Uri url) {
|
| - var encode = Uri.encodeComponent;
|
| - return url.resolve('/api/packages/versions/newUploadFinish');
|
| - }
|
| -
|
| -
|
| - // Upload custom urls.
|
| -
|
| - Uri _uploadSimpleUrl(Uri url) {
|
| - return url.resolve('/api/packages/versions/newUpload');
|
| - }
|
| -
|
| - Uri _finishUploadSimpleUrl(Uri url, {String error}) {
|
| - var postfix = error == null ? '' : '?error=${Uri.encodeComponent(error)}';
|
| - return url.resolve('/api/packages/versions/newUploadFinish$postfix');
|
| - }
|
| -}
|
|
|