1 /**
2  * ae.sys.net implementation using std.net.curl
3  * Note: std.net.curl requires libcurl.
4  *
5  * License:
6  *   This Source Code Form is subject to the terms of
7  *   the Mozilla Public License, v. 2.0. If a copy of
8  *   the MPL was not distributed with this file, You
9  *   can obtain one at http://mozilla.org/MPL/2.0/.
10  *
11  * Authors:
12  *   Vladimir Panteleev <ae@cy.md>
13  */
14 
15 module ae.sys.net.curl;
16 
17 import etc.c.curl : CurlSeekPos, CurlSeek;
18 
19 import std.algorithm.comparison;
20 import std.file;
21 import std.net.curl;
22 import std.string;
23 
24 import ae.net.http.common;
25 import ae.net.ietf.url;
26 import ae.sys.data;
27 import ae.sys.net;
28 
29 /// Implementation of `Network` using libcurl.
30 class CurlNetwork : Network
31 {
32 	override void downloadFile(string url, string target)
33 	{
34 		std.file.write(target, getFile(url));
35 	} ///
36 
37 	override void[] getFile(string url)
38 	{
39 		return get!(AutoProtocol, ubyte)(url);
40 	} ///
41 
42 	override void[] post(string url, in void[] data)
43 	{
44 		return .post!ubyte(url, data);
45 	} ///
46 
47 	override bool urlOK(string url)
48 	{
49 		try
50 		{
51 			auto http = HTTP(url);
52 			http.method = HTTP.Method.head;
53 			http.perform();
54 			return http.statusLine.code == 200; // OK
55 		}
56 		catch (Exception e)
57 			return false;
58 	} ///
59 
60 	override string resolveRedirect(string url)
61 	{
62 		string result = null;
63 
64 		auto http = HTTP(url);
65 		http.method = HTTP.Method.head;
66 		http.onReceiveHeader =
67 			(in char[] key, in char[] value)
68 			{
69 				if (icmp(key, "Location")==0)
70 				{
71 					result = value.idup;
72 					if (result)
73 						result = url.applyRelativeURL(result);
74 				}
75 			};
76 		http.perform();
77 
78 		return result;
79 	} ///
80 
81 	override HttpResponse httpRequest(HttpRequest request)
82 	{
83 		auto http = HTTP();
84 		http.url = request.url;
85 		switch (request.method.toUpper)
86 		{
87 			case "HEAD"   : http.method = HTTP.Method.head; break;
88 			case "GET"    : http.method = HTTP.Method.get; break;
89 			case "POST"   : http.method = HTTP.Method.post; break;
90 			case "PUT"    : http.method = HTTP.Method.put; break;
91 			case "DEL"    : http.method = HTTP.Method.del; break;
92 			case "OPTIONS": http.method = HTTP.Method.options; break;
93 			case "TRACE"  : http.method = HTTP.Method.trace; break;
94 			case "CONNECT": http.method = HTTP.Method.connect; break;
95 			case "PATCH"  : http.method = HTTP.Method.patch; break;
96 			default: throw new Exception("Unknown HTTP method: " ~ request.method);
97 		}
98 		foreach (name, value; request.headers)
99 			http.addRequestHeader(name, value);
100 
101 		DataVec remainingData; // https://issues.dlang.org/show_bug.cgi?id=14261
102 		if (request.data)
103 		{
104 			remainingData = request.data.dup;
105 			http.contentLength = request.data.bytes.length;
106 			http.onSend =
107 				(void[] buf)
108 				{
109 					size_t bytesToSend = min(buf.length, remainingData.bytes.length);
110 					if (!bytesToSend)
111 						return 0;
112 					auto dataToSend = remainingData.bytes[0 .. bytesToSend];
113 					{
114 						size_t p = 0;
115 						foreach (ref datum; dataToSend)
116 						{
117 							buf[p .. p + datum.length] = datum.contents;
118 							p += datum.length;
119 						}
120 					}
121 					remainingData = remainingData.bytes[bytesToSend .. $];
122 					return bytesToSend;
123 				};
124 			http.handle.onSeek =
125 				(long offset, CurlSeekPos mode)
126 				{
127 					switch (mode)
128 					{
129 						case CurlSeekPos.set:
130 							remainingData = request.data.bytes[cast(size_t) offset .. $];
131 							return CurlSeek.ok;
132 						default:
133 							return CurlSeek.cantseek;
134 					}
135 				};
136 		}
137 
138 		auto response = new HttpResponse;
139 		http.onReceiveStatusLine =
140 			(HTTP.StatusLine statusLine)
141 			{
142 				response.status = cast(HttpStatusCode)statusLine.code;
143 				response.statusMessage = statusLine.reason;
144 			};
145 		http.onReceiveHeader =
146 			(in char[] key, in char[] value)
147 			{
148 				response.headers.add(key.idup, value.idup);
149 			};
150 		http.onReceive =
151 			(ubyte[] data)
152 			{
153 				response.data ~= Data(data);
154 				return data.length;
155 			};
156 		http.perform();
157 		return response;
158 	} ///
159 }
160 
161 static this()
162 {
163 	net = new CurlNetwork();
164 }