Fix stream_get_filters
[hiphop-php.git] / hphp / runtime / ext / stream / ext_stream-user-filters.cpp
blobafcc5ee5e366f1fd8a65360d1b308d417a806303
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/stream/ext_stream-user-filters.h"
19 #include "hphp/runtime/base/base-includes.h"
20 #include "hphp/runtime/ext/ext_array.h"
21 #include "hphp/system/systemlib.h"
23 namespace HPHP {
25 ///////////////////////////////////////////////////////////////////////////////
27 const StaticString s_filter("filter");
28 const StaticString s_onCreate("onCreate");
29 const StaticString s_onClose("onClose");
31 const StaticString s_bucket_class("__SystemLib\\StreamFilterBucket");
33 ///////////////////////////////////////////////////////////////////////////////
35 const StaticString s_default_filters_register_func(
36 "__SystemLib\\register_default_stream_filters");
38 class StreamUserFilters : public RequestEventHandler {
39 public:
40 virtual ~StreamUserFilters() {}
41 Array m_registeredFilters;
43 bool registerFilter(const String& name, const String& class_name) {
44 if (m_registeredFilters.exists(name)) {
45 return false;
47 m_registeredFilters.add(name, class_name);
48 return true;
51 Variant prepend(CResRef stream,
52 const String& filtername,
53 CVarRef readwrite,
54 CVarRef params) {
55 return appendOrPrependFilter(stream,
56 filtername,
57 readwrite,
58 params,
59 /* append = */ false);
62 Variant append(CResRef stream,
63 const String& filtername,
64 CVarRef readwrite,
65 CVarRef params) {
66 return appendOrPrependFilter(stream,
67 filtername,
68 readwrite,
69 params,
70 /* append = */ true);
73 virtual void requestInit() {
74 vm_call_user_func(s_default_filters_register_func, empty_array);
77 virtual void requestShutdown() {}
78 private:
79 Variant appendOrPrependFilter(CResRef stream,
80 const String& filtername,
81 CVarRef readwrite,
82 CVarRef params,
83 bool append) {
84 const char* func_name =
85 append ? "stream_filter_append()" : "stream_filter_prepend()";
87 if (!m_registeredFilters.exists(filtername)) {
88 raise_warning("%s: unable to locate filter \"%s\"",
89 func_name,
90 filtername->data());
91 return false;
94 auto file = stream.getTyped<File>();
95 assert(file);
97 int mode = readwrite.toInt32();
98 if (!mode) {
99 auto str = file->getMode();
100 /* The documentation says a read filter is only created for 'r' and '+'
101 * modes, but the implementation will always create one unless
102 * STREAM_FILTER_WRITE is passed.
104 * This branch is only executed if no STREAM_FILTER* args were passed,
105 * so we always create a READ filter.
107 mode = k_STREAM_FILTER_READ;
108 if (str.find('+') != -1 || str.find('w') != -1 || str.find('a') != -1) {
109 mode |= k_STREAM_FILTER_WRITE;
112 if (!(mode & k_STREAM_FILTER_ALL)) {
113 return false;
116 // If it's ALL we create two resources, but only return one - this
117 // matches Zend, and is the documented behavior.
118 Resource ret;
119 if (mode & k_STREAM_FILTER_READ) {
120 auto resource = createInstance(func_name,
121 stream,
122 filtername,
123 params);
124 if (resource.isNull()) {
125 return false;
127 ret = resource;
128 if (append) {
129 file->appendReadFilter(resource);
130 } else {
131 file->prependReadFilter(resource);
134 if (mode & k_STREAM_FILTER_WRITE) {
135 auto resource = createInstance(func_name,
136 stream,
137 filtername,
138 params);
139 if (resource.isNull()) {
140 return false;
142 ret = resource;
143 if (append) {
144 file->appendWriteFilter(resource);
145 } else {
146 file->prependWriteFilter(resource);
149 return ret;
152 Resource createInstance(const char* php_func,
153 const Resource& stream,
154 const String& filter,
155 CVarRef params) {
156 auto class_name = m_registeredFilters.rvalAt(filter).asCStrRef();
157 Class* class_ = Unit::lookupClass(class_name.get());
158 Object obj = null_object;
160 if (LIKELY(class_ != nullptr)) {
161 ArrayInit ctor_args(3);
162 ctor_args.set(stream);
163 ctor_args.set(filter);
164 ctor_args.set(params);
165 obj = g_context->createObject(class_name.get(), ctor_args.toArray());
166 auto created = obj->o_invoke(s_onCreate, Array::Create());
167 /* - true: documented value for success
168 * - null: undocumented default successful value
169 * - false: documented value for failure
171 if (!(created.isNull() || created.toBoolean())) {
172 obj = null_object;
174 } else {
175 raise_warning("%s: user-filter \"%s\" requires class \"%s\", but that "
176 "class " "is not defined",
177 php_func,
178 filter->data(),
179 class_name->data());
180 // Fall through, as to match Zend, the warning below should also be raised
183 if (obj.isNull()) {
184 raise_warning("%s: unable to create or locate filter \"%s\"",
185 php_func,
186 filter->data());
187 return null_resource;
190 return Resource(NEWOBJ(StreamFilter)(obj));
193 IMPLEMENT_STATIC_REQUEST_LOCAL(StreamUserFilters, s_stream_user_filters);
195 ///////////////////////////////////////////////////////////////////////////////
196 // StreamFilter
198 int64_t StreamFilter::invokeFilter(Resource in,
199 Resource out,
200 bool closing) {
201 auto consumedTV = make_tv<KindOfInt64>(0);
202 auto consumedRef = RefData::Make(consumedTV);
204 ArrayInit params(4);
205 params.set(in);
206 params.set(out);
207 params.set(consumedRef);
208 params.set(closing);
209 return m_filter->o_invoke(s_filter, params.toArray()).toInt64();
212 void StreamFilter::invokeOnClose() {
213 m_filter->o_invoke(s_onClose, Array::Create());
216 ///////////////////////////////////////////////////////////////////////////////
217 // BucketBrigade
219 BucketBrigade::BucketBrigade(const String& data) {
220 ArrayInit ai(2);
221 ai.set(data);
222 ai.set(data.length());
223 auto bucket = g_context->createObject(s_bucket_class.get(), ai.toArray());
224 appendBucket(bucket);
227 void BucketBrigade::appendBucket(CObjRef bucket) {
228 m_buckets.push_back(bucket);
231 void BucketBrigade::prependBucket(CObjRef bucket) {
232 m_buckets.push_front(bucket);
235 Object BucketBrigade::popFront() {
236 if (m_buckets.empty()) {
237 return null_object;
239 auto bucket = m_buckets.front();
240 m_buckets.pop_front();
241 return bucket;
244 String BucketBrigade::createString() {
245 StringBuffer buffer;
246 for (auto& bucket_obj: m_buckets) {
247 buffer.append(bucket_obj.toString());
249 return buffer.detach();
252 ///////////////////////////////////////////////////////////////////////////////
254 bool HHVM_FUNCTION(stream_filter_register,
255 const String& name,
256 const String& classname) {
257 return s_stream_user_filters.get()->registerFilter(name, classname);
260 Array HHVM_FUNCTION(stream_get_filters) {
261 auto filters = s_stream_user_filters.get()->m_registeredFilters;
262 if (UNLIKELY(filters.isNull())) {
263 return Array::Create();
265 return f_array_keys(filters).toArray();
268 Variant HHVM_FUNCTION(stream_filter_append,
269 CResRef stream,
270 const String& filtername,
271 CVarRef readwrite,
272 CVarRef params) {
273 return s_stream_user_filters.get()->append(stream,
274 filtername,
275 readwrite,
276 params);
279 Variant HHVM_FUNCTION(stream_filter_prepend,
280 CResRef stream,
281 const String& filtername,
282 CVarRef readwrite,
283 CVarRef params) {
284 return s_stream_user_filters.get()->prepend(stream,
285 filtername,
286 readwrite,
287 params);
290 Variant HHVM_FUNCTION(stream_bucket_make_writeable, CResRef bb_res) {
291 auto brigade = bb_res.getTyped<BucketBrigade>();
292 assert(brigade);
293 auto ret = brigade->popFront();
294 return ret;
297 void HHVM_FUNCTION(stream_bucket_append, CResRef bb_res, CObjRef bucket) {
298 auto brigade = bb_res.getTyped<BucketBrigade>();
299 assert(brigade);
300 brigade->appendBucket(bucket);
303 void HHVM_FUNCTION(stream_bucket_prepend, CResRef bb_res, CObjRef bucket) {
304 auto brigade = bb_res.getTyped<BucketBrigade>();
305 assert(brigade);
306 brigade->prependBucket(bucket);
309 ///////////////////////////////////////////////////////////////////////////////