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 <vladimir@thecybershadow.net>
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 class CurlNetwork : Network
30 {
31 	override void downloadFile(string url, string target)
32 	{
33 		std.file.write(target, getFile(url));
34 	}
35 
36 	override void[] getFile(string url)
37 	{
38 		return get!(AutoProtocol, ubyte)(url);
39 	}
40 
41 	override void[] post(string url, in void[] data)
42 	{
43 		return .post!ubyte(url, data);
44 	}
45 
46 	override bool urlOK(string url)
47 	{
48 		try
49 		{
50 			auto http = HTTP(url);
51 			http.method = HTTP.Method.head;
52 			http.perform();
53 			return http.statusLine.code == 200; // OK
54 		}
55 		catch (Exception e)
56 			return false;
57 	}
58 
59 	override string resolveRedirect(string url)
60 	{
61 		string result = null;
62 
63 		auto http = HTTP(url);
64 		http.method = HTTP.Method.head;
65 		http.onReceiveHeader =
66 			(in char[] key, in char[] value)
67 			{
68 				if (icmp(key, "Location")==0)
69 				{
70 					result = value.idup;
71 					if (result)
72 						result = url.applyRelativeURL(result);
73 				}
74 			};
75 		http.perform();
76 
77 		return result;
78 	}
79 
80 	override HttpResponse httpRequest(HttpRequest request)
81 	{
82 		auto http = HTTP();
83 		http.url = request.url;
84 		switch (request.method.toUpper)
85 		{
86 			case "HEAD"   : http.method = HTTP.Method.head; break;
87 			case "GET"    : http.method = HTTP.Method.get; break;
88 			case "POST"   : http.method = HTTP.Method.post; break;
89 			case "PUT"    : http.method = HTTP.Method.put; break;
90 			case "DEL"    : http.method = HTTP.Method.del; break;
91 			case "OPTIONS": http.method = HTTP.Method.options; break;
92 			case "TRACE"  : http.method = HTTP.Method.trace; break;
93 			case "CONNECT": http.method = HTTP.Method.connect; break;
94 			case "PATCH"  : http.method = HTTP.Method.patch; break;
95 			default: throw new Exception("Unknown HTTP method: " ~ request.method);
96 		}
97 		foreach (name, value; request.headers)
98 			http.addRequestHeader(name, value);
99 
100 		if (request.data)
101 		{
102 			auto requestData = request.data.bytes;
103 			http.contentLength = requestData.length;
104 			auto remainingData = requestData;
105 			http.onSend =
106 				(void[] buf)
107 				{
108 					size_t bytesToSend = min(buf.length, remainingData.length);
109 					if (!bytesToSend)
110 						return 0;
111 					auto dataToSend = remainingData[0 .. bytesToSend];
112 					{
113 						size_t p = 0;
114 						foreach (datum; dataToSend)
115 						{
116 							buf[p .. p + datum.length] = datum.contents;
117 							p += datum.length;
118 						}
119 					}
120 					remainingData = remainingData[bytesToSend .. $].bytes;
121 					return bytesToSend;
122 				};
123 			http.handle.onSeek =
124 				(long offset, CurlSeekPos mode)
125 				{
126 					switch (mode)
127 					{
128 						case CurlSeekPos.set:
129 							remainingData = requestData[cast(size_t) offset .. $].bytes;
130 							return CurlSeek.ok;
131 						default:
132 							return CurlSeek.cantseek;
133 					}
134 				};
135 		}
136 
137 		auto response = new HttpResponse;
138 		http.onReceiveStatusLine =
139 			(HTTP.StatusLine statusLine)
140 			{
141 				response.status = cast(HttpStatusCode)statusLine.code;
142 				response.statusMessage = statusLine.reason;
143 			};
144 		http.onReceiveHeader =
145 			(in char[] key, in char[] value)
146 			{
147 				response.headers.add(key.idup, value.idup);
148 			};
149 		http.onReceive =
150 			(ubyte[] data)
151 			{
152 				response.data ~= Data(data);
153 				return data.length;
154 			};
155 		http.perform();
156 		return response;
157 	}
158 }
159 
160 static this()
161 {
162 	net = new CurlNetwork();
163 }