http://git-wip-us.apache.org/repos/asf/couchdb/blob/e62a4fc1/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c
----------------------------------------------------------------------
diff --git a/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c 
b/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c
new file mode 100644
index 0000000..0678231
--- /dev/null
+++ b/apps/couch/c_src/spawnkillable/couchspawnkillable_win.c
@@ -0,0 +1,145 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License.  You may obtain a copy 
of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+// License for the specific language governing permissions and limitations 
under
+// the License.
+
+// Do what 2 lines of shell script in couchspawnkillable does...
+// * Create a new suspended process with the same (duplicated) standard 
+//   handles as us.
+// * Write a line to stdout, consisting of the path to ourselves, plus
+//   '--kill {pid}' where {pid} is the PID of the newly created process.
+// * Un-suspend the new process.
+// * Wait for the process to terminate.
+// * Terminate with the child's exit-code.
+
+// Later, couch will call us with --kill and the PID, so we dutifully
+// terminate the specified PID.
+
+#include <stdlib.h>
+#include "windows.h"
+
+char *get_child_cmdline(int argc, char **argv)
+{
+    // make a new command-line, but skipping me.
+    // XXX - todo - spaces etc in args???
+    int i;
+    char *p, *cmdline;
+    int nchars = 0;
+    int nthis = 1;
+    for (i=1;i<argc;i++)
+        nchars += strlen(argv[i])+1;
+    cmdline = p = malloc(nchars+1);
+    if (!cmdline)
+        return NULL;
+    for (i=1;i<argc;i++) {
+        nthis = strlen(argv[i]);
+        strncpy(p, argv[i], nthis);
+        p[nthis] = ' ';
+        p += nthis+1;
+    }
+    // Replace the last space we added above with a '\0'
+    cmdline[nchars-1] = '\0';
+    return cmdline;
+}
+
+// create the child process, returning 0, or the exit-code we will
+// terminate with.
+int create_child(int argc, char **argv, PROCESS_INFORMATION *pi)
+{
+    char buf[1024];
+    DWORD dwcreate;
+    STARTUPINFO si;
+    char *cmdline;
+    if (argc < 2)
+        return 1;
+    cmdline = get_child_cmdline(argc, argv);
+    if (!cmdline)
+        return 2;
+
+    memset(&si, 0, sizeof(si));
+    si.cb = sizeof(si);
+    // depending on how *our* parent is started, we may or may not have
+    // a valid stderr stream - so although we try and duplicate it, only
+    // failing to duplicate stdin and stdout are considered fatal.
+    if (!DuplicateHandle(GetCurrentProcess(),
+                       GetStdHandle(STD_INPUT_HANDLE),
+                       GetCurrentProcess(),
+                       &si.hStdInput,
+                       0,
+                       TRUE, // inheritable
+                       DUPLICATE_SAME_ACCESS) ||
+       !DuplicateHandle(GetCurrentProcess(),
+                       GetStdHandle(STD_OUTPUT_HANDLE),
+                       GetCurrentProcess(),
+                       &si.hStdOutput,
+                       0,
+                       TRUE, // inheritable
+                       DUPLICATE_SAME_ACCESS)) {
+        return 3;
+    }
+    DuplicateHandle(GetCurrentProcess(),
+                   GetStdHandle(STD_ERROR_HANDLE),
+                   GetCurrentProcess(),
+                   &si.hStdError,
+                   0,
+                   TRUE, // inheritable
+                   DUPLICATE_SAME_ACCESS);
+
+    si.dwFlags = STARTF_USESTDHANDLES;
+    dwcreate = CREATE_SUSPENDED;
+    if (!CreateProcess( NULL, cmdline,
+                        NULL,
+                        NULL,
+                        TRUE, // inherit handles
+                        dwcreate,
+                        NULL, // environ
+                        NULL, // cwd
+                        &si,
+                        pi))
+        return 4;
+    return 0;
+}
+
+// and here we go...
+int main(int argc, char **argv)
+{
+    char out_buf[1024];
+    int rc;
+    DWORD cbwritten;
+    DWORD exitcode;
+    PROCESS_INFORMATION pi;
+    if (argc==3 && strcmp(argv[1], "--kill")==0) {
+        HANDLE h = OpenProcess(PROCESS_TERMINATE, 0, atoi(argv[2]));
+        if (!h)
+            return 1;
+        if (!TerminateProcess(h, 0))
+            return 2;
+        CloseHandle(h);
+        return 0;
+    }
+    // spawn the new suspended process
+    rc = create_child(argc, argv, &pi);
+    if (rc)
+        return rc;
+    // Write the 'terminate' command, which includes this PID, back to couch.
+    // *sob* - what about spaces etc?
+    sprintf_s(out_buf, sizeof(out_buf), "%s --kill %d\n", 
+              argv[0], pi.dwProcessId);
+    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), out_buf, strlen(out_buf), 
+              &cbwritten, NULL);
+    // Let the child process go...
+    ResumeThread(pi.hThread);
+    // Wait for the process to terminate so we can reflect the exit code
+    // back to couch.
+    WaitForSingleObject(pi.hProcess, INFINITE);
+    if (!GetExitCodeProcess(pi.hProcess, &exitcode))
+        return 6;
+    return exitcode;
+}

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e62a4fc1/apps/couch/include/couch_db.hrl
----------------------------------------------------------------------
diff --git a/apps/couch/include/couch_db.hrl b/apps/couch/include/couch_db.hrl
new file mode 100644
index 0000000..e0a1c82
--- /dev/null
+++ b/apps/couch/include/couch_db.hrl
@@ -0,0 +1,286 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-define(LOCAL_DOC_PREFIX, "_local/").
+-define(DESIGN_DOC_PREFIX0, "_design").
+-define(DESIGN_DOC_PREFIX, "_design/").
+-define(DEFAULT_COMPRESSION, snappy).
+
+-define(MIN_STR, <<"">>).
+-define(MAX_STR, <<255>>). % illegal utf string
+
+% the lowest possible database sequence number
+-define(LOWEST_SEQ, 0).
+
+-define(REWRITE_COUNT, couch_rewrite_count).
+
+-define(JSON_ENCODE(V), jiffy:encode(V, [uescape])).
+-define(JSON_DECODE(V), couch_util:json_decode(V)).
+
+-define(b2l(V), binary_to_list(V)).
+-define(l2b(V), list_to_binary(V)).
+-define(term_to_bin(T), term_to_binary(T, [{minor_version, 1}])).
+-define(term_size(T),
+    try
+        erlang:external_size(T)
+    catch _:_ ->
+        byte_size(?term_to_bin(T))
+    end).
+
+-define(DEFAULT_ATTACHMENT_CONTENT_TYPE, <<"application/octet-stream">>).
+
+-define(LOG_DEBUG(Format, Args),
+    case couch_log:debug_on(?MODULE) of
+        true ->
+            couch_log:debug(Format, Args);
+        false -> ok
+    end).
+
+-define(LOG_INFO(Format, Args),
+    case couch_log:info_on(?MODULE) of
+        true ->
+            couch_log:info(Format, Args);
+        false -> ok
+    end).
+
+-define(LOG_WARN(Format, Args),
+    case couch_log:warn_on(?MODULE) of
+        true ->
+            couch_log:warn(Format, Args);
+        false -> ok
+    end).
+
+-define(LOG_ERROR(Format, Args), couch_log:error(Format, Args)).
+
+% Tree::term() is really a tree(), but we don't want to require R13B04 yet
+-type branch() :: {Key::term(), Value::term(), Tree::term()}.
+-type path() :: {Start::pos_integer(), branch()}.
+-type tree() :: [branch()]. % sorted by key
+
+-record(rev_info,
+    {
+    rev,
+    seq = 0,
+    deleted = false,
+    body_sp = nil % stream pointer
+    }).
+
+-record(doc_info,
+    {
+    id = <<"">>,
+    high_seq = 0,
+    revs = [] % rev_info
+    }).
+
+-record(full_doc_info,
+    {id = <<"">>,
+    update_seq = 0,
+    deleted = false,
+    rev_tree = [],
+    leafs_size = 0
+    }).
+
+-record(httpd,
+    {mochi_req,
+    peer,
+    method,
+    requested_path_parts,
+    path_parts,
+    db_url_handlers,
+    user_ctx,
+    req_body = undefined,
+    design_url_handlers,
+    auth,
+    default_fun,
+    url_handlers
+    }).
+
+
+-record(doc,
+    {
+    id = <<"">>,
+    revs = {0, []},
+
+    % the json body object.
+    body = {[]},
+
+    atts = [], % attachments
+
+    deleted = false,
+
+    % key/value tuple of meta information, provided when using special options:
+    % couch_db:open_doc(Db, Id, Options).
+    meta = []
+    }).
+
+
+-record(att,
+    {
+    name,
+    type,
+    att_len,
+    disk_len, % length of the attachment in its identity form
+              % (that is, without a content encoding applied to it)
+              % differs from att_len when encoding /= identity
+    md5= <<>>,
+    revpos=0,
+    data,
+    encoding=identity % currently supported values are:
+                      %     identity, gzip
+                      % additional values to support in the future:
+                      %     deflate, compress
+    }).
+
+
+-record(user_ctx,
+    {
+    name=null,
+    roles=[],
+    handler
+    }).
+
+% This should be updated anytime a header change happens that requires more
+% than filling in new defaults.
+%
+% As long the changes are limited to new header fields (with inline
+% defaults) added to the end of the record, then there is no need to increment
+% the disk revision number.
+%
+% if the disk revision is incremented, then new upgrade logic will need to be
+% added to couch_db_updater:init_db.
+
+-define(LATEST_DISK_VERSION, 6).
+
+-record(db_header,
+    {disk_version = ?LATEST_DISK_VERSION,
+     update_seq = 0,
+     unused = 0,
+     fulldocinfo_by_id_btree_state = nil,
+     docinfo_by_seq_btree_state = nil,
+     local_docs_btree_state = nil,
+     purge_seq = 0,
+     purged_docs = nil,
+     security_ptr = nil,
+     revs_limit = 1000
+    }).
+
+-record(db,
+    {main_pid = nil,
+    update_pid = nil,
+    compactor_pid = nil,
+    instance_start_time, % number of microsecs since jan 1 1970 as a binary 
string
+    fd,
+    updater_fd,
+    fd_ref_counter,
+    header = #db_header{},
+    committed_update_seq,
+    fulldocinfo_by_id_btree,
+    docinfo_by_seq_btree,
+    local_docs_btree,
+    update_seq,
+    name,
+    filepath,
+    validate_doc_funs = [],
+    security = [],
+    security_ptr = nil,
+    user_ctx = #user_ctx{},
+    waiting_delayed_commit = nil,
+    revs_limit = 1000,
+    fsync_options = [],
+    options = [],
+    compression,
+    before_doc_update = nil, % nil | fun(Doc, Db) -> NewDoc
+    after_doc_read = nil     % nil | fun(Doc, Db) -> NewDoc
+    }).
+
+
+-record(view_query_args, {
+    start_key,
+    end_key,
+    start_docid = ?MIN_STR,
+    end_docid = ?MAX_STR,
+
+    direction = fwd,
+    inclusive_end=true, % aka a closed-interval
+
+    limit = 10000000000, % Huge number to simplify logic
+    skip = 0,
+
+    group_level = 0,
+
+    view_type = nil,
+    include_docs = false,
+    doc_options = [],
+    conflicts = false,
+    stale = false,
+    multi_get = false,
+    callback = nil,
+    list = nil
+}).
+
+-record(view_fold_helper_funs, {
+    reduce_count,
+    passed_end,
+    start_response,
+    send_row
+}).
+
+-record(reduce_fold_helper_funs, {
+    start_response,
+    send_row
+}).
+
+-record(extern_resp_args, {
+    code = 200,
+    stop = false,
+    data = <<>>,
+    ctype = "application/json",
+    headers = [],
+    json = nil
+}).
+
+-record(index_header,
+    {seq=0,
+    purge_seq=0,
+    id_btree_state=nil,
+    view_states=nil
+    }).
+
+% small value used in revision trees to indicate the revision isn't stored
+-define(REV_MISSING, []).
+
+-record(changes_args, {
+    feed = "normal",
+    dir = fwd,
+    since = 0,
+    limit = 1000000000000000,
+    style = main_only,
+    heartbeat,
+    timeout,
+    filter = "",
+    filter_fun,
+    filter_args = [],
+    include_docs = false,
+    doc_options = [],
+    conflicts = false,
+    db_open_options = []
+}).
+
+-record(btree, {
+    fd,
+    root,
+    extract_kv = fun({_Key, _Value} = KV) -> KV end,
+    assemble_kv = fun(Key, Value) -> {Key, Value} end,
+    less = fun(A, B) -> A < B end,
+    reduce = nil,
+    compression = ?DEFAULT_COMPRESSION
+}).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e62a4fc1/apps/couch/include/couch_js_functions.hrl
----------------------------------------------------------------------
diff --git a/apps/couch/include/couch_js_functions.hrl 
b/apps/couch/include/couch_js_functions.hrl
new file mode 100644
index 0000000..a48feae
--- /dev/null
+++ b/apps/couch/include/couch_js_functions.hrl
@@ -0,0 +1,170 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy 
of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-define(AUTH_DB_DOC_VALIDATE_FUNCTION, <<"
+    function(newDoc, oldDoc, userCtx, secObj) {
+        if (newDoc._deleted === true) {
+            // allow deletes by admins and matching users
+            // without checking the other fields
+            if ((userCtx.roles.indexOf('_admin') !== -1) ||
+                (userCtx.name == oldDoc.name)) {
+                return;
+            } else {
+                throw({forbidden: 'Only admins may delete other user docs.'});
+            }
+        }
+
+        if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') {
+            throw({forbidden : 'doc.type must be user'});
+        } // we only allow user docs for now
+
+        if (!newDoc.name) {
+            throw({forbidden: 'doc.name is required'});
+        }
+
+        if (!newDoc.roles) {
+            throw({forbidden: 'doc.roles must exist'});
+        }
+
+        if (!isArray(newDoc.roles)) {
+            throw({forbidden: 'doc.roles must be an array'});
+        }
+
+        for (var idx = 0; idx < newDoc.roles.length; idx++) {
+            if (typeof newDoc.roles[idx] !== 'string') {
+                throw({forbidden: 'doc.roles can only contain strings'});
+            }
+        }
+
+        if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) {
+            throw({
+                forbidden: 'Doc ID must be of the form org.couchdb.user:name'
+            });
+        }
+
+        if (oldDoc) { // validate all updates
+            if (oldDoc.name !== newDoc.name) {
+                throw({forbidden: 'Usernames can not be changed.'});
+            }
+        }
+
+        if (newDoc.password_sha && !newDoc.salt) {
+            throw({
+                forbidden: 'Users with password_sha must have a salt.' +
+                    'See /_utils/script/couch.js for example code.'
+            });
+        }
+
+        if (newDoc.password_scheme === \"pbkdf2\") {
+            if (typeof(newDoc.iterations) !== \"number\") {
+               throw({forbidden: \"iterations must be a number.\"});
+            }
+            if (typeof(newDoc.derived_key) !== \"string\") {
+               throw({forbidden: \"derived_key must be a string.\"});
+            }
+        }
+
+        var is_server_or_database_admin = function(userCtx, secObj) {
+            // see if the user is a server admin
+            if(userCtx.roles.indexOf('_admin') !== -1) {
+                return true; // a server admin
+            }
+
+            // see if the user a database admin specified by name
+            if(secObj && secObj.admins && secObj.admins.names) {
+                if(secObj.admins.names.indexOf(userCtx.name) !== -1) {
+                    return true; // database admin
+                }
+            }
+
+            // see if the user a database admin specified by role
+            if(secObj && secObj.admins && secObj.admins.roles) {
+                var db_roles = secObj.admins.roles;
+                for(var idx = 0; idx < userCtx.roles.length; idx++) {
+                    var user_role = userCtx.roles[idx];
+                    if(db_roles.indexOf(user_role) !== -1) {
+                        return true; // role matches!
+                    }
+                }
+            }
+
+            return false; // default to no admin
+        }
+
+        if (!is_server_or_database_admin(userCtx, secObj)) {
+            if (oldDoc) { // validate non-admin updates
+                if (userCtx.name !== newDoc.name) {
+                    throw({
+                        forbidden: 'You may only update your own user 
document.'
+                    });
+                }
+                // validate role updates
+                var oldRoles = oldDoc.roles.sort();
+                var newRoles = newDoc.roles.sort();
+
+                if (oldRoles.length !== newRoles.length) {
+                    throw({forbidden: 'Only _admin may edit roles'});
+                }
+
+                for (var i = 0; i < oldRoles.length; i++) {
+                    if (oldRoles[i] !== newRoles[i]) {
+                        throw({forbidden: 'Only _admin may edit roles'});
+                    }
+                }
+            } else if (newDoc.roles.length > 0) {
+                throw({forbidden: 'Only _admin may set roles'});
+            }
+        }
+
+        // no system roles in users db
+        for (var i = 0; i < newDoc.roles.length; i++) {
+            if (newDoc.roles[i][0] === '_') {
+                throw({
+                    forbidden:
+                    'No system roles (starting with underscore) in users db.'
+                });
+            }
+        }
+
+        // no system names as names
+        if (newDoc.name[0] === '_') {
+            throw({forbidden: 'Username may not start with underscore.'});
+        }
+
+        var badUserNameChars = [':'];
+
+        for (var i = 0; i < badUserNameChars.length; i++) {
+            if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) {
+                throw({forbidden: 'Character `' + badUserNameChars[i] +
+                        '` is not allowed in usernames.'});
+            }
+        }
+    }
+">>).
+
+
+-define(OAUTH_MAP_FUN, <<"
+    function(doc) {
+        if (doc.type === 'user' && doc.oauth && doc.oauth.consumer_keys) {
+            for (var consumer_key in doc.oauth.consumer_keys) {
+                for (var token in doc.oauth.tokens) {
+                    var obj = {
+                        'consumer_secret': 
doc.oauth.consumer_keys[consumer_key],
+                        'token_secret': doc.oauth.tokens[token],
+                        'username': doc.name
+                    };
+                    emit([consumer_key, token], obj);
+                }
+            }
+        }
+    }
+">>).

Reply via email to