xref: /haiku/src/kits/network/libnetservices/DataRequest.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
1 /*
2  * Copyright 2013 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Adrien Destugues, pulkomandy@pulkomandy.tk
7  */
8 
9 
10 #include "DataRequest.h"
11 
12 #include <AutoDeleter.h>
13 #include <HttpAuthentication.h>
14 #include <mail_encoding.h>
15 #include <stdio.h>
16 
17 using namespace BPrivate::Network;
18 
19 
20 BDataRequest::BDataRequest(const BUrl& url, BDataIO* output,
21 	BUrlProtocolListener* listener,
22 	BUrlContext* context)
23 	:
24 	BUrlRequest(url, output, listener, context, "data URL parser", "data"),
25 	fResult()
26 {
27 	fResult.SetContentType("text/plain");
28 }
29 
30 
31 const BUrlResult&
32 BDataRequest::Result() const
33 {
34 	return fResult;
35 }
36 
37 
38 status_t
39 BDataRequest::_ProtocolLoop()
40 {
41 	BString mimeType;
42 	BString charset;
43 	const char* payload;
44 	ssize_t length;
45 	bool isBase64 = false;
46 
47 	// The RFC has examples where some characters are URL-Encoded.
48 	fUrl.UrlDecode(true);
49 
50 	// The RFC says this uses a nonstandard scheme, so the path, query and
51 	// fragment are a bit nonsensical. It would be nice to handle them, but
52 	// some software (eg. WebKit) relies on data URIs with embedded "#" char
53 	// in the data...
54 	BString data = fUrl.UrlString();
55 	data.Remove(0, 5); // remove "data:"
56 
57 	int separatorPosition = data.FindFirst(',');
58 
59 	if (fListener != NULL)
60 		fListener->ConnectionOpened(this);
61 
62 	if (separatorPosition >= 0) {
63 		BString meta = data;
64 		meta.Truncate(separatorPosition);
65 		data.Remove(0, separatorPosition + 1);
66 
67 		int pos = 0;
68 		while (meta.Length() > 0) {
69 			// Extract next parameter
70 			pos = meta.FindFirst(';', pos);
71 
72 			BString parameter = meta;
73 			if (pos >= 0) {
74 				parameter.Truncate(pos);
75 				meta.Remove(0, pos+1);
76 			} else
77 				meta.Truncate(0);
78 
79 			// Interpret the parameter
80 			if (parameter == "base64") {
81 				isBase64 = true;
82 			} else if (parameter.FindFirst("charset=") == 0) {
83 				charset = parameter;
84 			} else {
85 				// Must be the MIME type
86 				mimeType = parameter;
87 			}
88 		}
89 
90 		if (charset.Length() > 0)
91 			mimeType << ";" << charset;
92 		fResult.SetContentType(mimeType);
93 
94 	}
95 
96 	ArrayDeleter<char> buffer;
97 	if (isBase64) {
98 		// Check that the base64 data is properly padded (we process characters
99 		// by groups of 4 and there must not be stray chars at the end as
100 		// Base64 specifies padding.
101 		if (data.Length() & 3)
102 			return B_BAD_DATA;
103 
104 		buffer.SetTo(new char[data.Length() * 3 / 4]);
105 		payload = buffer.Get();
106 			// payload must be a const char* so we can assign data.String() to
107 			// it below, but decode_64 modifies buffer.
108 		length = decode_base64(buffer.Get(), data.String(), data.Length());
109 
110 		// There may be some padding at the end of the base64 stream. This
111 		// prevents us from computing the exact length we should get, so allow
112 		// for some error margin.
113 		if (length > data.Length() * 3 / 4
114 			|| length < data.Length() * 3 / 4 - 3) {
115 			return B_BAD_DATA;
116 		}
117 	} else {
118 		payload = data.String();
119 		length = data.Length();
120 	}
121 
122 	fResult.SetLength(length);
123 
124 	if (fListener != NULL)
125 		fListener->HeadersReceived(this);
126 	if (length > 0) {
127 		if (fOutput != NULL) {
128 			size_t written = 0;
129 			status_t err = fOutput->WriteExactly(payload, length, &written);
130 			if (fListener != NULL && written > 0)
131 				fListener->BytesWritten(this, written);
132 			if (err != B_OK)
133 				return err;
134 			if (fListener != NULL)
135 				fListener->DownloadProgress(this, written, written);
136 		}
137 	}
138 
139 	return B_OK;
140 }
141