kcar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +7 -0
- data/.gitignore +20 -0
- data/COPYING +339 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +197 -0
- data/LICENSE +55 -0
- data/README +86 -0
- data/Rakefile +149 -0
- data/TODO +6 -0
- data/ext/kcar/c_util.h +105 -0
- data/ext/kcar/ext_help.h +82 -0
- data/ext/kcar/extconf.rb +14 -0
- data/ext/kcar/kcar.rl +656 -0
- data/ext/kcar/kcar_http_common.rl +56 -0
- data/kcar.gemspec +40 -0
- data/lib/kcar.rb +11 -0
- data/lib/kcar/parser.rb +39 -0
- data/lib/kcar/response.rb +168 -0
- data/setup.rb +1586 -0
- data/test/test_parser.rb +257 -0
- data/test/test_response.rb +415 -0
- metadata +96 -0
data/LICENSE
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
kcar is copyrighted free software by all contributors, see logs in
|
2
|
+
revision control for names and email addresses of all of them. You can
|
3
|
+
redistribute it and/or modify it under either the terms of the
|
4
|
+
{GPL2}[http://www.gnu.org/licenses/gpl-2.0.txt] (see link:COPYING) or
|
5
|
+
the conditions below:
|
6
|
+
|
7
|
+
1. You may make and give away verbatim copies of the source form of the
|
8
|
+
software without restriction, provided that you duplicate all of the
|
9
|
+
original copyright notices and associated disclaimers.
|
10
|
+
|
11
|
+
2. You may modify your copy of the software in any way, provided that
|
12
|
+
you do at least ONE of the following:
|
13
|
+
|
14
|
+
a) place your modifications in the Public Domain or otherwise make them
|
15
|
+
Freely Available, such as by posting said modifications to Usenet or an
|
16
|
+
equivalent medium, or by allowing the author to include your
|
17
|
+
modifications in the software.
|
18
|
+
|
19
|
+
b) use the modified software only within your corporation or
|
20
|
+
organization.
|
21
|
+
|
22
|
+
c) rename any non-standard executables so the names do not conflict with
|
23
|
+
standard executables, which must also be provided.
|
24
|
+
|
25
|
+
d) make other distribution arrangements with the author.
|
26
|
+
|
27
|
+
3. You may distribute the software in object code or executable
|
28
|
+
form, provided that you do at least ONE of the following:
|
29
|
+
|
30
|
+
a) distribute the executables and library files of the software,
|
31
|
+
together with instructions (in the manual page or equivalent) on where
|
32
|
+
to get the original distribution.
|
33
|
+
|
34
|
+
b) accompany the distribution with the machine-readable source of the
|
35
|
+
software.
|
36
|
+
|
37
|
+
c) give non-standard executables non-standard names, with
|
38
|
+
instructions on where to get the original software distribution.
|
39
|
+
|
40
|
+
d) make other distribution arrangements with the author.
|
41
|
+
|
42
|
+
4. You may modify and include the part of the software into any other
|
43
|
+
software (possibly commercial). But some files in the distribution
|
44
|
+
are not written by the author, so that they are not under this terms.
|
45
|
+
|
46
|
+
5. The scripts and library files supplied as input to or produced as
|
47
|
+
output from the software do not automatically fall under the
|
48
|
+
copyright of the software, but belong to whomever generated them,
|
49
|
+
and may be sold commercially, and may be aggregated with this
|
50
|
+
software.
|
51
|
+
|
52
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
53
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
54
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
55
|
+
PURPOSE.
|
data/README
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= kcar - bytestream to Rack response converter
|
2
|
+
|
3
|
+
kcar features an HTTP parser that will convert a bytestream into a
|
4
|
+
3-element array suitable for use as a Rack response. It is IO interface
|
5
|
+
agnostic, so it may be used with HTTP streams over Unix domain sockets,
|
6
|
+
regular files, FIFOs, StringIOs as well as traditional TCP sockets.
|
7
|
+
|
8
|
+
A drop-in, Net::HTTP-compatible interface is planned.
|
9
|
+
|
10
|
+
== Features
|
11
|
+
|
12
|
+
* RFC2616-compliant Ragel+C parser adapted from Unicorn and Mongrel
|
13
|
+
|
14
|
+
* decodes chunked response bodies with an optional pass-through mode
|
15
|
+
(to avoid rechunking with Rack::Chunked)
|
16
|
+
|
17
|
+
* handles odd things like trailers and multiline headers
|
18
|
+
|
19
|
+
* streaming interface for response bodies allows for incremental
|
20
|
+
processing of arbitrarily large responses.
|
21
|
+
|
22
|
+
== Problems
|
23
|
+
|
24
|
+
* kcar is only lightly tested and is not yet aware of all quirks found in
|
25
|
+
all real (possibly broken) web servers.
|
26
|
+
|
27
|
+
== Install
|
28
|
+
|
29
|
+
If you're using a packaged Ruby distribution, make sure you have a C
|
30
|
+
compiler and the matching Ruby development libraries and headers.
|
31
|
+
|
32
|
+
If you use RubyGems:
|
33
|
+
|
34
|
+
gem install kcar
|
35
|
+
|
36
|
+
Otherwise grab the latest tarball from:
|
37
|
+
|
38
|
+
http://bogomips.org/kcar/files/
|
39
|
+
|
40
|
+
Unpack it, and run "ruby setup.rb"
|
41
|
+
|
42
|
+
== Usage:
|
43
|
+
|
44
|
+
While you can use the Kcar::Parser directly, you'll usually want the
|
45
|
+
higher-level interface of Kcar::Response:
|
46
|
+
|
47
|
+
require 'rack' # for Rack::Utils::HeaderHash, which is optional
|
48
|
+
require 'socket' # for TCPSocket
|
49
|
+
require 'kcar'
|
50
|
+
sock = TCPSocket.new('example.com', 80)
|
51
|
+
sock.write("GET / HTTP/1.0\r\n\r\n")
|
52
|
+
|
53
|
+
# instead of a Rack::Utils::HeaderHash object below, you can also
|
54
|
+
# pass a regular Hash or Array object.
|
55
|
+
response = Kcar::Response.new(sock, Rack::Utils::HeaderHash.new)
|
56
|
+
status, headers, body = response.rack
|
57
|
+
|
58
|
+
You can now do further processing on the status, headers, or iterate
|
59
|
+
through the body with body.each.
|
60
|
+
|
61
|
+
== Development
|
62
|
+
|
63
|
+
You can get the latest source via git from the following locations:
|
64
|
+
|
65
|
+
git://git.bogomips.org/kcar.git
|
66
|
+
git://repo.or.cz/kcar.git (mirror)
|
67
|
+
|
68
|
+
You may browse the code from the web and download the latest snapshot
|
69
|
+
tarballs here:
|
70
|
+
|
71
|
+
* http://git.bogomips.org/cgit/kcar.git (cgit)
|
72
|
+
* http://repo.or.cz/w/kcar.git (gitweb)
|
73
|
+
|
74
|
+
Inline patches (from "git format-patch") to the mailing list are
|
75
|
+
preferred because they allow code review and comments in the reply to
|
76
|
+
the patch.
|
77
|
+
|
78
|
+
We will adhere to mostly the same conventions for patch submissions as
|
79
|
+
git itself. See the Documentation/SubmittingPatches document
|
80
|
+
distributed with git on on patch submission guidelines to follow. Just
|
81
|
+
don't email the git mailing list or maintainer with kcar patches.
|
82
|
+
|
83
|
+
== Contact
|
84
|
+
|
85
|
+
All feedback (bug reports, user/development discussion, patches, pull
|
86
|
+
requests) go to the mailing list: mailto:kcar@librelist.com
|
data/Rakefile
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
desc "read news article from STDIN and post to rubyforge"
|
2
|
+
task :publish_news do
|
3
|
+
require 'rubyforge'
|
4
|
+
IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
|
5
|
+
msg = STDIN.readlines
|
6
|
+
subject = msg.shift
|
7
|
+
blank = msg.shift
|
8
|
+
blank == "\n" or abort "no newline after subject!"
|
9
|
+
subject.strip!
|
10
|
+
body = msg.join("").strip!
|
11
|
+
|
12
|
+
rf = RubyForge.new.configure
|
13
|
+
rf.login
|
14
|
+
rf.post_news('rainbows', subject, body)
|
15
|
+
end
|
16
|
+
|
17
|
+
def tags
|
18
|
+
timefmt = '%Y-%m-%dT%H:%M:%SZ'
|
19
|
+
@tags ||= `git tag -l`.split(/\n/).map do |tag|
|
20
|
+
next if tag == "v0.0.0"
|
21
|
+
if %r{\Av[\d\.]+\z} =~ tag
|
22
|
+
header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
|
23
|
+
header = header.split(/\n/)
|
24
|
+
tagger = header.grep(/\Atagger /).first
|
25
|
+
body ||= "initial"
|
26
|
+
{
|
27
|
+
:time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
|
28
|
+
:tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1],
|
29
|
+
:tagger_email => %r{<([^>]+)>}.match(tagger)[1],
|
30
|
+
:id => `git rev-parse refs/tags/#{tag}`.chomp!,
|
31
|
+
:tag => tag,
|
32
|
+
:subject => subject,
|
33
|
+
:body => body,
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end.compact.sort { |a,b| b[:time] <=> a[:time] }
|
37
|
+
end
|
38
|
+
|
39
|
+
cgit_url = "http://git.bogomips.org/cgit/kcar.git"
|
40
|
+
git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/kcar.git'
|
41
|
+
|
42
|
+
desc 'prints news as an Atom feed'
|
43
|
+
task :news_atom do
|
44
|
+
require 'nokogiri'
|
45
|
+
new_tags = tags[0,10]
|
46
|
+
puts(Nokogiri::XML::Builder.new do
|
47
|
+
feed :xmlns => "http://www.w3.org/2005/Atom" do
|
48
|
+
id! "http://kcar.rubyforge.org/NEWS.atom.xml"
|
49
|
+
title "Kcar news"
|
50
|
+
subtitle File.readlines("README").first
|
51
|
+
link! :rel => 'alternate', :type => 'text/html',
|
52
|
+
:href => 'http://bogomips.org/kcar/'
|
53
|
+
updated( (new_tags.first[:time] rescue nil) || Time.now )
|
54
|
+
new_tags.each do |tag|
|
55
|
+
entry do
|
56
|
+
title tag[:subject]
|
57
|
+
updated tag[:time]
|
58
|
+
published tag[:time]
|
59
|
+
author {
|
60
|
+
name tag[:tagger_name]
|
61
|
+
email tag[:tagger_email]
|
62
|
+
}
|
63
|
+
url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
|
64
|
+
link! :rel => "alternate", :type => "text/html", :href =>url
|
65
|
+
id! url
|
66
|
+
content(:type => 'text') { tag[:body] }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end.to_xml)
|
71
|
+
end
|
72
|
+
|
73
|
+
desc 'prints RDoc-formatted news'
|
74
|
+
task :news_rdoc do
|
75
|
+
tags.each do |tag|
|
76
|
+
time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
|
77
|
+
puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
|
78
|
+
puts ""
|
79
|
+
|
80
|
+
body = tag[:body]
|
81
|
+
puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
|
82
|
+
puts ""
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "print release changelog for Rubyforge"
|
87
|
+
task :release_changes do
|
88
|
+
version = ENV['VERSION'] or abort "VERSION= needed"
|
89
|
+
version = "v#{version}"
|
90
|
+
vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
|
91
|
+
prev = vtags[vtags.index(version) - 1]
|
92
|
+
system('git', 'diff', '--stat', prev, version) or abort $?
|
93
|
+
puts ""
|
94
|
+
system('git', 'log', "#{prev}..#{version}") or abort $?
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "print release notes for Rubyforge"
|
98
|
+
task :release_notes do
|
99
|
+
require 'rubygems'
|
100
|
+
|
101
|
+
spec = Gem::Specification.load('kcar.gemspec')
|
102
|
+
puts spec.description.strip
|
103
|
+
puts ""
|
104
|
+
puts "* #{spec.homepage}"
|
105
|
+
puts "* #{spec.email}"
|
106
|
+
puts "* #{git_url}"
|
107
|
+
|
108
|
+
_, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
|
109
|
+
print "\nChanges:\n\n"
|
110
|
+
puts body
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "post to RAA"
|
114
|
+
task :raa_update do
|
115
|
+
require 'rubygems'
|
116
|
+
require 'net/http'
|
117
|
+
require 'net/netrc'
|
118
|
+
rc = Net::Netrc.locate('kcar-raa') or abort "~/.netrc not found"
|
119
|
+
password = rc.password
|
120
|
+
|
121
|
+
s = Gem::Specification.load('kcar.gemspec')
|
122
|
+
desc = [ s.description.strip ]
|
123
|
+
desc << ""
|
124
|
+
desc << "* #{s.email}"
|
125
|
+
desc << "* #{git_url}"
|
126
|
+
desc << "* #{cgit_url}"
|
127
|
+
desc = desc.join("\n")
|
128
|
+
uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
|
129
|
+
form = {
|
130
|
+
:name => s.name,
|
131
|
+
:short_description => s.summary,
|
132
|
+
:version => s.version.to_s,
|
133
|
+
:status => 'experimental',
|
134
|
+
:owner => s.authors.first,
|
135
|
+
:email => s.email,
|
136
|
+
:category_major => 'Library',
|
137
|
+
:category_minor => 'WWW',
|
138
|
+
:url => s.homepage,
|
139
|
+
:download => 'http://rubyforge.org/frs/?group_id=8977',
|
140
|
+
:license => "Ruby's",
|
141
|
+
:description_style => 'Plain',
|
142
|
+
:description => desc,
|
143
|
+
:pass => password,
|
144
|
+
:submit => 'Update',
|
145
|
+
}
|
146
|
+
res = Net::HTTP.post_form(uri, form)
|
147
|
+
p res
|
148
|
+
puts res.body
|
149
|
+
end
|
data/TODO
ADDED
data/ext/kcar/c_util.h
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
/*
|
2
|
+
* Generic C functions and macros go here, there are no dependencies
|
3
|
+
* on Unicorn internal structures or the Ruby C API in here.
|
4
|
+
*/
|
5
|
+
|
6
|
+
#ifndef UH_util_h
|
7
|
+
#define UH_util_h
|
8
|
+
|
9
|
+
#include <unistd.h>
|
10
|
+
#include <assert.h>
|
11
|
+
|
12
|
+
#define MIN(a,b) (a < b ? a : b)
|
13
|
+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
14
|
+
|
15
|
+
#ifndef SIZEOF_OFF_T
|
16
|
+
# define SIZEOF_OFF_T 4
|
17
|
+
# warning SIZEOF_OFF_T not defined, guessing 4. Did you run extconf.rb?
|
18
|
+
#endif
|
19
|
+
|
20
|
+
#if SIZEOF_OFF_T == 4
|
21
|
+
# define UH_OFF_T_MAX 0x7fffffff
|
22
|
+
#elif SIZEOF_OFF_T == 8
|
23
|
+
# if SIZEOF_LONG == 4
|
24
|
+
# define UH_OFF_T_MAX 0x7fffffffffffffffLL
|
25
|
+
# else
|
26
|
+
# define UH_OFF_T_MAX 0x7fffffffffffffff
|
27
|
+
# endif
|
28
|
+
#else
|
29
|
+
# error off_t size unknown for this platform!
|
30
|
+
#endif /* SIZEOF_OFF_T check */
|
31
|
+
|
32
|
+
/*
|
33
|
+
* ragel enforces fpc as a const, and merely casting can make picky
|
34
|
+
* compilers unhappy, so we have this little helper do our dirty work
|
35
|
+
*/
|
36
|
+
static inline void *deconst(const void *in)
|
37
|
+
{
|
38
|
+
union { const void *in; void *out; } tmp;
|
39
|
+
|
40
|
+
tmp.in = in;
|
41
|
+
|
42
|
+
return tmp.out;
|
43
|
+
}
|
44
|
+
|
45
|
+
static int hexchar2int(int xdigit)
|
46
|
+
{
|
47
|
+
if (xdigit >= 'A' && xdigit <= 'F')
|
48
|
+
return xdigit - 'A' + 10;
|
49
|
+
if (xdigit >= 'a' && xdigit <= 'f')
|
50
|
+
return xdigit - 'a' + 10;
|
51
|
+
|
52
|
+
/* Ragel already does runtime range checking for us in Unicorn: */
|
53
|
+
assert(xdigit >= '0' && xdigit <= '9' && "invalid digit character");
|
54
|
+
|
55
|
+
return xdigit - '0';
|
56
|
+
}
|
57
|
+
|
58
|
+
/*
|
59
|
+
* multiplies +i+ by +base+ and increments the result by the parsed
|
60
|
+
* integer value of +xdigit+. +xdigit+ is a character byte
|
61
|
+
* representing a number the range of 0..(base-1)
|
62
|
+
* returns the new value of +i+ on success
|
63
|
+
* returns -1 on errors (including overflow)
|
64
|
+
*/
|
65
|
+
static off_t step_incr(off_t i, int xdigit, const int base)
|
66
|
+
{
|
67
|
+
static const off_t max = UH_OFF_T_MAX;
|
68
|
+
const off_t next_max = (max - (max % base)) / base;
|
69
|
+
off_t offset = hexchar2int(xdigit);
|
70
|
+
|
71
|
+
if (offset > (base - 1))
|
72
|
+
return -1;
|
73
|
+
if (i > next_max)
|
74
|
+
return -1;
|
75
|
+
i *= base;
|
76
|
+
|
77
|
+
if ((offset > (base - 1)) || ((max - i) < offset))
|
78
|
+
return -1;
|
79
|
+
|
80
|
+
return i + offset;
|
81
|
+
}
|
82
|
+
|
83
|
+
/*
|
84
|
+
* parses a non-negative length according to base-10 and
|
85
|
+
* returns it as an off_t value. Returns -1 on errors
|
86
|
+
* (including overflow).
|
87
|
+
*/
|
88
|
+
static off_t parse_length(const char *value, size_t length)
|
89
|
+
{
|
90
|
+
off_t rv;
|
91
|
+
|
92
|
+
for (rv = 0; length-- && rv >= 0; ++value) {
|
93
|
+
if (*value >= '0' && *value <= '9')
|
94
|
+
rv = step_incr(rv, *value, 10);
|
95
|
+
else
|
96
|
+
return -1;
|
97
|
+
}
|
98
|
+
|
99
|
+
return rv;
|
100
|
+
}
|
101
|
+
|
102
|
+
#define CONST_MEM_EQ(const_p, buf, len) \
|
103
|
+
((sizeof(const_p) - 1) == len && !memcmp(const_p, buf, sizeof(const_p) - 1))
|
104
|
+
|
105
|
+
#endif /* UH_util_h */
|
data/ext/kcar/ext_help.h
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#ifndef ext_help_h
|
2
|
+
#define ext_help_h
|
3
|
+
|
4
|
+
#ifndef RSTRING_PTR
|
5
|
+
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
6
|
+
#endif /* !defined(RSTRING_PTR) */
|
7
|
+
#ifndef RSTRING_LEN
|
8
|
+
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
9
|
+
#endif /* !defined(RSTRING_LEN) */
|
10
|
+
|
11
|
+
#ifndef RUBINIUS
|
12
|
+
# define rb_str_update(x) do {} while (0)
|
13
|
+
# define rb_str_flush(x) do {} while (0)
|
14
|
+
#endif /* !RUBINIUS */
|
15
|
+
|
16
|
+
#ifndef HAVE_RB_STR_SET_LEN
|
17
|
+
# ifdef RUBINIUS
|
18
|
+
# define rb_str_set_len(str,len) rb_str_resize(str,len)
|
19
|
+
# else /* 1.8.6 optimized version */
|
20
|
+
/* this is taken from Ruby 1.8.7, 1.8.6 may not have it */
|
21
|
+
static void rb_18_str_set_len(VALUE str, long len)
|
22
|
+
{
|
23
|
+
RSTRING(str)->len = len;
|
24
|
+
RSTRING(str)->ptr[len] = '\0';
|
25
|
+
rb_str_flush(str);
|
26
|
+
}
|
27
|
+
# define rb_str_set_len(str,len) rb_18_str_set_len(str,len)
|
28
|
+
# endif /* ! RUBINIUS */
|
29
|
+
#endif /* !defined(HAVE_RB_STR_SET_LEN) */
|
30
|
+
|
31
|
+
/* not all Ruby implementations support frozen objects (Rubinius does not) */
|
32
|
+
#if defined(OBJ_FROZEN)
|
33
|
+
# define assert_frozen(f) assert(OBJ_FROZEN(f) && "unfrozen object")
|
34
|
+
#else
|
35
|
+
# define assert_frozen(f) do {} while (0)
|
36
|
+
#endif /* !defined(OBJ_FROZEN) */
|
37
|
+
|
38
|
+
#if !defined(OFFT2NUM)
|
39
|
+
# if SIZEOF_OFF_T == SIZEOF_LONG
|
40
|
+
# define OFFT2NUM(n) LONG2NUM(n)
|
41
|
+
# else
|
42
|
+
# define OFFT2NUM(n) LL2NUM(n)
|
43
|
+
# endif
|
44
|
+
#endif /* ! defined(OFFT2NUM) */
|
45
|
+
|
46
|
+
#ifndef HAVE_RB_STR_MODIFY
|
47
|
+
# define rb_str_modify(x) do {} while (0)
|
48
|
+
#endif /* ! defined(HAVE_RB_STR_MODIFY) */
|
49
|
+
|
50
|
+
static inline int str_cstr_eq(VALUE val, const char *ptr, long len)
|
51
|
+
{
|
52
|
+
return (RSTRING_LEN(val) == len && !memcmp(ptr, RSTRING_PTR(val), len));
|
53
|
+
}
|
54
|
+
|
55
|
+
#define STR_CSTR_EQ(val, const_str) \
|
56
|
+
str_cstr_eq(val, const_str, sizeof(const_str) - 1)
|
57
|
+
|
58
|
+
static int cstr_case_eq(const char *a, long alen, const char *b, long blen)
|
59
|
+
{
|
60
|
+
if (alen == blen) {
|
61
|
+
for (; blen--; ++a, ++b) {
|
62
|
+
if ((*a == *b) || ((*a >= 'A' && *a <= 'Z') && (*a | 0x20) == *b))
|
63
|
+
continue;
|
64
|
+
return 0;
|
65
|
+
}
|
66
|
+
return 1;
|
67
|
+
}
|
68
|
+
return 0;
|
69
|
+
}
|
70
|
+
|
71
|
+
/* strcasecmp isn't locale independent */
|
72
|
+
static int str_cstr_case_eq(VALUE val, const char *ptr, long len)
|
73
|
+
{
|
74
|
+
return cstr_case_eq(RSTRING_PTR(val), RSTRING_LEN(val), ptr, len);
|
75
|
+
}
|
76
|
+
|
77
|
+
#define STR_CSTR_CASE_EQ(val, const_str) \
|
78
|
+
str_cstr_case_eq(val, const_str, sizeof(const_str) - 1)
|
79
|
+
#define CSTR_CASE_EQ(ptr, len, const_str) \
|
80
|
+
cstr_case_eq(ptr, len, const_str, sizeof(const_str) - 1)
|
81
|
+
|
82
|
+
#endif /* ext_help_h */
|