This repository has been archived by the owner on Apr 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathmultival.c
296 lines (259 loc) · 9.75 KB
/
multival.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*
* multival.c - The Apache::MultiVal class
* $Id$
*
* Author: Michael Granger <[email protected]>
* Copyright (c) 2003 RubyCrafters, LLC. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
/*
* This file contains Apache::MultiVal, a multi-valued variable class for Apache
* request parameters. Instances of it are used to represent request parameters
* in an Apache mod_ruby application. Since parameters can be multi-valued (ie.,
* have more than one value associated with any key), a Hash (and even an
* Apache::Table) is a poor way to represent them. You must either discard all
* but the first value read in, or put multiple values into Arrays. If you put
* only parameters which have multiple values into Arrays, it requires that the
* application author always check for Array-ness for every parameter (even if
* it's only ever expected to take one) in case someone modifies the query and
* sends more than one. For example, if you have the following code:
*
* foo = request.param['foo'].downcase
*
* And someone sends a request with the following QUERY_STRING to your
* application:
*
* ?foo=bad&foo=second+value
*
* then your code will blithely try to call 'downcase' on the resultant Array,
* which will result in an exception:
*
* NoMethodError: undefined method `downcase' for ["bad", "second value"]:Array
*
* You're then forced to do something like this for each parameter:
*
* if request.param['foo'].is_a?( Array )
* foo = request.param['foo'][0].downcase
* else
* foo = request.param['foo'].downcase
* end
*
* Making *every* parameter an Array, no matter if there are multiple values or
* not solves this somewhat, but forces you do use an index for *every*
* parameter:
*
* foo = request.params['foo'][0].downcase
*
* Apache::MultiVal solves this by making each parameter capable of being
* treated like both a String *and* an Array by delegating String and Array
* instance methods to either the first value or the Array of values,
* respectively. The instance methods which String and Array share in common are
* delegated to the String, except #each, which is sent to the Array. This
* allows the code to be kept simple: if you only ever expect a parameter to
* have a single value, you can treat it as if it is a String:
*
* foo = request.params['foo'].downcase
*
* and treat parameters which can have multiple values (mostly) as an Array:
*
* bars = request.params['bar'].collect {|val| val.downcase}
*
* For the methods that Array and String share in common, you can cast the
* parameter to the object you wish with the normal #to_a and #to_s methods:
*
* foo = request.params['foo']
* if foo.to_a.length > 1
* request.log_warn( "Request had more than one 'foo' parameter: %s",
* foo.to_a.inspect )
*
* Of course, the Array's length could be obtained with foo.nitems, too, since
* Array#nitems isn't obscured by String's instance methods.
*
*
* === Obscured Array methods
*
* As indicated above, some of Array's methods are obscured by those of String,
* so you should take special note when using them to be sure you know what
* you'll be getting. For the version of Ruby that was most recent as of this
* writing (ruby 1.8.0 (2003-04-27)), these are:
*
* String::instance_methods(false) & Array::instance_methods(false) - ["each"]
* # ["slice!", "length", "delete", "*", "+", "empty?",
* "to_s", "copy_object", "rindex", "slice", "reverse!", "[]",
* "concat", "[]=", "size", "<<", "reverse", "inspect", "insert",
* "replace", "<=>", "eql?", "==", "index", "include?", "hash"]
*
*
*/
#include "mod_ruby.h"
#include "apachelib.h"
VALUE rb_cApacheMultiVal;
ID stringish, arrayish;
extern VALUE rb_load_path;
/*
* Apache::MultiVal#initialize( *values )
* --
* Create a new MultiVal object with the specified +values+. The first of
* the values given will be the value used for String operations.
*/
static VALUE
multival_init( VALUE self, VALUE args )
{
VALUE collect;
long len, i;
/* Make sure there's at least one argument to work with */
if ( RARRAY_LEN(args) == 0 )
rb_ary_push( args, rb_tainted_str_new("", 0) );
/* Stringify all arguments */
len = RARRAY_LEN(args);
collect = rb_ary_new2(len);
for ( i = 0; i < len; i++ ) {
VALUE str;
str = rb_str_dup( rb_obj_as_string(RARRAY_PTR(args)[i]) );
OBJ_INFECT( str, RARRAY_PTR(args)[i] );
rb_ary_push( collect, str );
}
rb_iv_set( self, "@args", collect );
return self;
}
/*
* Apache::MultiVal#to_s
* --
* Return the String part of the MultiVal.
*/
static VALUE
multival_to_s( VALUE self )
{
VALUE args = rb_iv_get( self, "@args" );
return rb_ary_entry( args, 0 );
}
/*
* Apache::MultiVal#to_a
* --
* Return the Array part of the MultiVal.
*/
static VALUE
multival_to_a( VALUE self )
{
return rb_iv_get( self, "@args" );
}
/*
* Apache::MultiVal#<=>( other )
* --
* Comparison -- Returns -1 if the receiver is less than, 0 if it is equal to,
* or -1 if it is greater than the specified +other+ object. Returns +nil+ if
* the +other+ object isn't an Apache::MultiVal.
*/
static VALUE
multival_compare( VALUE self, VALUE other )
{
/* If it's another MultiVal (or a subclass thereof), do array-wise comparison. */
if ( rb_obj_is_kind_of(other, CLASS_OF(self)) ) {
VALUE args = rb_iv_get( self, "@args" );
VALUE otherval = rb_funcall( other, rb_intern("to_a"), 0 );
return rb_funcall( args, rb_intern("<=>"), 1, otherval );
}
else {
return Qnil;
}
}
#if 0
/*
* Delegate the current method to the stringish version of the multival (ie.,
* the first element of the array).
*/
static VALUE
multival_string_delegator( int argc, VALUE *argv, VALUE self )
{
ID meth = rb_frame_this_func();
VALUE args = rb_iv_get( self, "@args" );
return rb_funcall2( RARRAY_PTR(args)[0], meth, argc, argv );
}
/*
* Delegate the current method to the arrayish version of the multival.
*/
static VALUE
multival_array_delegator( int argc, VALUE *argv, VALUE self )
{
ID meth = rb_frame_this_func();
VALUE args = rb_iv_get( self, "@args" );
return rb_funcall2( args, meth, argc, argv );
}
/*
* Make a delegator method to either the @args Array or the first element of
* @args.
*/
static VALUE
multival_make_delegator( VALUE name, ID which )
{
if ( which == stringish ) {
rb_define_method( rb_cApacheMultiVal, StringValuePtr(name), multival_string_delegator, -1 );
} else {
rb_define_method( rb_cApacheMultiVal, StringValuePtr(name), multival_array_delegator, -1 );
}
return Qtrue;
}
#endif
/* Module initializer */
void rb_init_apache_multival()
{
VALUE dmethods;
VALUE args[1];
/* Kluge to make Rdoc see the associations in this file */
#if FOR_RDOC_PARSER
rb_mApache = rb_define_module( "Apache" );
#endif
rb_cApacheMultiVal = rb_define_class_under( rb_mApache, "MultiVal", rb_cObject );
/* Define IDs which indicate to the delegator-creator whether it's supposed
to delegate to the whole array or just the first element. */
stringish = rb_intern("stringish");
arrayish = rb_intern("arrayish");
/* Since rb_load_path isn't yet set up, we have to set up our own
delegation instead of relying on 'forwardable'. Set up stringish
delegation first. */
args[0] = Qfalse;
dmethods = rb_class_instance_methods( 1, args, rb_cString );
rb_ary_delete( dmethods, rb_str_new2("each") );
rb_ary_delete( dmethods, rb_str_new2("[]") );
rb_ary_delete( dmethods, rb_str_new2("[]=") );
/* TODO: avoid SEGV */
#if 0
rb_iterate( rb_each, dmethods, multival_make_delegator, stringish );
/* Now set up array-ish delegation */
dmethods = rb_class_instance_methods( 1, args, rb_cArray );
rb_iterate( rb_each, dmethods, multival_make_delegator, arrayish );
#endif
/* include Comparable */
rb_include_module( rb_cApacheMultiVal, rb_mComparable );
/* Instance methods */
rb_define_method( rb_cApacheMultiVal, "initialize", multival_init, -2 );
rb_define_method( rb_cApacheMultiVal, "to_s", multival_to_s, 0 );
rb_define_method( rb_cApacheMultiVal, "to_str", multival_to_s, 0 );
rb_define_alias ( rb_cApacheMultiVal, "as_string", "to_s" );
rb_define_method( rb_cApacheMultiVal, "to_a", multival_to_a, 0 );
rb_define_method( rb_cApacheMultiVal, "to_ary", multival_to_a, 0 );
rb_define_alias ( rb_cApacheMultiVal, "as_array", "to_a" );
rb_define_method( rb_cApacheMultiVal, "<=>", multival_compare, 1 );
}
/* vim: set filetype=c ts=8 sw=4 : */