1 /**
2 	Downloading and uploading of data from/to URLs.
3 
4 	Note that this module is scheduled for deprecation and will be replaced by
5 	another module in the future. All functions are defined as templates to
6 	avoid this dependency issue when building the library.
7 
8 	Copyright: © 2012-2015 Sönke Ludwig
9 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
10 	Authors: Sönke Ludwig
11 */
12 module vibe.inet.urltransfer;
13 
14 import vibe.core.log;
15 import vibe.core.file;
16 import vibe.inet.url;
17 import vibe.core.stream;
18 import vibe.internal.interfaceproxy : asInterface;
19 
20 import std.exception;
21 import std.string;
22 
23 
24 /**
25 	Downloads a file from the specified URL.
26 
27 	Any redirects will be followed until the actual file resource is reached or if the redirection
28 	limit of 10 is reached. Note that only HTTP(S) is currently supported.
29 */
30 void download(HTTPClient_ = void*)(URL url, scope void delegate(scope InputStream) callback, HTTPClient_ client_ = null)
31 {
32 	import vibe.http.client;
33 
34 	assert(url.username.length == 0 && url.password.length == 0, "Auth not supported yet.");
35 	assert(url.schema == "http" || url.schema == "https", "Only http(s):// supported for now.");
36 
37 	HTTPClient client;
38 	static if (is(HTTPClient_ == HTTPClient)) client = client_;
39 	if (!client) client = new HTTPClient();
40 	scope (exit) {
41 		if (client_ is null) // disconnect default client
42 			client.disconnect();
43 	}
44 
45 	if (!url.port)
46 		url.port = url.defaultPort;
47 
48 	foreach( i; 0 .. 10 ){
49 		client.connect(url.host, url.port, url.schema == "https");
50 		logTrace("connect to %s", url.host);
51 		bool done = false;
52 		client.request(
53 			(scope HTTPClientRequest req) {
54 				req.requestURL = url.localURI;
55 				logTrace("REQUESTING %s!", req.requestURL);
56 			},
57 			(scope HTTPClientResponse res) {
58 				logTrace("GOT ANSWER!");
59 
60 				switch( res.statusCode ){
61 					default:
62 						throw new HTTPStatusException(res.statusCode, "Server responded with "~httpStatusText(res.statusCode)~" for "~url.toString());
63 					case HTTPStatus.ok:
64 						done = true;
65 						callback(res.bodyReader.asInterface!InputStream);
66 						break;
67 					case HTTPStatus.movedPermanently:
68 					case HTTPStatus.found:
69 					case HTTPStatus.seeOther:
70 					case HTTPStatus.temporaryRedirect:
71 						logTrace("Status code: %s", res.statusCode);
72 						auto pv = "Location" in res.headers;
73 						enforce(pv !is null, "Server responded with redirect but did not specify the redirect location for "~url.toString());
74 						logDebug("Redirect to '%s'", *pv);
75 						if( startsWith((*pv), "http:") || startsWith((*pv), "https:") ){
76 							logTrace("parsing %s", *pv);
77 							auto nurl = URL(*pv);
78 							if (!nurl.port)
79 								nurl.port = nurl.defaultPort;
80 							if (url.host != nurl.host || url.schema != nurl.schema ||
81 								url.port != nurl.port)
82 								client.disconnect();
83 							url = nurl;
84 						} else
85 							url.localURI = *pv;
86 						break;
87 				}
88 			}
89 		);
90 		if (done) return;
91 	}
92 	enforce(false, "Too many redirects!");
93 	assert(false);
94 }
95 
96 /// ditto
97 void download(HTTPClient_ = void*)(string url, scope void delegate(scope InputStream) callback, HTTPClient_ client_ = null)
98 {
99 	download(URL(url), callback, client_);
100 }
101 
102 /// ditto
103 void download()(string url, string filename)
104 {
105 	download(url, (scope input){
106 		auto fil = openFile(filename, FileMode.createTrunc);
107 		scope(exit) fil.close();
108 		fil.write(input);
109 	});
110 }
111 
112 /// ditto
113 void download()(URL url, NativePath filename)
114 {
115 	download(url.toString(), filename.toNativeString());
116 }