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
17using namespace BPrivate::Network;
18
19
20BDataRequest::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
31const BUrlResult&
32BDataRequest::Result() const
33{
34	return fResult;
35}
36
37
38status_t
39BDataRequest::_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