parent
0785506fad
commit
32d3ceeaf3
@ -1,22 +0,0 @@ |
|||||||
# Getting Started |
|
||||||
|
|
||||||
### Reference Documentation |
|
||||||
For further reference, please consider the following sections: |
|
||||||
|
|
||||||
* [Official Gradle documentation](https://docs.gradle.org) |
|
||||||
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/gradle-plugin/reference/html/) |
|
||||||
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/gradle-plugin/reference/html/#build-image) |
|
||||||
* [Spring Web](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications) |
|
||||||
|
|
||||||
### Guides |
|
||||||
The following guides illustrate how to use some features concretely: |
|
||||||
|
|
||||||
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) |
|
||||||
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) |
|
||||||
* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) |
|
||||||
|
|
||||||
### Additional Links |
|
||||||
These additional references should also help you: |
|
||||||
|
|
||||||
* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) |
|
||||||
|
|
@ -1,24 +0,0 @@ |
|||||||
plugins { |
|
||||||
id 'org.springframework.boot' version '2.3.1.RELEASE' |
|
||||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE' |
|
||||||
id 'java' |
|
||||||
} |
|
||||||
|
|
||||||
group = 'com.example' |
|
||||||
version = '0.0.1-SNAPSHOT' |
|
||||||
sourceCompatibility = '1.8' |
|
||||||
|
|
||||||
repositories { |
|
||||||
mavenCentral() |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web' |
|
||||||
testImplementation('org.springframework.boot:spring-boot-starter-test') { |
|
||||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
test { |
|
||||||
useJUnitPlatform() |
|
||||||
} |
|
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@ |
|||||||
server.port=4503 |
|
Binary file not shown.
@ -1,5 +0,0 @@ |
|||||||
distributionBase=GRADLE_USER_HOME |
|
||||||
distributionPath=wrapper/dists |
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip |
|
||||||
zipStoreBase=GRADLE_USER_HOME |
|
||||||
zipStorePath=wrapper/dists |
|
@ -1,185 +0,0 @@ |
|||||||
#!/usr/bin/env sh |
|
||||||
|
|
||||||
# |
|
||||||
# Copyright 2015 the original author or authors. |
|
||||||
# |
|
||||||
# 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 |
|
||||||
# |
|
||||||
# https://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. |
|
||||||
# |
|
||||||
|
|
||||||
############################################################################## |
|
||||||
## |
|
||||||
## Gradle start up script for UN*X |
|
||||||
## |
|
||||||
############################################################################## |
|
||||||
|
|
||||||
# Attempt to set APP_HOME |
|
||||||
# Resolve links: $0 may be a link |
|
||||||
PRG="$0" |
|
||||||
# Need this for relative symlinks. |
|
||||||
while [ -h "$PRG" ] ; do |
|
||||||
ls=`ls -ld "$PRG"` |
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
|
||||||
if expr "$link" : '/.*' > /dev/null; then |
|
||||||
PRG="$link" |
|
||||||
else |
|
||||||
PRG=`dirname "$PRG"`"/$link" |
|
||||||
fi |
|
||||||
done |
|
||||||
SAVED="`pwd`" |
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null |
|
||||||
APP_HOME="`pwd -P`" |
|
||||||
cd "$SAVED" >/dev/null |
|
||||||
|
|
||||||
APP_NAME="Gradle" |
|
||||||
APP_BASE_NAME=`basename "$0"` |
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value. |
|
||||||
MAX_FD="maximum" |
|
||||||
|
|
||||||
warn () { |
|
||||||
echo "$*" |
|
||||||
} |
|
||||||
|
|
||||||
die () { |
|
||||||
echo |
|
||||||
echo "$*" |
|
||||||
echo |
|
||||||
exit 1 |
|
||||||
} |
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false'). |
|
||||||
cygwin=false |
|
||||||
msys=false |
|
||||||
darwin=false |
|
||||||
nonstop=false |
|
||||||
case "`uname`" in |
|
||||||
CYGWIN* ) |
|
||||||
cygwin=true |
|
||||||
;; |
|
||||||
Darwin* ) |
|
||||||
darwin=true |
|
||||||
;; |
|
||||||
MINGW* ) |
|
||||||
msys=true |
|
||||||
;; |
|
||||||
NONSTOP* ) |
|
||||||
nonstop=true |
|
||||||
;; |
|
||||||
esac |
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM. |
|
||||||
if [ -n "$JAVA_HOME" ] ; then |
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
|
||||||
# IBM's JDK on AIX uses strange locations for the executables |
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
|
||||||
else |
|
||||||
JAVACMD="$JAVA_HOME/bin/java" |
|
||||||
fi |
|
||||||
if [ ! -x "$JAVACMD" ] ; then |
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the |
|
||||||
location of your Java installation." |
|
||||||
fi |
|
||||||
else |
|
||||||
JAVACMD="java" |
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the |
|
||||||
location of your Java installation." |
|
||||||
fi |
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can. |
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
|
||||||
MAX_FD_LIMIT=`ulimit -H -n` |
|
||||||
if [ $? -eq 0 ] ; then |
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
|
||||||
MAX_FD="$MAX_FD_LIMIT" |
|
||||||
fi |
|
||||||
ulimit -n $MAX_FD |
|
||||||
if [ $? -ne 0 ] ; then |
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD" |
|
||||||
fi |
|
||||||
else |
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
|
||||||
fi |
|
||||||
fi |
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock |
|
||||||
if $darwin; then |
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
|
||||||
fi |
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java |
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"` |
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath |
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
|
||||||
SEP="" |
|
||||||
for dir in $ROOTDIRSRAW ; do |
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir" |
|
||||||
SEP="|" |
|
||||||
done |
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))" |
|
||||||
# Add a user-defined pattern to the cygpath arguments |
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
|
||||||
fi |
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
|
||||||
i=0 |
|
||||||
for arg in "$@" ; do |
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
|
||||||
else |
|
||||||
eval `echo args$i`="\"$arg\"" |
|
||||||
fi |
|
||||||
i=`expr $i + 1` |
|
||||||
done |
|
||||||
case $i in |
|
||||||
0) set -- ;; |
|
||||||
1) set -- "$args0" ;; |
|
||||||
2) set -- "$args0" "$args1" ;; |
|
||||||
3) set -- "$args0" "$args1" "$args2" ;; |
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
|
||||||
esac |
|
||||||
fi |
|
||||||
|
|
||||||
# Escape application args |
|
||||||
save () { |
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
|
||||||
echo " " |
|
||||||
} |
|
||||||
APP_ARGS=`save "$@"` |
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules |
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
|
||||||
|
|
||||||
exec "$JAVACMD" "$@" |
|
@ -1,104 +0,0 @@ |
|||||||
@rem |
|
||||||
@rem Copyright 2015 the original author or authors. |
|
||||||
@rem |
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
@rem you may not use this file except in compliance with the License. |
|
||||||
@rem You may obtain a copy of the License at |
|
||||||
@rem |
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0 |
|
||||||
@rem |
|
||||||
@rem Unless required by applicable law or agreed to in writing, software |
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
@rem See the License for the specific language governing permissions and |
|
||||||
@rem limitations under the License. |
|
||||||
@rem |
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off |
|
||||||
@rem ########################################################################## |
|
||||||
@rem |
|
||||||
@rem Gradle startup script for Windows |
|
||||||
@rem |
|
||||||
@rem ########################################################################## |
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell |
|
||||||
if "%OS%"=="Windows_NT" setlocal |
|
||||||
|
|
||||||
set DIRNAME=%~dp0 |
|
||||||
if "%DIRNAME%" == "" set DIRNAME=. |
|
||||||
set APP_BASE_NAME=%~n0 |
|
||||||
set APP_HOME=%DIRNAME% |
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter. |
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
|
||||||
|
|
||||||
@rem Find java.exe |
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome |
|
||||||
|
|
||||||
set JAVA_EXE=java.exe |
|
||||||
%JAVA_EXE% -version >NUL 2>&1 |
|
||||||
if "%ERRORLEVEL%" == "0" goto init |
|
||||||
|
|
||||||
echo. |
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
|
||||||
echo. |
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the |
|
||||||
echo location of your Java installation. |
|
||||||
|
|
||||||
goto fail |
|
||||||
|
|
||||||
:findJavaFromJavaHome |
|
||||||
set JAVA_HOME=%JAVA_HOME:"=% |
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init |
|
||||||
|
|
||||||
echo. |
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
|
||||||
echo. |
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the |
|
||||||
echo location of your Java installation. |
|
||||||
|
|
||||||
goto fail |
|
||||||
|
|
||||||
:init |
|
||||||
@rem Get command-line arguments, handling Windows variants |
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args |
|
||||||
|
|
||||||
:win9xME_args |
|
||||||
@rem Slurp the command line arguments. |
|
||||||
set CMD_LINE_ARGS= |
|
||||||
set _SKIP=2 |
|
||||||
|
|
||||||
:win9xME_args_slurp |
|
||||||
if "x%~1" == "x" goto execute |
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%* |
|
||||||
|
|
||||||
:execute |
|
||||||
@rem Setup the command line |
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle |
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% |
|
||||||
|
|
||||||
:end |
|
||||||
@rem End local scope for the variables with windows NT shell |
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd |
|
||||||
|
|
||||||
:fail |
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
|
||||||
rem the _cmd.exe /c_ return code! |
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
|
||||||
exit /b 1 |
|
||||||
|
|
||||||
:mainEnd |
|
||||||
if "%OS%"=="Windows_NT" endlocal |
|
||||||
|
|
||||||
:omega |
|
@ -0,0 +1 @@ |
|||||||
|
Subproject commit c23231e281c8d55f275be235158f326244e1c445 |
@ -1 +0,0 @@ |
|||||||
rootProject.name = 'demo' |
|
@ -1,19 +0,0 @@ |
|||||||
package com.example.demo; |
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping; |
|
||||||
import org.springframework.web.bind.annotation.RestController; |
|
||||||
import org.springframework.web.bind.annotation.RequestParam; |
|
||||||
import java.util.HashMap; |
|
||||||
|
|
||||||
@RestController |
|
||||||
public class Controller { |
|
||||||
|
|
||||||
@GetMapping("/image") |
|
||||||
public HashMap<String,String> helloWorld(@RequestParam("url") String url){ |
|
||||||
HashMap<String,String> map = new HashMap<>(); |
|
||||||
map.put("test1",Integer.toString(1)); |
|
||||||
map.put("test2","Hello World"); |
|
||||||
return map; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
package com.example.demo; |
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping; |
|
||||||
import org.springframework.web.bind.annotation.RestController; |
|
||||||
|
|
||||||
@RestController |
|
||||||
public class Controller { |
|
||||||
|
|
||||||
@GetMapping("/") |
|
||||||
public String helloWorld() { |
|
||||||
return "Hello from Spring!"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,13 +0,0 @@ |
|||||||
package com.example.demo; |
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication; |
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|
||||||
|
|
||||||
@SpringBootApplication |
|
||||||
public class DemoApplication { |
|
||||||
|
|
||||||
public static void main(String[] args) { |
|
||||||
SpringApplication.run(DemoApplication.class, args); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
server.port=4503 |
|
@ -1,13 +0,0 @@ |
|||||||
package com.example.demo; |
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test; |
|
||||||
import org.springframework.boot.test.context.SpringBootTest; |
|
||||||
|
|
||||||
@SpringBootTest |
|
||||||
class DemoApplicationTests { |
|
||||||
|
|
||||||
@Test |
|
||||||
void contextLoads() { |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1 +1,2 @@ |
|||||||
.env.twitter |
.env.twitter |
||||||
|
files |
@ -0,0 +1,16 @@ |
|||||||
|
sudo: false |
||||||
|
language: cpp |
||||||
|
notifications: |
||||||
|
email: false |
||||||
|
env: |
||||||
|
matrix: |
||||||
|
- TRAVIS_NODE_VERSION="4" |
||||||
|
- TRAVIS_NODE_VERSION="6" |
||||||
|
- TRAVIS_NODE_VERSION="8" |
||||||
|
- TRAVIS_NODE_VERSION="10" |
||||||
|
install: |
||||||
|
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION |
||||||
|
- node --version |
||||||
|
- npm --version |
||||||
|
- npm install |
||||||
|
script: npm test |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright Brian White. All rights reserved. |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to |
||||||
|
deal in the Software without restriction, including without limitation the |
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
||||||
|
sell copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
||||||
|
IN THE SOFTWARE. |
@ -0,0 +1,222 @@ |
|||||||
|
Description |
||||||
|
=========== |
||||||
|
|
||||||
|
A node.js module for parsing incoming HTML form data. |
||||||
|
|
||||||
|
|
||||||
|
Requirements |
||||||
|
============ |
||||||
|
|
||||||
|
* [node.js](http://nodejs.org/) -- v4.5.0 or newer |
||||||
|
|
||||||
|
|
||||||
|
Install |
||||||
|
======= |
||||||
|
|
||||||
|
npm install busboy |
||||||
|
|
||||||
|
|
||||||
|
Examples |
||||||
|
======== |
||||||
|
|
||||||
|
* Parsing (multipart) with default options: |
||||||
|
|
||||||
|
```javascript |
||||||
|
var http = require('http'), |
||||||
|
inspect = require('util').inspect; |
||||||
|
|
||||||
|
var Busboy = require('busboy'); |
||||||
|
|
||||||
|
http.createServer(function(req, res) { |
||||||
|
if (req.method === 'POST') { |
||||||
|
var busboy = new Busboy({ headers: req.headers }); |
||||||
|
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { |
||||||
|
console.log('File [' + fieldname + ']: filename: ' + filename + ', encoding: ' + encoding + ', mimetype: ' + mimetype); |
||||||
|
file.on('data', function(data) { |
||||||
|
console.log('File [' + fieldname + '] got ' + data.length + ' bytes'); |
||||||
|
}); |
||||||
|
file.on('end', function() { |
||||||
|
console.log('File [' + fieldname + '] Finished'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) { |
||||||
|
console.log('Field [' + fieldname + ']: value: ' + inspect(val)); |
||||||
|
}); |
||||||
|
busboy.on('finish', function() { |
||||||
|
console.log('Done parsing form!'); |
||||||
|
res.writeHead(303, { Connection: 'close', Location: '/' }); |
||||||
|
res.end(); |
||||||
|
}); |
||||||
|
req.pipe(busboy); |
||||||
|
} else if (req.method === 'GET') { |
||||||
|
res.writeHead(200, { Connection: 'close' }); |
||||||
|
res.end('<html><head></head><body>\ |
||||||
|
<form method="POST" enctype="multipart/form-data">\ |
||||||
|
<input type="text" name="textfield"><br />\ |
||||||
|
<input type="file" name="filefield"><br />\ |
||||||
|
<input type="submit">\ |
||||||
|
</form>\ |
||||||
|
</body></html>'); |
||||||
|
} |
||||||
|
}).listen(8000, function() { |
||||||
|
console.log('Listening for requests'); |
||||||
|
}); |
||||||
|
|
||||||
|
// Example output, using http://nodejs.org/images/ryan-speaker.jpg as the file: |
||||||
|
// |
||||||
|
// Listening for requests |
||||||
|
// File [filefield]: filename: ryan-speaker.jpg, encoding: binary |
||||||
|
// File [filefield] got 11971 bytes |
||||||
|
// Field [textfield]: value: 'testing! :-)' |
||||||
|
// File [filefield] Finished |
||||||
|
// Done parsing form! |
||||||
|
``` |
||||||
|
|
||||||
|
* Save all incoming files to disk: |
||||||
|
|
||||||
|
```javascript |
||||||
|
var http = require('http'), |
||||||
|
path = require('path'), |
||||||
|
os = require('os'), |
||||||
|
fs = require('fs'); |
||||||
|
|
||||||
|
var Busboy = require('busboy'); |
||||||
|
|
||||||
|
http.createServer(function(req, res) { |
||||||
|
if (req.method === 'POST') { |
||||||
|
var busboy = new Busboy({ headers: req.headers }); |
||||||
|
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { |
||||||
|
var saveTo = path.join(os.tmpDir(), path.basename(fieldname)); |
||||||
|
file.pipe(fs.createWriteStream(saveTo)); |
||||||
|
}); |
||||||
|
busboy.on('finish', function() { |
||||||
|
res.writeHead(200, { 'Connection': 'close' }); |
||||||
|
res.end("That's all folks!"); |
||||||
|
}); |
||||||
|
return req.pipe(busboy); |
||||||
|
} |
||||||
|
res.writeHead(404); |
||||||
|
res.end(); |
||||||
|
}).listen(8000, function() { |
||||||
|
console.log('Listening for requests'); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
* Parsing (urlencoded) with default options: |
||||||
|
|
||||||
|
```javascript |
||||||
|
var http = require('http'), |
||||||
|
inspect = require('util').inspect; |
||||||
|
|
||||||
|
var Busboy = require('busboy'); |
||||||
|
|
||||||
|
http.createServer(function(req, res) { |
||||||
|
if (req.method === 'POST') { |
||||||
|
var busboy = new Busboy({ headers: req.headers }); |
||||||
|
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { |
||||||
|
console.log('File [' + fieldname + ']: filename: ' + filename); |
||||||
|
file.on('data', function(data) { |
||||||
|
console.log('File [' + fieldname + '] got ' + data.length + ' bytes'); |
||||||
|
}); |
||||||
|
file.on('end', function() { |
||||||
|
console.log('File [' + fieldname + '] Finished'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) { |
||||||
|
console.log('Field [' + fieldname + ']: value: ' + inspect(val)); |
||||||
|
}); |
||||||
|
busboy.on('finish', function() { |
||||||
|
console.log('Done parsing form!'); |
||||||
|
res.writeHead(303, { Connection: 'close', Location: '/' }); |
||||||
|
res.end(); |
||||||
|
}); |
||||||
|
req.pipe(busboy); |
||||||
|
} else if (req.method === 'GET') { |
||||||
|
res.writeHead(200, { Connection: 'close' }); |
||||||
|
res.end('<html><head></head><body>\ |
||||||
|
<form method="POST">\ |
||||||
|
<input type="text" name="textfield"><br />\ |
||||||
|
<select name="selectfield">\ |
||||||
|
<option value="1">1</option>\ |
||||||
|
<option value="10">10</option>\ |
||||||
|
<option value="100">100</option>\ |
||||||
|
<option value="9001">9001</option>\ |
||||||
|
</select><br />\ |
||||||
|
<input type="checkbox" name="checkfield">Node.js rules!<br />\ |
||||||
|
<input type="submit">\ |
||||||
|
</form>\ |
||||||
|
</body></html>'); |
||||||
|
} |
||||||
|
}).listen(8000, function() { |
||||||
|
console.log('Listening for requests'); |
||||||
|
}); |
||||||
|
|
||||||
|
// Example output: |
||||||
|
// |
||||||
|
// Listening for requests |
||||||
|
// Field [textfield]: value: 'testing! :-)' |
||||||
|
// Field [selectfield]: value: '9001' |
||||||
|
// Field [checkfield]: value: 'on' |
||||||
|
// Done parsing form! |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
API |
||||||
|
=== |
||||||
|
|
||||||
|
_Busboy_ is a _Writable_ stream |
||||||
|
|
||||||
|
Busboy (special) events |
||||||
|
----------------------- |
||||||
|
|
||||||
|
* **file**(< _string_ >fieldname, < _ReadableStream_ >stream, < _string_ >filename, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new file form field found. `transferEncoding` contains the 'Content-Transfer-Encoding' value for the file stream. `mimeType` contains the 'Content-Type' value for the file stream. |
||||||
|
* Note: if you listen for this event, you should always handle the `stream` no matter if you care about the file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents), otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any** incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically and safely discarded (these discarded files do still count towards `files` and `parts` limits). |
||||||
|
* If a configured file size limit was reached, `stream` will both have a boolean property `truncated` (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens. |
||||||
|
|
||||||
|
* **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new non-file field found. |
||||||
|
|
||||||
|
* **partsLimit**() - Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted. |
||||||
|
|
||||||
|
* **filesLimit**() - Emitted when specified `files` limit has been reached. No more 'file' events will be emitted. |
||||||
|
|
||||||
|
* **fieldsLimit**() - Emitted when specified `fields` limit has been reached. No more 'field' events will be emitted. |
||||||
|
|
||||||
|
|
||||||
|
Busboy methods |
||||||
|
-------------- |
||||||
|
|
||||||
|
* **(constructor)**(< _object_ >config) - Creates and returns a new Busboy instance. |
||||||
|
|
||||||
|
* The constructor takes the following valid `config` settings: |
||||||
|
|
||||||
|
* **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers. |
||||||
|
|
||||||
|
* **highWaterMark** - _integer_ - highWaterMark to use for this Busboy instance (Default: WritableStream default). |
||||||
|
|
||||||
|
* **fileHwm** - _integer_ - highWaterMark to use for file streams (Default: ReadableStream default). |
||||||
|
|
||||||
|
* **defCharset** - _string_ - Default character set to use when one isn't defined (Default: 'utf8'). |
||||||
|
|
||||||
|
* **preservePath** - _boolean_ - If paths in the multipart 'filename' field shall be preserved. (Default: false). |
||||||
|
|
||||||
|
* **limits** - _object_ - Various limits on incoming data. Valid properties are: |
||||||
|
|
||||||
|
* **fieldNameSize** - _integer_ - Max field name size (in bytes) (Default: 100 bytes). |
||||||
|
|
||||||
|
* **fieldSize** - _integer_ - Max field value size (in bytes) (Default: 1MB). |
||||||
|
|
||||||
|
* **fields** - _integer_ - Max number of non-file fields (Default: Infinity). |
||||||
|
|
||||||
|
* **fileSize** - _integer_ - For multipart forms, the max file size (in bytes) (Default: Infinity). |
||||||
|
|
||||||
|
* **files** - _integer_ - For multipart forms, the max number of file fields (Default: Infinity). |
||||||
|
|
||||||
|
* **parts** - _integer_ - For multipart forms, the max number of parts (fields + files) (Default: Infinity). |
||||||
|
|
||||||
|
* **headerPairs** - _integer_ - For multipart forms, the max number of header key=>value pairs to parse **Default:** 2000 (same as node's http). |
||||||
|
|
||||||
|
* The constructor can throw errors: |
||||||
|
|
||||||
|
* **Unsupported content type: $type** - The `Content-Type` isn't one Busboy can parse. |
||||||
|
|
||||||
|
* **Missing Content-Type** - The provided headers don't include `Content-Type` at all. |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,88 @@ |
|||||||
|
var fs = require('fs'), |
||||||
|
WritableStream = require('stream').Writable, |
||||||
|
inherits = require('util').inherits; |
||||||
|
|
||||||
|
var parseParams = require('./utils').parseParams; |
||||||
|
|
||||||
|
function Busboy(opts) { |
||||||
|
if (!(this instanceof Busboy)) |
||||||
|
return new Busboy(opts); |
||||||
|
if (opts.highWaterMark !== undefined) |
||||||
|
WritableStream.call(this, { highWaterMark: opts.highWaterMark }); |
||||||
|
else |
||||||
|
WritableStream.call(this); |
||||||
|
|
||||||
|
this._done = false; |
||||||
|
this._parser = undefined; |
||||||
|
this._finished = false; |
||||||
|
|
||||||
|
this.opts = opts; |
||||||
|
if (opts.headers && typeof opts.headers['content-type'] === 'string') |
||||||
|
this.parseHeaders(opts.headers); |
||||||
|
else |
||||||
|
throw new Error('Missing Content-Type'); |
||||||
|
} |
||||||
|
inherits(Busboy, WritableStream); |
||||||
|
|
||||||
|
Busboy.prototype.emit = function(ev) { |
||||||
|
if (ev === 'finish') { |
||||||
|
if (!this._done) { |
||||||
|
this._parser && this._parser.end(); |
||||||
|
return; |
||||||
|
} else if (this._finished) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this._finished = true; |
||||||
|
} |
||||||
|
WritableStream.prototype.emit.apply(this, arguments); |
||||||
|
}; |
||||||
|
|
||||||
|
Busboy.prototype.parseHeaders = function(headers) { |
||||||
|
this._parser = undefined; |
||||||
|
if (headers['content-type']) { |
||||||
|
var parsed = parseParams(headers['content-type']), |
||||||
|
matched, type; |
||||||
|
for (var i = 0; i < TYPES.length; ++i) { |
||||||
|
type = TYPES[i]; |
||||||
|
if (typeof type.detect === 'function') |
||||||
|
matched = type.detect(parsed); |
||||||
|
else |
||||||
|
matched = type.detect.test(parsed[0]); |
||||||
|
if (matched) |
||||||
|
break; |
||||||
|
} |
||||||
|
if (matched) { |
||||||
|
var cfg = { |
||||||
|
limits: this.opts.limits, |
||||||
|
headers: headers, |
||||||
|
parsedConType: parsed, |
||||||
|
highWaterMark: undefined, |
||||||
|
fileHwm: undefined, |
||||||
|
defCharset: undefined, |
||||||
|
preservePath: false |
||||||
|
}; |
||||||
|
if (this.opts.highWaterMark) |
||||||
|
cfg.highWaterMark = this.opts.highWaterMark; |
||||||
|
if (this.opts.fileHwm) |
||||||
|
cfg.fileHwm = this.opts.fileHwm; |
||||||
|
cfg.defCharset = this.opts.defCharset; |
||||||
|
cfg.preservePath = this.opts.preservePath; |
||||||
|
this._parser = type(this, cfg); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
throw new Error('Unsupported content type: ' + headers['content-type']); |
||||||
|
}; |
||||||
|
|
||||||
|
Busboy.prototype._write = function(chunk, encoding, cb) { |
||||||
|
if (!this._parser) |
||||||
|
return cb(new Error('Not ready to parse. Missing Content-Type?')); |
||||||
|
this._parser.write(chunk, cb); |
||||||
|
}; |
||||||
|
|
||||||
|
var TYPES = [ |
||||||
|
require('./types/multipart'), |
||||||
|
require('./types/urlencoded'), |
||||||
|
]; |
||||||
|
|
||||||
|
module.exports = Busboy; |
@ -0,0 +1,325 @@ |
|||||||
|
// TODO:
|
||||||
|
// * support 1 nested multipart level
|
||||||
|
// (see second multipart example here:
|
||||||
|
// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data)
|
||||||
|
// * support limits.fieldNameSize
|
||||||
|
// -- this will require modifications to utils.parseParams
|
||||||
|
|
||||||
|
var ReadableStream = require('stream').Readable, |
||||||
|
inherits = require('util').inherits; |
||||||
|
|
||||||
|
var Dicer = require('dicer'); |
||||||
|
|
||||||
|
var parseParams = require('../utils').parseParams, |
||||||
|
decodeText = require('../utils').decodeText, |
||||||
|
basename = require('../utils').basename; |
||||||
|
|
||||||
|
var RE_BOUNDARY = /^boundary$/i, |
||||||
|
RE_FIELD = /^form-data$/i, |
||||||
|
RE_CHARSET = /^charset$/i, |
||||||
|
RE_FILENAME = /^filename$/i, |
||||||
|
RE_NAME = /^name$/i; |
||||||
|
|
||||||
|
Multipart.detect = /^multipart\/form-data/i; |
||||||
|
function Multipart(boy, cfg) { |
||||||
|
if (!(this instanceof Multipart)) |
||||||
|
return new Multipart(boy, cfg); |
||||||
|
var i, |
||||||
|
len, |
||||||
|
self = this, |
||||||
|
boundary, |
||||||
|
limits = cfg.limits, |
||||||
|
parsedConType = cfg.parsedConType || [], |
||||||
|
defCharset = cfg.defCharset || 'utf8', |
||||||
|
preservePath = cfg.preservePath, |
||||||
|
fileopts = (typeof cfg.fileHwm === 'number' |
||||||
|
? { highWaterMark: cfg.fileHwm } |
||||||
|
: {}); |
||||||
|
|
||||||
|
for (i = 0, len = parsedConType.length; i < len; ++i) { |
||||||
|
if (Array.isArray(parsedConType[i]) |
||||||
|
&& RE_BOUNDARY.test(parsedConType[i][0])) { |
||||||
|
boundary = parsedConType[i][1]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function checkFinished() { |
||||||
|
if (nends === 0 && finished && !boy._done) { |
||||||
|
finished = false; |
||||||
|
process.nextTick(function() { |
||||||
|
boy._done = true; |
||||||
|
boy.emit('finish'); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (typeof boundary !== 'string') |
||||||
|
throw new Error('Multipart: Boundary not found'); |
||||||
|
|
||||||
|
var fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' |
||||||
|
? limits.fieldSize |
||||||
|
: 1 * 1024 * 1024), |
||||||
|
fileSizeLimit = (limits && typeof limits.fileSize === 'number' |
||||||
|
? limits.fileSize |
||||||
|
: Infinity), |
||||||
|
filesLimit = (limits && typeof limits.files === 'number' |
||||||
|
? limits.files |
||||||
|
: Infinity), |
||||||
|
fieldsLimit = (limits && typeof limits.fields === 'number' |
||||||
|
? limits.fields |
||||||
|
: Infinity), |
||||||
|
partsLimit = (limits && typeof limits.parts === 'number' |
||||||
|
? limits.parts |
||||||
|
: Infinity); |
||||||
|
|
||||||
|
var nfiles = 0, |
||||||
|
nfields = 0, |
||||||
|
nends = 0, |
||||||
|
curFile, |
||||||
|
curField, |
||||||
|
finished = false; |
||||||
|
|
||||||
|
this._needDrain = false; |
||||||
|
this._pause = false; |
||||||
|
this._cb = undefined; |
||||||
|
this._nparts = 0; |
||||||
|
this._boy = boy; |
||||||
|
|
||||||
|
var parserCfg = { |
||||||
|
boundary: boundary, |
||||||
|
maxHeaderPairs: (limits && limits.headerPairs) |
||||||
|
}; |
||||||
|
if (fileopts.highWaterMark) |
||||||
|
parserCfg.partHwm = fileopts.highWaterMark; |
||||||
|
if (cfg.highWaterMark) |
||||||
|
parserCfg.highWaterMark = cfg.highWaterMark; |
||||||
|
|
||||||
|
this.parser = new Dicer(parserCfg); |
||||||
|
this.parser.on('drain', function() { |
||||||
|
self._needDrain = false; |
||||||
|
if (self._cb && !self._pause) { |
||||||
|
var cb = self._cb; |
||||||
|
self._cb = undefined; |
||||||
|
cb(); |
||||||
|
} |
||||||
|
}).on('part', function onPart(part) { |
||||||
|
if (++self._nparts > partsLimit) { |
||||||
|
self.parser.removeListener('part', onPart); |
||||||
|
self.parser.on('part', skipPart); |
||||||
|
boy.hitPartsLimit = true; |
||||||
|
boy.emit('partsLimit'); |
||||||
|
return skipPart(part); |
||||||
|
} |
||||||
|
|
||||||
|
// hack because streams2 _always_ doesn't emit 'end' until nextTick, so let
|
||||||
|
// us emit 'end' early since we know the part has ended if we are already
|
||||||
|
// seeing the next part
|
||||||
|
if (curField) { |
||||||
|
var field = curField; |
||||||
|
field.emit('end'); |
||||||
|
field.removeAllListeners('end'); |
||||||
|
} |
||||||
|
|
||||||
|
part.on('header', function(header) { |
||||||
|
var contype, |
||||||
|
fieldname, |
||||||
|
parsed, |
||||||
|
charset, |
||||||
|
encoding, |
||||||
|
filename, |
||||||
|
nsize = 0; |
||||||
|
|
||||||
|
if (header['content-type']) { |
||||||
|
parsed = parseParams(header['content-type'][0]); |
||||||
|
if (parsed[0]) { |
||||||
|
contype = parsed[0].toLowerCase(); |
||||||
|
for (i = 0, len = parsed.length; i < len; ++i) { |
||||||
|
if (RE_CHARSET.test(parsed[i][0])) { |
||||||
|
charset = parsed[i][1].toLowerCase(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (contype === undefined) |
||||||
|
contype = 'text/plain'; |
||||||
|
if (charset === undefined) |
||||||
|
charset = defCharset; |
||||||
|
|
||||||
|
if (header['content-disposition']) { |
||||||
|
parsed = parseParams(header['content-disposition'][0]); |
||||||
|
if (!RE_FIELD.test(parsed[0])) |
||||||
|
return skipPart(part); |
||||||
|
for (i = 0, len = parsed.length; i < len; ++i) { |
||||||
|
if (RE_NAME.test(parsed[i][0])) { |
||||||
|
fieldname = decodeText(parsed[i][1], 'binary', 'utf8'); |
||||||
|
} else if (RE_FILENAME.test(parsed[i][0])) { |
||||||
|
filename = decodeText(parsed[i][1], 'binary', 'utf8'); |
||||||
|
if (!preservePath) |
||||||
|
filename = basename(filename); |
||||||
|
} |
||||||
|
} |
||||||
|
} else |
||||||
|
return skipPart(part); |
||||||
|
|
||||||
|
if (header['content-transfer-encoding']) |
||||||
|
encoding = header['content-transfer-encoding'][0].toLowerCase(); |
||||||
|
else |
||||||
|
encoding = '7bit'; |
||||||
|
|
||||||
|
var onData, |
||||||
|
onEnd; |
||||||
|
if (contype === 'application/octet-stream' || filename !== undefined) { |
||||||
|
// file/binary field
|
||||||
|
if (nfiles === filesLimit) { |
||||||
|
if (!boy.hitFilesLimit) { |
||||||
|
boy.hitFilesLimit = true; |
||||||
|
boy.emit('filesLimit'); |
||||||
|
} |
||||||
|
return skipPart(part); |
||||||
|
} |
||||||
|
|
||||||
|
++nfiles; |
||||||
|
|
||||||
|
if (!boy._events.file) { |
||||||
|
self.parser._ignore(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
++nends; |
||||||
|
var file = new FileStream(fileopts); |
||||||
|
curFile = file; |
||||||
|
file.on('end', function() { |
||||||
|
--nends; |
||||||
|
self._pause = false; |
||||||
|
checkFinished(); |
||||||
|
if (self._cb && !self._needDrain) { |
||||||
|
var cb = self._cb; |
||||||
|
self._cb = undefined; |
||||||
|
cb(); |
||||||
|
} |
||||||
|
}); |
||||||
|
file._read = function(n) { |
||||||
|
if (!self._pause) |
||||||
|
return; |
||||||
|
self._pause = false; |
||||||
|
if (self._cb && !self._needDrain) { |
||||||
|
var cb = self._cb; |
||||||
|
self._cb = undefined; |
||||||
|
cb(); |
||||||
|
} |
||||||
|
}; |
||||||
|
boy.emit('file', fieldname, file, filename, encoding, contype); |
||||||
|
|
||||||
|
onData = function(data) { |
||||||
|
if ((nsize += data.length) > fileSizeLimit) { |
||||||
|
var extralen = (fileSizeLimit - (nsize - data.length)); |
||||||
|
if (extralen > 0) |
||||||
|
file.push(data.slice(0, extralen)); |
||||||
|
file.emit('limit'); |
||||||
|
file.truncated = true; |
||||||
|
part.removeAllListeners('data'); |
||||||
|
} else if (!file.push(data)) |
||||||
|
self._pause = true; |
||||||
|
}; |
||||||
|
|
||||||
|
onEnd = function() { |
||||||
|
curFile = undefined; |
||||||
|
file.push(null); |
||||||
|
}; |
||||||
|
} else { |
||||||
|
// non-file field
|
||||||
|
if (nfields === fieldsLimit) { |
||||||
|
if (!boy.hitFieldsLimit) { |
||||||
|
boy.hitFieldsLimit = true; |
||||||
|
boy.emit('fieldsLimit'); |
||||||
|
} |
||||||
|
return skipPart(part); |
||||||
|
} |
||||||
|
|
||||||
|
++nfields; |
||||||
|
++nends; |
||||||
|
var buffer = '', |
||||||
|
truncated = false; |
||||||
|
curField = part; |
||||||
|
|
||||||
|
onData = function(data) { |
||||||
|
if ((nsize += data.length) > fieldSizeLimit) { |
||||||
|
var extralen = (fieldSizeLimit - (nsize - data.length)); |
||||||
|
buffer += data.toString('binary', 0, extralen); |
||||||
|
truncated = true; |
||||||
|
part.removeAllListeners('data'); |
||||||
|
} else |
||||||
|
buffer += data.toString('binary'); |
||||||
|
}; |
||||||
|
|
||||||
|
onEnd = function() { |
||||||
|
curField = undefined; |
||||||
|
if (buffer.length) |
||||||
|
buffer = decodeText(buffer, 'binary', charset); |
||||||
|
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype); |
||||||
|
--nends; |
||||||
|
checkFinished(); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become |
||||||
|
broken. Streams2/streams3 is a huge black box of confusion, but |
||||||
|
somehow overriding the sync state seems to fix things again (and still |
||||||
|
seems to work for previous node versions). |
||||||
|
*/ |
||||||
|
part._readableState.sync = false; |
||||||
|
|
||||||
|
part.on('data', onData); |
||||||
|
part.on('end', onEnd); |
||||||
|
}).on('error', function(err) { |
||||||
|
if (curFile) |
||||||
|
curFile.emit('error', err); |
||||||
|
}); |
||||||
|
}).on('error', function(err) { |
||||||
|
boy.emit('error', err); |
||||||
|
}).on('finish', function() { |
||||||
|
finished = true; |
||||||
|
checkFinished(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
Multipart.prototype.write = function(chunk, cb) { |
||||||
|
var r; |
||||||
|
if ((r = this.parser.write(chunk)) && !this._pause) |
||||||
|
cb(); |
||||||
|
else { |
||||||
|
this._needDrain = !r; |
||||||
|
this._cb = cb; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Multipart.prototype.end = function() { |
||||||
|
var self = this; |
||||||
|
if (this._nparts === 0 && !self._boy._done) { |
||||||
|
process.nextTick(function() { |
||||||
|
self._boy._done = true; |
||||||
|
self._boy.emit('finish'); |
||||||
|
}); |
||||||
|
} else if (this.parser.writable) |
||||||
|
this.parser.end(); |
||||||
|
}; |
||||||
|
|
||||||
|
function skipPart(part) { |
||||||
|
part.resume(); |
||||||
|
} |
||||||
|
|
||||||
|
function FileStream(opts) { |
||||||
|
if (!(this instanceof FileStream)) |
||||||
|
return new FileStream(opts); |
||||||
|
ReadableStream.call(this, opts); |
||||||
|
|
||||||
|
this.truncated = false; |
||||||
|
} |
||||||
|
inherits(FileStream, ReadableStream); |
||||||
|
|
||||||
|
FileStream.prototype._read = function(n) {}; |
||||||
|
|
||||||
|
module.exports = Multipart; |
@ -0,0 +1,214 @@ |
|||||||
|
var Decoder = require('../utils').Decoder, |
||||||
|
decodeText = require('../utils').decodeText; |
||||||
|
|
||||||
|
var RE_CHARSET = /^charset$/i; |
||||||
|
|
||||||
|
UrlEncoded.detect = /^application\/x-www-form-urlencoded/i; |
||||||
|
function UrlEncoded(boy, cfg) { |
||||||
|
if (!(this instanceof UrlEncoded)) |
||||||
|
return new UrlEncoded(boy, cfg); |
||||||
|
var limits = cfg.limits, |
||||||
|
headers = cfg.headers, |
||||||
|
parsedConType = cfg.parsedConType; |
||||||
|
this.boy = boy; |
||||||
|
|
||||||
|
this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' |
||||||
|
? limits.fieldSize |
||||||
|
: 1 * 1024 * 1024); |
||||||
|
this.fieldNameSizeLimit = (limits && typeof limits.fieldNameSize === 'number' |
||||||
|
? limits.fieldNameSize |
||||||
|
: 100); |
||||||
|
this.fieldsLimit = (limits && typeof limits.fields === 'number' |
||||||
|
? limits.fields |
||||||
|
: Infinity); |
||||||
|
|
||||||
|
var charset; |
||||||
|
for (var i = 0, len = parsedConType.length; i < len; ++i) { |
||||||
|
if (Array.isArray(parsedConType[i]) |
||||||
|
&& RE_CHARSET.test(parsedConType[i][0])) { |
||||||
|
charset = parsedConType[i][1].toLowerCase(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (charset === undefined) |
||||||
|
charset = cfg.defCharset || 'utf8'; |
||||||
|
|
||||||
|
this.decoder = new Decoder(); |
||||||
|
this.charset = charset; |
||||||
|
this._fields = 0; |
||||||
|
this._state = 'key'; |
||||||
|
this._checkingBytes = true; |
||||||
|
this._bytesKey = 0; |
||||||
|
this._bytesVal = 0; |
||||||
|
this._key = ''; |
||||||
|
this._val = ''; |
||||||
|
this._keyTrunc = false; |
||||||
|
this._valTrunc = false; |
||||||
|
this._hitlimit = false; |
||||||
|
} |
||||||
|
|
||||||
|
UrlEncoded.prototype.write = function(data, cb) { |
||||||
|
if (this._fields === this.fieldsLimit) { |
||||||
|
if (!this.boy.hitFieldsLimit) { |
||||||
|
this.boy.hitFieldsLimit = true; |
||||||
|
this.boy.emit('fieldsLimit'); |
||||||
|
} |
||||||
|
return cb(); |
||||||
|
} |
||||||
|
|
||||||
|
var idxeq, idxamp, i, p = 0, len = data.length; |
||||||
|
|
||||||
|
while (p < len) { |
||||||
|
if (this._state === 'key') { |
||||||
|
idxeq = idxamp = undefined; |
||||||
|
for (i = p; i < len; ++i) { |
||||||
|
if (!this._checkingBytes) |
||||||
|
++p; |
||||||
|
if (data[i] === 0x3D/*=*/) { |
||||||
|
idxeq = i; |
||||||
|
break; |
||||||
|
} else if (data[i] === 0x26/*&*/) { |
||||||
|
idxamp = i; |
||||||
|
break; |
||||||
|
} |
||||||
|
if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) { |
||||||
|
this._hitLimit = true; |
||||||
|
break; |
||||||
|
} else if (this._checkingBytes) |
||||||
|
++this._bytesKey; |
||||||
|
} |
||||||
|
|
||||||
|
if (idxeq !== undefined) { |
||||||
|
// key with assignment
|
||||||
|
if (idxeq > p) |
||||||
|
this._key += this.decoder.write(data.toString('binary', p, idxeq)); |
||||||
|
this._state = 'val'; |
||||||
|
|
||||||
|
this._hitLimit = false; |
||||||
|
this._checkingBytes = true; |
||||||
|
this._val = ''; |
||||||
|
this._bytesVal = 0; |
||||||
|
this._valTrunc = false; |
||||||
|
this.decoder.reset(); |
||||||
|
|
||||||
|
p = idxeq + 1; |
||||||
|
} else if (idxamp !== undefined) { |
||||||
|
// key with no assignment
|
||||||
|
++this._fields; |
||||||
|
var key, keyTrunc = this._keyTrunc; |
||||||
|
if (idxamp > p) |
||||||
|
key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))); |
||||||
|
else |
||||||
|
key = this._key; |
||||||
|
|
||||||
|
this._hitLimit = false; |
||||||
|
this._checkingBytes = true; |
||||||
|
this._key = ''; |
||||||
|
this._bytesKey = 0; |
||||||
|
this._keyTrunc = false; |
||||||
|
this.decoder.reset(); |
||||||
|
|
||||||
|
if (key.length) { |
||||||
|
this.boy.emit('field', decodeText(key, 'binary', this.charset), |
||||||
|
'', |
||||||
|
keyTrunc, |
||||||
|
false); |
||||||
|
} |
||||||
|
|
||||||
|
p = idxamp + 1; |
||||||
|
if (this._fields === this.fieldsLimit) |
||||||
|
return cb(); |
||||||
|
} else if (this._hitLimit) { |
||||||
|
// we may not have hit the actual limit if there are encoded bytes...
|
||||||
|
if (i > p) |
||||||
|
this._key += this.decoder.write(data.toString('binary', p, i)); |
||||||
|
p = i; |
||||||
|
if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) { |
||||||
|
// yep, we actually did hit the limit
|
||||||
|
this._checkingBytes = false; |
||||||
|
this._keyTrunc = true; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (p < len) |
||||||
|
this._key += this.decoder.write(data.toString('binary', p)); |
||||||
|
p = len; |
||||||
|
} |
||||||
|
} else { |
||||||
|
idxamp = undefined; |
||||||
|
for (i = p; i < len; ++i) { |
||||||
|
if (!this._checkingBytes) |
||||||
|
++p; |
||||||
|
if (data[i] === 0x26/*&*/) { |
||||||
|
idxamp = i; |
||||||
|
break; |
||||||
|
} |
||||||
|
if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) { |
||||||
|
this._hitLimit = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
else if (this._checkingBytes) |
||||||
|
++this._bytesVal; |
||||||
|
} |
||||||
|
|
||||||
|
if (idxamp !== undefined) { |
||||||
|
++this._fields; |
||||||
|
if (idxamp > p) |
||||||
|
this._val += this.decoder.write(data.toString('binary', p, idxamp)); |
||||||
|
this.boy.emit('field', decodeText(this._key, 'binary', this.charset), |
||||||
|
decodeText(this._val, 'binary', this.charset), |
||||||
|
this._keyTrunc, |
||||||
|
this._valTrunc); |
||||||
|
this._state = 'key'; |
||||||
|
|
||||||
|
this._hitLimit = false; |
||||||
|
this._checkingBytes = true; |
||||||
|
this._key = ''; |
||||||
|
this._bytesKey = 0; |
||||||
|
this._keyTrunc = false; |
||||||
|
this.decoder.reset(); |
||||||
|
|
||||||
|
p = idxamp + 1; |
||||||
|
if (this._fields === this.fieldsLimit) |
||||||
|
return cb(); |
||||||
|
} else if (this._hitLimit) { |
||||||
|
// we may not have hit the actual limit if there are encoded bytes...
|
||||||
|
if (i > p) |
||||||
|
this._val += this.decoder.write(data.toString('binary', p, i)); |
||||||
|
p = i; |
||||||
|
if ((this._val === '' && this.fieldSizeLimit === 0) |
||||||
|
|| (this._bytesVal = this._val.length) === this.fieldSizeLimit) { |
||||||
|
// yep, we actually did hit the limit
|
||||||
|
this._checkingBytes = false; |
||||||
|
this._valTrunc = true; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (p < len) |
||||||
|
this._val += this.decoder.write(data.toString('binary', p)); |
||||||
|
p = len; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
cb(); |
||||||
|
}; |
||||||
|
|
||||||
|
UrlEncoded.prototype.end = function() { |
||||||
|
if (this.boy._done) |
||||||
|
return; |
||||||
|
|
||||||
|
if (this._state === 'key' && this._key.length > 0) { |
||||||
|
this.boy.emit('field', decodeText(this._key, 'binary', this.charset), |
||||||
|
'', |
||||||
|
this._keyTrunc, |
||||||
|
false); |
||||||
|
} else if (this._state === 'val') { |
||||||
|
this.boy.emit('field', decodeText(this._key, 'binary', this.charset), |
||||||
|
decodeText(this._val, 'binary', this.charset), |
||||||
|
this._keyTrunc, |
||||||
|
this._valTrunc); |
||||||
|
} |
||||||
|
this.boy._done = true; |
||||||
|
this.boy.emit('finish'); |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = UrlEncoded; |
@ -0,0 +1,172 @@ |
|||||||
|
var jsencoding = require('../deps/encoding/encoding'); |
||||||
|
|
||||||
|
var RE_ENCODED = /%([a-fA-F0-9]{2})/g; |
||||||
|
function encodedReplacer(match, byte) { |
||||||
|
return String.fromCharCode(parseInt(byte, 16)); |
||||||
|
} |
||||||
|
function parseParams(str) { |
||||||
|
var res = [], |
||||||
|
state = 'key', |
||||||
|
charset = '', |
||||||
|
inquote = false, |
||||||
|
escaping = false, |
||||||
|
p = 0, |
||||||
|
tmp = ''; |
||||||
|
|
||||||
|
for (var i = 0, len = str.length; i < len; ++i) { |
||||||
|
if (str[i] === '\\' && inquote) { |
||||||
|
if (escaping) |
||||||
|
escaping = false; |
||||||
|
else { |
||||||
|
escaping = true; |
||||||
|
continue; |
||||||
|
} |
||||||
|
} else if (str[i] === '"') { |
||||||
|
if (!escaping) { |
||||||
|
if (inquote) { |
||||||
|
inquote = false; |
||||||
|
state = 'key'; |
||||||
|
} else |
||||||
|
inquote = true; |
||||||
|
continue; |
||||||
|
} else |
||||||
|
escaping = false; |
||||||
|
} else { |
||||||
|
if (escaping && inquote) |
||||||
|
tmp += '\\'; |
||||||
|
escaping = false; |
||||||
|
if ((state === 'charset' || state === 'lang') && str[i] === "'") { |
||||||
|
if (state === 'charset') { |
||||||
|
state = 'lang'; |
||||||
|
charset = tmp.substring(1); |
||||||
|
} else |
||||||
|
state = 'value'; |
||||||
|
tmp = ''; |
||||||
|
continue; |
||||||
|
} else if (state === 'key' |
||||||
|
&& (str[i] === '*' || str[i] === '=') |
||||||
|
&& res.length) { |
||||||
|
if (str[i] === '*') |
||||||
|
state = 'charset'; |
||||||
|
else |
||||||
|
state = 'value'; |
||||||
|
res[p] = [tmp, undefined]; |
||||||
|
tmp = ''; |
||||||
|
continue; |
||||||
|
} else if (!inquote && str[i] === ';') { |
||||||
|
state = 'key'; |
||||||
|
if (charset) { |
||||||
|
if (tmp.length) { |
||||||
|
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), |
||||||
|
'binary', |
||||||
|
charset); |
||||||
|
} |
||||||
|
charset = ''; |
||||||
|
} |
||||||
|
if (res[p] === undefined) |
||||||
|
res[p] = tmp; |
||||||
|
else |
||||||
|
res[p][1] = tmp; |
||||||
|
tmp = ''; |
||||||
|
++p; |
||||||
|
continue; |
||||||
|
} else if (!inquote && (str[i] === ' ' || str[i] === '\t')) |
||||||
|
continue; |
||||||
|
} |
||||||
|
tmp += str[i]; |
||||||
|
} |
||||||
|
if (charset && tmp.length) { |
||||||
|
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), |
||||||
|
'binary', |
||||||
|
charset); |
||||||
|
} |
||||||
|
|
||||||
|
if (res[p] === undefined) { |
||||||
|
if (tmp) |
||||||
|
res[p] = tmp; |
||||||
|
} else |
||||||
|
res[p][1] = tmp; |
||||||
|
|
||||||
|
return res; |
||||||
|
}; |
||||||
|
exports.parseParams = parseParams; |
||||||
|
|
||||||
|
|
||||||
|
function decodeText(text, textEncoding, destEncoding) { |
||||||
|
var ret; |
||||||
|
if (text && jsencoding.encodingExists(destEncoding)) { |
||||||
|
try { |
||||||
|
ret = jsencoding.TextDecoder(destEncoding) |
||||||
|
.decode(Buffer.from(text, textEncoding)); |
||||||
|
} catch(e) {} |
||||||
|
} |
||||||
|
return (typeof ret === 'string' ? ret : text); |
||||||
|
} |
||||||
|
exports.decodeText = decodeText; |
||||||
|
|
||||||
|
|
||||||
|
var HEX = [ |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
||||||
|
], RE_PLUS = /\+/g; |
||||||
|
function Decoder() { |
||||||
|
this.buffer = undefined; |
||||||
|
} |
||||||
|
Decoder.prototype.write = function(str) { |
||||||
|
// Replace '+' with ' ' before decoding
|
||||||
|
str = str.replace(RE_PLUS, ' '); |
||||||
|
var res = ''; |
||||||
|
var i = 0, p = 0, len = str.length; |
||||||
|
for (; i < len; ++i) { |
||||||
|
if (this.buffer !== undefined) { |
||||||
|
if (!HEX[str.charCodeAt(i)]) { |
||||||
|
res += '%' + this.buffer; |
||||||
|
this.buffer = undefined; |
||||||
|
--i; // retry character
|
||||||
|
} else { |
||||||
|
this.buffer += str[i]; |
||||||
|
++p; |
||||||
|
if (this.buffer.length === 2) { |
||||||
|
res += String.fromCharCode(parseInt(this.buffer, 16)); |
||||||
|
this.buffer = undefined; |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (str[i] === '%') { |
||||||
|
if (i > p) { |
||||||
|
res += str.substring(p, i); |
||||||
|
p = i; |
||||||
|
} |
||||||
|
this.buffer = ''; |
||||||
|
++p; |
||||||
|
} |
||||||
|
} |
||||||
|
if (p < len && this.buffer === undefined) |
||||||
|
res += str.substring(p); |
||||||
|
return res; |
||||||
|
}; |
||||||
|
Decoder.prototype.reset = function() { |
||||||
|
this.buffer = undefined; |
||||||
|
}; |
||||||
|
exports.Decoder = Decoder; |
||||||
|
|
||||||
|
|
||||||
|
function basename(path) { |
||||||
|
if (typeof path !== 'string') |
||||||
|
return ''; |
||||||
|
for (var i = path.length - 1; i >= 0; --i) { |
||||||
|
switch (path.charCodeAt(i)) { |
||||||
|
case 0x2F: // '/'
|
||||||
|
case 0x5C: // '\'
|
||||||
|
path = path.slice(i + 1); |
||||||
|
return (path === '..' || path === '.' ? '' : path); |
||||||
|
} |
||||||
|
} |
||||||
|
return (path === '..' || path === '.' ? '' : path); |
||||||
|
} |
||||||
|
exports.basename = basename; |
@ -0,0 +1,64 @@ |
|||||||
|
{ |
||||||
|
"_from": "busboy@^0.3.1", |
||||||
|
"_id": "busboy@0.3.1", |
||||||
|
"_inBundle": false, |
||||||
|
"_integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", |
||||||
|
"_location": "/busboy", |
||||||
|
"_phantomChildren": {}, |
||||||
|
"_requested": { |
||||||
|
"type": "range", |
||||||
|
"registry": true, |
||||||
|
"raw": "busboy@^0.3.1", |
||||||
|
"name": "busboy", |
||||||
|
"escapedName": "busboy", |
||||||
|
"rawSpec": "^0.3.1", |
||||||
|
"saveSpec": null, |
||||||
|
"fetchSpec": "^0.3.1" |
||||||
|
}, |
||||||
|
"_requiredBy": [ |
||||||
|
"/express-fileupload" |
||||||
|
], |
||||||
|
"_resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", |
||||||
|
"_shasum": "170899274c5bf38aae27d5c62b71268cd585fd1b", |
||||||
|
"_spec": "busboy@^0.3.1", |
||||||
|
"_where": "/home/sigonasr2/divar/server/node_modules/express-fileupload", |
||||||
|
"author": { |
||||||
|
"name": "Brian White", |
||||||
|
"email": "mscdex@mscdex.net" |
||||||
|
}, |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/mscdex/busboy/issues" |
||||||
|
}, |
||||||
|
"bundleDependencies": false, |
||||||
|
"dependencies": { |
||||||
|
"dicer": "0.3.0" |
||||||
|
}, |
||||||
|
"deprecated": false, |
||||||
|
"description": "A streaming parser for HTML form data for node.js", |
||||||
|
"engines": { |
||||||
|
"node": ">=4.5.0" |
||||||
|
}, |
||||||
|
"homepage": "https://github.com/mscdex/busboy#readme", |
||||||
|
"keywords": [ |
||||||
|
"uploads", |
||||||
|
"forms", |
||||||
|
"multipart", |
||||||
|
"form-data" |
||||||
|
], |
||||||
|
"licenses": [ |
||||||
|
{ |
||||||
|
"type": "MIT", |
||||||
|
"url": "http://github.com/mscdex/busboy/raw/master/LICENSE" |
||||||
|
} |
||||||
|
], |
||||||
|
"main": "./lib/main", |
||||||
|
"name": "busboy", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git+ssh://git@github.com/mscdex/busboy.git" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"test": "node test/test.js" |
||||||
|
}, |
||||||
|
"version": "0.3.1" |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
var Busboy = require('..'); |
||||||
|
|
||||||
|
var path = require('path'); |
||||||
|
var inspect = require('util').inspect; |
||||||
|
var assert = require('assert'); |
||||||
|
|
||||||
|
function formDataSection(key, value) { |
||||||
|
return Buffer.from('\r\n--' + BOUNDARY |
||||||
|
+ '\r\nContent-Disposition: form-data; name="' |
||||||
|
+ key + '"\r\n\r\n' + value); |
||||||
|
} |
||||||
|
function formDataFile(key, filename, contentType) { |
||||||
|
return Buffer.concat([ |
||||||
|
Buffer.from('\r\n--' + BOUNDARY + '\r\n'), |
||||||
|
Buffer.from('Content-Disposition: form-data; name="' |
||||||
|
+ key + '"; filename="' + filename + '"\r\n'), |
||||||
|
Buffer.from('Content-Type: ' + contentType + '\r\n\r\n'), |
||||||
|
Buffer.allocUnsafe(100000) |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
var BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh'; |
||||||
|
var reqChunks = [ |
||||||
|
Buffer.concat([ |
||||||
|
formDataFile('file', 'file.bin', 'application/octet-stream'), |
||||||
|
formDataSection('foo', 'foo value') |
||||||
|
]), |
||||||
|
formDataSection('bar', 'bar value'), |
||||||
|
Buffer.from('\r\n--' + BOUNDARY + '--\r\n') |
||||||
|
]; |
||||||
|
var busboy = new Busboy({ |
||||||
|
headers: { |
||||||
|
'content-type': 'multipart/form-data; boundary=' + BOUNDARY |
||||||
|
} |
||||||
|
}); |
||||||
|
var finishes = 0; |
||||||
|
var results = []; |
||||||
|
var expected = [ |
||||||
|
['file', 'file', 'file.bin', '7bit', 'application/octet-stream'], |
||||||
|
['field', 'foo', 'foo value', false, false, '7bit', 'text/plain'], |
||||||
|
['field', 'bar', 'bar value', false, false, '7bit', 'text/plain'], |
||||||
|
]; |
||||||
|
|
||||||
|
busboy.on('field', function(key, val, keyTrunc, valTrunc, encoding, contype) { |
||||||
|
results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]); |
||||||
|
}); |
||||||
|
busboy.on('file', function(fieldname, stream, filename, encoding, mimeType) { |
||||||
|
results.push(['file', fieldname, filename, encoding, mimeType]); |
||||||
|
// Simulate a pipe where the destination is pausing (perhaps due to waiting
|
||||||
|
// for file system write to finish)
|
||||||
|
setTimeout(function() { |
||||||
|
stream.resume(); |
||||||
|
}, 10); |
||||||
|
}); |
||||||
|
busboy.on('finish', function() { |
||||||
|
assert(finishes++ === 0, 'finish emitted multiple times'); |
||||||
|
assert.deepEqual(results.length, |
||||||
|
expected.length, |
||||||
|
'Parsed result count mismatch. Saw ' |
||||||
|
+ results.length |
||||||
|
+ '. Expected: ' + expected.length); |
||||||
|
|
||||||
|
results.forEach(function(result, i) { |
||||||
|
assert.deepEqual(result, |
||||||
|
expected[i], |
||||||
|
'Result mismatch:\nParsed: ' + inspect(result) |
||||||
|
+ '\nExpected: ' + inspect(expected[i])); |
||||||
|
}); |
||||||
|
}).on('error', function(err) { |
||||||
|
assert(false, 'Unexpected error: ' + err.stack); |
||||||
|
}); |
||||||
|
|
||||||
|
reqChunks.forEach(function(buf) { |
||||||
|
busboy.write(buf); |
||||||
|
}); |
||||||
|
busboy.end(); |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
assert(finishes === 1, 'busboy did not finish'); |
||||||
|
}); |
@ -0,0 +1,343 @@ |
|||||||
|
var Busboy = require('..'); |
||||||
|
|
||||||
|
var path = require('path'), |
||||||
|
inspect = require('util').inspect, |
||||||
|
assert = require('assert'); |
||||||
|
|
||||||
|
var EMPTY_FN = function() {}; |
||||||
|
|
||||||
|
var t = 0, |
||||||
|
group = path.basename(__filename, '.js') + '/'; |
||||||
|
var tests = [ |
||||||
|
{ source: [ |
||||||
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="file_name_0"', |
||||||
|
'', |
||||||
|
'super alpha file', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="file_name_1"', |
||||||
|
'', |
||||||
|
'super beta file', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
expected: [ |
||||||
|
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], |
||||||
|
['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'], |
||||||
|
['file', 'upload_file_0', 1023, 0, '1k_a.dat', '7bit', 'application/octet-stream'], |
||||||
|
['file', 'upload_file_1', 1023, 0, '1k_b.dat', '7bit', 'application/octet-stream'] |
||||||
|
], |
||||||
|
what: 'Fields and files' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
'Content-Disposition: form-data; name="cont"', |
||||||
|
'', |
||||||
|
'some random content', |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
'Content-Disposition: form-data; name="pass"', |
||||||
|
'', |
||||||
|
'some random pass', |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
'Content-Disposition: form-data; name="bit"', |
||||||
|
'', |
||||||
|
'2', |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
expected: [ |
||||||
|
['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'], |
||||||
|
['field', 'pass', 'some random pass', false, false, '7bit', 'text/plain'], |
||||||
|
['field', 'bit', '2', false, false, '7bit', 'text/plain'] |
||||||
|
], |
||||||
|
what: 'Fields only' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
'' |
||||||
|
], |
||||||
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
expected: [], |
||||||
|
what: 'No fields and no files' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="file_name_0"', |
||||||
|
'', |
||||||
|
'super alpha file', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
limits: { |
||||||
|
fileSize: 13, |
||||||
|
fieldSize: 5 |
||||||
|
}, |
||||||
|
expected: [ |
||||||
|
['field', 'file_name_0', 'super', false, true, '7bit', 'text/plain'], |
||||||
|
['file', 'upload_file_0', 13, 2, '1k_a.dat', '7bit', 'application/octet-stream'] |
||||||
|
], |
||||||
|
what: 'Fields and files (limits)' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="file_name_0"', |
||||||
|
'', |
||||||
|
'super alpha file', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
limits: { |
||||||
|
files: 0 |
||||||
|
}, |
||||||
|
expected: [ |
||||||
|
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'] |
||||||
|
], |
||||||
|
what: 'Fields and files (limits: 0 files)' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="file_name_0"', |
||||||
|
'', |
||||||
|
'super alpha file', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="file_name_1"', |
||||||
|
'', |
||||||
|
'super beta file', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
expected: [ |
||||||
|
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], |
||||||
|
['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'], |
||||||
|
], |
||||||
|
events: ['field'], |
||||||
|
what: 'Fields and (ignored) files' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_0"; filename="/tmp/1k_a.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\files\\1k_b.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
expected: [ |
||||||
|
['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'], |
||||||
|
['file', 'upload_file_1', 26, 0, '1k_b.dat', '7bit', 'application/octet-stream'], |
||||||
|
['file', 'upload_file_2', 26, 0, '1k_c.dat', '7bit', 'application/octet-stream'] |
||||||
|
], |
||||||
|
what: 'Files with filenames containing paths' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_0"; filename="/absolute/1k_a.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', |
||||||
|
'Content-Type: application/octet-stream', |
||||||
|
'', |
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||||
|
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||||
|
preservePath: true, |
||||||
|
expected: [ |
||||||
|
['file', 'upload_file_0', 26, 0, '/absolute/1k_a.dat', '7bit', 'application/octet-stream'], |
||||||
|
['file', 'upload_file_1', 26, 0, 'C:\\absolute\\1k_b.dat', '7bit', 'application/octet-stream'], |
||||||
|
['file', 'upload_file_2', 26, 0, 'relative/1k_c.dat', '7bit', 'application/octet-stream'] |
||||||
|
], |
||||||
|
what: 'Paths to be preserved through the preservePath option' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
'Content-Disposition: form-data; name="cont"', |
||||||
|
'Content-Type: ', |
||||||
|
'', |
||||||
|
'some random content', |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
'Content-Disposition: ', |
||||||
|
'', |
||||||
|
'some random pass', |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
expected: [ |
||||||
|
['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'] |
||||||
|
], |
||||||
|
what: 'Empty content-type and empty content-disposition' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['--asdasdasdasd\r\n', |
||||||
|
'Content-Type: text/plain\r\n', |
||||||
|
'Content-Disposition: form-data; name="foo"\r\n', |
||||||
|
'\r\n', |
||||||
|
'asd\r\n', |
||||||
|
'--asdasdasdasd--' |
||||||
|
].join(':)') |
||||||
|
], |
||||||
|
boundary: 'asdasdasdasd', |
||||||
|
expected: [], |
||||||
|
shouldError: 'Unexpected end of multipart data', |
||||||
|
what: 'Stopped mid-header' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
'Content-Disposition: form-data; name="cont"', |
||||||
|
'Content-Type: application/json', |
||||||
|
'', |
||||||
|
'{}', |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--', |
||||||
|
].join('\r\n') |
||||||
|
], |
||||||
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
expected: [ |
||||||
|
['field', 'cont', '{}', false, false, '7bit', 'application/json'] |
||||||
|
], |
||||||
|
what: 'content-type for fields' |
||||||
|
}, |
||||||
|
{ source: [ |
||||||
|
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' |
||||||
|
], |
||||||
|
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||||
|
expected: [], |
||||||
|
what: 'empty form' |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (t === tests.length) |
||||||
|
return; |
||||||
|
|
||||||
|
var v = tests[t]; |
||||||
|
|
||||||
|
var busboy = new Busboy({ |
||||||
|
limits: v.limits, |
||||||
|
preservePath: v.preservePath, |
||||||
|
headers: { |
||||||
|
'content-type': 'multipart/form-data; boundary=' + v.boundary |
||||||
|
} |
||||||
|
}), |
||||||
|
finishes = 0, |
||||||
|
results = []; |
||||||
|
|
||||||
|
if (v.events === undefined || v.events.indexOf('field') > -1) { |
||||||
|
busboy.on('field', function(key, val, keyTrunc, valTrunc, encoding, contype) { |
||||||
|
results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]); |
||||||
|
}); |
||||||
|
} |
||||||
|
if (v.events === undefined || v.events.indexOf('file') > -1) { |
||||||
|
busboy.on('file', function(fieldname, stream, filename, encoding, mimeType) { |
||||||
|
var nb = 0, |
||||||
|
info = ['file', |
||||||
|
fieldname, |
||||||
|
nb, |
||||||
|
0, |
||||||
|
filename, |
||||||
|
encoding, |
||||||
|
mimeType]; |
||||||
|
results.push(info); |
||||||
|
stream.on('data', function(d) { |
||||||
|
nb += d.length; |
||||||
|
}).on('limit', function() { |
||||||
|
++info[3]; |
||||||
|
}).on('end', function() { |
||||||
|
info[2] = nb; |
||||||
|
if (stream.truncated) |
||||||
|
++info[3]; |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
busboy.on('finish', function() { |
||||||
|
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||||
|
assert.deepEqual(results.length, |
||||||
|
v.expected.length, |
||||||
|
makeMsg(v.what, 'Parsed result count mismatch. Saw ' |
||||||
|
+ results.length |
||||||
|
+ '. Expected: ' + v.expected.length)); |
||||||
|
|
||||||
|
results.forEach(function(result, i) { |
||||||
|
assert.deepEqual(result, |
||||||
|
v.expected[i], |
||||||
|
makeMsg(v.what, |
||||||
|
'Result mismatch:\nParsed: ' + inspect(result) |
||||||
|
+ '\nExpected: ' + inspect(v.expected[i])) |
||||||
|
); |
||||||
|
}); |
||||||
|
++t; |
||||||
|
next(); |
||||||
|
}).on('error', function(err) { |
||||||
|
if (!v.shouldError || v.shouldError !== err.message) |
||||||
|
assert(false, makeMsg(v.what, 'Unexpected error: ' + err)); |
||||||
|
}); |
||||||
|
|
||||||
|
v.source.forEach(function(s) { |
||||||
|
busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN); |
||||||
|
}); |
||||||
|
busboy.end(); |
||||||
|
} |
||||||
|
next(); |
||||||
|
|
||||||
|
function makeMsg(what, msg) { |
||||||
|
return '[' + group + what + ']: ' + msg; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
assert(t === tests.length, |
||||||
|
makeMsg('_exit', |
||||||
|
'Only finished ' + t + '/' + tests.length + ' tests')); |
||||||
|
}); |
@ -0,0 +1,183 @@ |
|||||||
|
var Busboy = require('..'); |
||||||
|
|
||||||
|
var path = require('path'), |
||||||
|
inspect = require('util').inspect, |
||||||
|
assert = require('assert'); |
||||||
|
|
||||||
|
var EMPTY_FN = function() {}; |
||||||
|
|
||||||
|
var t = 0, |
||||||
|
group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
var tests = [ |
||||||
|
{ source: ['foo'], |
||||||
|
expected: [['foo', '', false, false]], |
||||||
|
what: 'Unassigned value' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar'], |
||||||
|
expected: [['foo', 'bar', false, false]], |
||||||
|
what: 'Assigned value' |
||||||
|
}, |
||||||
|
{ source: ['foo&bar=baz'], |
||||||
|
expected: [['foo', '', false, false], |
||||||
|
['bar', 'baz', false, false]], |
||||||
|
what: 'Unassigned and assigned value' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz'], |
||||||
|
expected: [['foo', 'bar', false, false], |
||||||
|
['baz', '', false, false]], |
||||||
|
what: 'Assigned and unassigned value' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['foo', 'bar', false, false], |
||||||
|
['baz', 'bla', false, false]], |
||||||
|
what: 'Two assigned values' |
||||||
|
}, |
||||||
|
{ source: ['foo&bar'], |
||||||
|
expected: [['foo', '', false, false], |
||||||
|
['bar', '', false, false]], |
||||||
|
what: 'Two unassigned values' |
||||||
|
}, |
||||||
|
{ source: ['foo&bar&'], |
||||||
|
expected: [['foo', '', false, false], |
||||||
|
['bar', '', false, false]], |
||||||
|
what: 'Two unassigned values and ampersand' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar+baz%2Bquux'], |
||||||
|
expected: [['foo', 'bar baz+quux', false, false]], |
||||||
|
what: 'Assigned value with (plus) space' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar%20baz%21'], |
||||||
|
expected: [['foo', 'bar baz!', false, false]], |
||||||
|
what: 'Assigned value with encoded bytes' |
||||||
|
}, |
||||||
|
{ source: ['foo%20bar=baz%20bla%21'], |
||||||
|
expected: [['foo bar', 'baz bla!', false, false]], |
||||||
|
what: 'Assigned value with encoded bytes #2' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar%20baz%21&num=1000'], |
||||||
|
expected: [['foo', 'bar baz!', false, false], |
||||||
|
['num', '1000', false, false]], |
||||||
|
what: 'Two assigned values, one with encoded bytes' |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [], |
||||||
|
what: 'Limits: zero fields', |
||||||
|
limits: { fields: 0 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['foo', 'bar', false, false]], |
||||||
|
what: 'Limits: one field', |
||||||
|
limits: { fields: 1 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['foo', 'bar', false, false], |
||||||
|
['baz', 'bla', false, false]], |
||||||
|
what: 'Limits: field part lengths match limits', |
||||||
|
limits: { fieldNameSize: 3, fieldSize: 3 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['fo', 'bar', true, false], |
||||||
|
['ba', 'bla', true, false]], |
||||||
|
what: 'Limits: truncated field name', |
||||||
|
limits: { fieldNameSize: 2 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['foo', 'ba', false, true], |
||||||
|
['baz', 'bl', false, true]], |
||||||
|
what: 'Limits: truncated field value', |
||||||
|
limits: { fieldSize: 2 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['fo', 'ba', true, true], |
||||||
|
['ba', 'bl', true, true]], |
||||||
|
what: 'Limits: truncated field name and value', |
||||||
|
limits: { fieldNameSize: 2, fieldSize: 2 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['fo', '', true, true], |
||||||
|
['ba', '', true, true]], |
||||||
|
what: 'Limits: truncated field name and zero value limit', |
||||||
|
limits: { fieldNameSize: 2, fieldSize: 0 } |
||||||
|
}, |
||||||
|
{ source: ['foo=bar&baz=bla'], |
||||||
|
expected: [['', '', true, true], |
||||||
|
['', '', true, true]], |
||||||
|
what: 'Limits: truncated zero field name and zero value limit', |
||||||
|
limits: { fieldNameSize: 0, fieldSize: 0 } |
||||||
|
}, |
||||||
|
{ source: ['&'], |
||||||
|
expected: [], |
||||||
|
what: 'Ampersand' |
||||||
|
}, |
||||||
|
{ source: ['&&&&&'], |
||||||
|
expected: [], |
||||||
|
what: 'Many ampersands' |
||||||
|
}, |
||||||
|
{ source: ['='], |
||||||
|
expected: [['', '', false, false]], |
||||||
|
what: 'Assigned value, empty name and value' |
||||||
|
}, |
||||||
|
{ source: [''], |
||||||
|
expected: [], |
||||||
|
what: 'Nothing' |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (t === tests.length) |
||||||
|
return; |
||||||
|
|
||||||
|
var v = tests[t]; |
||||||
|
|
||||||
|
var busboy = new Busboy({ |
||||||
|
limits: v.limits, |
||||||
|
headers: { |
||||||
|
'content-type': 'application/x-www-form-urlencoded; charset=utf-8' |
||||||
|
} |
||||||
|
}), |
||||||
|
finishes = 0, |
||||||
|
results = []; |
||||||
|
|
||||||
|
busboy.on('field', function(key, val, keyTrunc, valTrunc) { |
||||||
|
results.push([key, val, keyTrunc, valTrunc]); |
||||||
|
}); |
||||||
|
busboy.on('file', function() { |
||||||
|
throw new Error(makeMsg(v.what, 'Unexpected file')); |
||||||
|
}); |
||||||
|
busboy.on('finish', function() { |
||||||
|
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||||
|
assert.deepEqual(results.length, |
||||||
|
v.expected.length, |
||||||
|
makeMsg(v.what, 'Parsed result count mismatch. Saw ' |
||||||
|
+ results.length |
||||||
|
+ '. Expected: ' + v.expected.length)); |
||||||
|
|
||||||
|
var i = 0; |
||||||
|
results.forEach(function(result) { |
||||||
|
assert.deepEqual(result, |
||||||
|
v.expected[i], |
||||||
|
makeMsg(v.what, |
||||||
|
'Result mismatch:\nParsed: ' + inspect(result) |
||||||
|
+ '\nExpected: ' + inspect(v.expected[i])) |
||||||
|
); |
||||||
|
++i; |
||||||
|
}); |
||||||
|
++t; |
||||||
|
next(); |
||||||
|
}); |
||||||
|
|
||||||
|
v.source.forEach(function(s) { |
||||||
|
busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN); |
||||||
|
}); |
||||||
|
busboy.end(); |
||||||
|
} |
||||||
|
next(); |
||||||
|
|
||||||
|
function makeMsg(what, msg) { |
||||||
|
return '[' + group + what + ']: ' + msg; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
assert(t === tests.length, makeMsg('_exit', 'Only finished ' + t + '/' + tests.length + ' tests')); |
||||||
|
}); |
@ -0,0 +1,66 @@ |
|||||||
|
var Decoder = require('../lib/utils').Decoder; |
||||||
|
|
||||||
|
var path = require('path'), |
||||||
|
assert = require('assert'); |
||||||
|
|
||||||
|
var group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
[ |
||||||
|
{ source: ['Hello world'], |
||||||
|
expected: 'Hello world', |
||||||
|
what: 'No encoded bytes' |
||||||
|
}, |
||||||
|
{ source: ['Hello%20world'], |
||||||
|
expected: 'Hello world', |
||||||
|
what: 'One full encoded byte' |
||||||
|
}, |
||||||
|
{ source: ['Hello%20world%21'], |
||||||
|
expected: 'Hello world!', |
||||||
|
what: 'Two full encoded bytes' |
||||||
|
}, |
||||||
|
{ source: ['Hello%', '20world'], |
||||||
|
expected: 'Hello world', |
||||||
|
what: 'One full encoded byte split #1' |
||||||
|
}, |
||||||
|
{ source: ['Hello%2', '0world'], |
||||||
|
expected: 'Hello world', |
||||||
|
what: 'One full encoded byte split #2' |
||||||
|
}, |
||||||
|
{ source: ['Hello%20', 'world'], |
||||||
|
expected: 'Hello world', |
||||||
|
what: 'One full encoded byte (concat)' |
||||||
|
}, |
||||||
|
{ source: ['Hello%2Qworld'], |
||||||
|
expected: 'Hello%2Qworld', |
||||||
|
what: 'Malformed encoded byte #1' |
||||||
|
}, |
||||||
|
{ source: ['Hello%world'], |
||||||
|
expected: 'Hello%world', |
||||||
|
what: 'Malformed encoded byte #2' |
||||||
|
}, |
||||||
|
{ source: ['Hello+world'], |
||||||
|
expected: 'Hello world', |
||||||
|
what: 'Plus to space' |
||||||
|
}, |
||||||
|
{ source: ['Hello+world%21'], |
||||||
|
expected: 'Hello world!', |
||||||
|
what: 'Plus and encoded byte' |
||||||
|
}, |
||||||
|
{ source: ['5%2B5%3D10'], |
||||||
|
expected: '5+5=10', |
||||||
|
what: 'Encoded plus' |
||||||
|
}, |
||||||
|
{ source: ['5+%2B+5+%3D+10'], |
||||||
|
expected: '5 + 5 = 10', |
||||||
|
what: 'Spaces and encoded plus' |
||||||
|
}, |
||||||
|
].forEach(function(v) { |
||||||
|
var dec = new Decoder(), result = ''; |
||||||
|
v.source.forEach(function(s) { |
||||||
|
result += dec.write(s); |
||||||
|
}); |
||||||
|
var msg = '[' + group + v.what + ']: decoded string mismatch.\n' |
||||||
|
+ 'Saw: ' + result + '\n' |
||||||
|
+ 'Expected: ' + v.expected; |
||||||
|
assert.deepEqual(result, v.expected, msg); |
||||||
|
}); |
@ -0,0 +1,96 @@ |
|||||||
|
var parseParams = require('../lib/utils').parseParams; |
||||||
|
|
||||||
|
var path = require('path'), |
||||||
|
assert = require('assert'), |
||||||
|
inspect = require('util').inspect; |
||||||
|
|
||||||
|
var group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
[ |
||||||
|
{ source: 'video/ogg', |
||||||
|
expected: ['video/ogg'], |
||||||
|
what: 'No parameters' |
||||||
|
}, |
||||||
|
{ source: 'video/ogg;', |
||||||
|
expected: ['video/ogg'], |
||||||
|
what: 'No parameters (with separator)' |
||||||
|
}, |
||||||
|
{ source: 'video/ogg; ', |
||||||
|
expected: ['video/ogg'], |
||||||
|
what: 'No parameters (with separator followed by whitespace)' |
||||||
|
}, |
||||||
|
{ source: ';video/ogg', |
||||||
|
expected: ['', 'video/ogg'], |
||||||
|
what: 'Empty parameter' |
||||||
|
}, |
||||||
|
{ source: 'video/*', |
||||||
|
expected: ['video/*'], |
||||||
|
what: 'Subtype with asterisk' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; encoding=utf8', |
||||||
|
expected: ['text/plain', ['encoding', 'utf8']], |
||||||
|
what: 'Unquoted' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; encoding=', |
||||||
|
expected: ['text/plain', ['encoding', '']], |
||||||
|
what: 'Unquoted empty string' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; encoding="utf8"', |
||||||
|
expected: ['text/plain', ['encoding', 'utf8']], |
||||||
|
what: 'Quoted' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; greeting="hello \\"world\\""', |
||||||
|
expected: ['text/plain', ['greeting', 'hello "world"']], |
||||||
|
what: 'Quotes within quoted' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; encoding=""', |
||||||
|
expected: ['text/plain', ['encoding', '']], |
||||||
|
what: 'Quoted empty string' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; encoding="utf8";\t foo=bar;test', |
||||||
|
expected: ['text/plain', ['encoding', 'utf8'], ['foo', 'bar'], 'test'], |
||||||
|
what: 'Multiple params with various spacing' |
||||||
|
}, |
||||||
|
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates", |
||||||
|
expected: ['text/plain', ['filename', '£ rates']], |
||||||
|
what: 'Extended parameter (RFC 5987) with language' |
||||||
|
}, |
||||||
|
{ source: "text/plain; filename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates", |
||||||
|
expected: ['text/plain', ['filename', '£ and € rates']], |
||||||
|
what: 'Extended parameter (RFC 5987) without language' |
||||||
|
}, |
||||||
|
{ source: "text/plain; filename*=utf-8''%E6%B5%8B%E8%AF%95%E6%96%87%E6%A1%A3", |
||||||
|
expected: ['text/plain', ['filename', '测试文档']], |
||||||
|
what: 'Extended parameter (RFC 5987) without language #2' |
||||||
|
}, |
||||||
|
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates", |
||||||
|
expected: ['text/plain', ['filename', '£ rates'], ['altfilename', '£ and € rates']], |
||||||
|
what: 'Multiple extended parameters (RFC 5987) with mixed charsets' |
||||||
|
}, |
||||||
|
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename=\"foobarbaz\"", |
||||||
|
expected: ['text/plain', ['filename', '£ rates'], ['altfilename', 'foobarbaz']], |
||||||
|
what: 'Mixed regular and extended parameters (RFC 5987)' |
||||||
|
}, |
||||||
|
{ source: "text/plain; filename=\"foobarbaz\"; altfilename*=iso-8859-1'en'%A3%20rates", |
||||||
|
expected: ['text/plain', ['filename', 'foobarbaz'], ['altfilename', '£ rates']], |
||||||
|
what: 'Mixed regular and extended parameters (RFC 5987) #2' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; filename="C:\\folder\\test.png"', |
||||||
|
expected: ['text/plain', ['filename', 'C:\\folder\\test.png']], |
||||||
|
what: 'Unescaped backslashes should be considered backslashes' |
||||||
|
}, |
||||||
|
{ source: 'text/plain; filename="John \\"Magic\\" Smith.png"', |
||||||
|
expected: ['text/plain', ['filename', 'John "Magic" Smith.png']], |
||||||
|
what: 'Escaped double-quotes should be considered double-quotes' |
||||||
|
}, |
||||||
|
{ source: 'multipart/form-data; charset=utf-8; boundary=0xKhTmLbOuNdArY', |
||||||
|
expected: ['multipart/form-data', ['charset', 'utf-8'], ['boundary', '0xKhTmLbOuNdArY']], |
||||||
|
what: 'Multiple non-quoted parameters' |
||||||
|
}, |
||||||
|
].forEach(function(v) { |
||||||
|
var result = parseParams(v.source), |
||||||
|
msg = '[' + group + v.what + ']: parsed parameters mismatch.\n' |
||||||
|
+ 'Saw: ' + inspect(result) + '\n' |
||||||
|
+ 'Expected: ' + inspect(v.expected); |
||||||
|
assert.deepEqual(result, v.expected, msg); |
||||||
|
}); |
@ -0,0 +1,4 @@ |
|||||||
|
require('fs').readdirSync(__dirname).forEach(function(f) { |
||||||
|
if (f.substr(0, 5) === 'test-') |
||||||
|
require('./' + f); |
||||||
|
}); |
@ -0,0 +1,16 @@ |
|||||||
|
sudo: false |
||||||
|
language: cpp |
||||||
|
notifications: |
||||||
|
email: false |
||||||
|
env: |
||||||
|
matrix: |
||||||
|
- TRAVIS_NODE_VERSION="4" |
||||||
|
- TRAVIS_NODE_VERSION="6" |
||||||
|
- TRAVIS_NODE_VERSION="8" |
||||||
|
- TRAVIS_NODE_VERSION="10" |
||||||
|
install: |
||||||
|
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION |
||||||
|
- node --version |
||||||
|
- npm --version |
||||||
|
- npm install |
||||||
|
script: npm test |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright Brian White. All rights reserved. |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to |
||||||
|
deal in the Software without restriction, including without limitation the |
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
||||||
|
sell copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
||||||
|
IN THE SOFTWARE. |
@ -0,0 +1,122 @@ |
|||||||
|
|
||||||
|
Description |
||||||
|
=========== |
||||||
|
|
||||||
|
A very fast streaming multipart parser for node.js. |
||||||
|
|
||||||
|
Benchmarks can be found [here](https://github.com/mscdex/dicer/wiki/Benchmarks). |
||||||
|
|
||||||
|
|
||||||
|
Requirements |
||||||
|
============ |
||||||
|
|
||||||
|
* [node.js](http://nodejs.org/) -- v4.5.0 or newer |
||||||
|
|
||||||
|
|
||||||
|
Install |
||||||
|
============ |
||||||
|
|
||||||
|
npm install dicer |
||||||
|
|
||||||
|
|
||||||
|
Examples |
||||||
|
======== |
||||||
|
|
||||||
|
* Parse an HTTP form upload |
||||||
|
|
||||||
|
```javascript |
||||||
|
var inspect = require('util').inspect, |
||||||
|
http = require('http'); |
||||||
|
|
||||||
|
var Dicer = require('dicer'); |
||||||
|
|
||||||
|
// quick and dirty way to parse multipart boundary |
||||||
|
var RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i, |
||||||
|
HTML = Buffer.from('<html><head></head><body>\ |
||||||
|
<form method="POST" enctype="multipart/form-data">\ |
||||||
|
<input type="text" name="textfield"><br />\ |
||||||
|
<input type="file" name="filefield"><br />\ |
||||||
|
<input type="submit">\ |
||||||
|
</form>\ |
||||||
|
</body></html>'), |
||||||
|
PORT = 8080; |
||||||
|
|
||||||
|
http.createServer(function(req, res) { |
||||||
|
var m; |
||||||
|
if (req.method === 'POST' |
||||||
|
&& req.headers['content-type'] |
||||||
|
&& (m = RE_BOUNDARY.exec(req.headers['content-type']))) { |
||||||
|
var d = new Dicer({ boundary: m[1] || m[2] }); |
||||||
|
|
||||||
|
d.on('part', function(p) { |
||||||
|
console.log('New part!'); |
||||||
|
p.on('header', function(header) { |
||||||
|
for (var h in header) { |
||||||
|
console.log('Part header: k: ' + inspect(h) |
||||||
|
+ ', v: ' + inspect(header[h])); |
||||||
|
} |
||||||
|
}); |
||||||
|
p.on('data', function(data) { |
||||||
|
console.log('Part data: ' + inspect(data.toString())); |
||||||
|
}); |
||||||
|
p.on('end', function() { |
||||||
|
console.log('End of part\n'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
d.on('finish', function() { |
||||||
|
console.log('End of parts'); |
||||||
|
res.writeHead(200); |
||||||
|
res.end('Form submission successful!'); |
||||||
|
}); |
||||||
|
req.pipe(d); |
||||||
|
} else if (req.method === 'GET' && req.url === '/') { |
||||||
|
res.writeHead(200); |
||||||
|
res.end(HTML); |
||||||
|
} else { |
||||||
|
res.writeHead(404); |
||||||
|
res.end(); |
||||||
|
} |
||||||
|
}).listen(PORT, function() { |
||||||
|
console.log('Listening for requests on port ' + PORT); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
API |
||||||
|
=== |
||||||
|
|
||||||
|
_Dicer_ is a _WritableStream_ |
||||||
|
|
||||||
|
Dicer (special) events |
||||||
|
---------------------- |
||||||
|
|
||||||
|
* **finish**() - Emitted when all parts have been parsed and the Dicer instance has been ended. |
||||||
|
|
||||||
|
* **part**(< _PartStream_ >stream) - Emitted when a new part has been found. |
||||||
|
|
||||||
|
* **preamble**(< _PartStream_ >stream) - Emitted for preamble if you should happen to need it (can usually be ignored). |
||||||
|
|
||||||
|
* **trailer**(< _Buffer_ >data) - Emitted when trailing data was found after the terminating boundary (as with the preamble, this can usually be ignored too). |
||||||
|
|
||||||
|
|
||||||
|
Dicer methods |
||||||
|
------------- |
||||||
|
|
||||||
|
* **(constructor)**(< _object_ >config) - Creates and returns a new Dicer instance with the following valid `config` settings: |
||||||
|
|
||||||
|
* **boundary** - _string_ - This is the boundary used to detect the beginning of a new part. |
||||||
|
|
||||||
|
* **headerFirst** - _boolean_ - If true, preamble header parsing will be performed first. |
||||||
|
|
||||||
|
* **maxHeaderPairs** - _integer_ - The maximum number of header key=>value pairs to parse **Default:** 2000 (same as node's http). |
||||||
|
|
||||||
|
* **setBoundary**(< _string_ >boundary) - _(void)_ - Sets the boundary to use for parsing and performs some initialization needed for parsing. You should only need to use this if you set `headerFirst` to true in the constructor and are parsing the boundary from the preamble header. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_PartStream_ is a _ReadableStream_ |
||||||
|
|
||||||
|
PartStream (special) events |
||||||
|
--------------------------- |
||||||
|
|
||||||
|
* **header**(< _object_ >header) - An object containing the header for this particular part. Each property value is an _array_ of one or more string values. |
@ -0,0 +1,63 @@ |
|||||||
|
var assert = require('assert'); |
||||||
|
var Dicer = require('..'), |
||||||
|
boundary = '-----------------------------168072824752491622650073', |
||||||
|
d = new Dicer({ boundary: boundary }), |
||||||
|
mb = 100, |
||||||
|
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||||
|
callbacks = |
||||||
|
{ partBegin: -1, |
||||||
|
partEnd: -1, |
||||||
|
headerField: -1, |
||||||
|
headerValue: -1, |
||||||
|
partData: -1, |
||||||
|
end: -1, |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
d.on('part', function(p) { |
||||||
|
callbacks.partBegin++; |
||||||
|
p.on('header', function(header) { |
||||||
|
/*for (var h in header) |
||||||
|
console.log('Part header: k: ' + inspect(h) + ', v: ' + inspect(header[h]));*/ |
||||||
|
}); |
||||||
|
p.on('data', function(data) { |
||||||
|
callbacks.partData++; |
||||||
|
//console.log('Part data: ' + inspect(data.toString()));
|
||||||
|
}); |
||||||
|
p.on('end', function() { |
||||||
|
//console.log('End of part\n');
|
||||||
|
callbacks.partEnd++; |
||||||
|
}); |
||||||
|
}); |
||||||
|
d.on('end', function() { |
||||||
|
//console.log('End of parts');
|
||||||
|
callbacks.end++; |
||||||
|
}); |
||||||
|
|
||||||
|
var start = +new Date(), |
||||||
|
nparsed = d.write(buffer), |
||||||
|
duration = +new Date - start, |
||||||
|
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||||
|
|
||||||
|
console.log(mbPerSec+' mb/sec'); |
||||||
|
|
||||||
|
//assert.equal(nparsed, buffer.length);
|
||||||
|
|
||||||
|
function createMultipartBuffer(boundary, size) { |
||||||
|
var head = |
||||||
|
'--'+boundary+'\r\n' |
||||||
|
+ 'content-disposition: form-data; name="field1"\r\n' |
||||||
|
+ '\r\n' |
||||||
|
, tail = '\r\n--'+boundary+'--\r\n' |
||||||
|
, buffer = Buffer.allocUnsafe(size); |
||||||
|
|
||||||
|
buffer.write(head, 'ascii', 0); |
||||||
|
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
/*for (var k in callbacks) { |
||||||
|
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||||
|
}*/ |
||||||
|
}); |
@ -0,0 +1,70 @@ |
|||||||
|
var assert = require('assert'); |
||||||
|
require('../node_modules/formidable/test/common'); |
||||||
|
var multipartParser = require('../node_modules/formidable/lib/multipart_parser'), |
||||||
|
MultipartParser = multipartParser.MultipartParser, |
||||||
|
parser = new MultipartParser(), |
||||||
|
boundary = '-----------------------------168072824752491622650073', |
||||||
|
mb = 100, |
||||||
|
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||||
|
callbacks = |
||||||
|
{ partBegin: -1, |
||||||
|
partEnd: -1, |
||||||
|
headerField: -1, |
||||||
|
headerValue: -1, |
||||||
|
partData: -1, |
||||||
|
end: -1, |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
parser.initWithBoundary(boundary); |
||||||
|
parser.onHeaderField = function() { |
||||||
|
callbacks.headerField++; |
||||||
|
}; |
||||||
|
|
||||||
|
parser.onHeaderValue = function() { |
||||||
|
callbacks.headerValue++; |
||||||
|
}; |
||||||
|
|
||||||
|
parser.onPartBegin = function() { |
||||||
|
callbacks.partBegin++; |
||||||
|
}; |
||||||
|
|
||||||
|
parser.onPartData = function() { |
||||||
|
callbacks.partData++; |
||||||
|
}; |
||||||
|
|
||||||
|
parser.onPartEnd = function() { |
||||||
|
callbacks.partEnd++; |
||||||
|
}; |
||||||
|
|
||||||
|
parser.onEnd = function() { |
||||||
|
callbacks.end++; |
||||||
|
}; |
||||||
|
|
||||||
|
var start = +new Date(), |
||||||
|
nparsed = parser.write(buffer), |
||||||
|
duration = +new Date - start, |
||||||
|
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||||
|
|
||||||
|
console.log(mbPerSec+' mb/sec'); |
||||||
|
|
||||||
|
//assert.equal(nparsed, buffer.length);
|
||||||
|
|
||||||
|
function createMultipartBuffer(boundary, size) { |
||||||
|
var head = |
||||||
|
'--'+boundary+'\r\n' |
||||||
|
+ 'content-disposition: form-data; name="field1"\r\n' |
||||||
|
+ '\r\n' |
||||||
|
, tail = '\r\n--'+boundary+'--\r\n' |
||||||
|
, buffer = Buffer.allocUnsafe(size); |
||||||
|
|
||||||
|
buffer.write(head, 'ascii', 0); |
||||||
|
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
/*for (var k in callbacks) { |
||||||
|
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||||
|
}*/ |
||||||
|
}); |
@ -0,0 +1,56 @@ |
|||||||
|
var assert = require('assert'); |
||||||
|
var multipartser = require('multipartser'), |
||||||
|
boundary = '-----------------------------168072824752491622650073', |
||||||
|
parser = multipartser(), |
||||||
|
mb = 100, |
||||||
|
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||||
|
callbacks = |
||||||
|
{ partBegin: -1, |
||||||
|
partEnd: -1, |
||||||
|
headerField: -1, |
||||||
|
headerValue: -1, |
||||||
|
partData: -1, |
||||||
|
end: -1, |
||||||
|
}; |
||||||
|
|
||||||
|
parser.boundary( boundary ); |
||||||
|
|
||||||
|
parser.on( 'part', function ( part ) { |
||||||
|
}); |
||||||
|
|
||||||
|
parser.on( 'end', function () { |
||||||
|
//console.log( 'completed parsing' );
|
||||||
|
}); |
||||||
|
|
||||||
|
parser.on( 'error', function ( error ) { |
||||||
|
console.error( error ); |
||||||
|
}); |
||||||
|
|
||||||
|
var start = +new Date(), |
||||||
|
nparsed = parser.data(buffer), |
||||||
|
nend = parser.end(), |
||||||
|
duration = +new Date - start, |
||||||
|
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||||
|
|
||||||
|
console.log(mbPerSec+' mb/sec'); |
||||||
|
|
||||||
|
//assert.equal(nparsed, buffer.length);
|
||||||
|
|
||||||
|
function createMultipartBuffer(boundary, size) { |
||||||
|
var head = |
||||||
|
'--'+boundary+'\r\n' |
||||||
|
+ 'content-disposition: form-data; name="field1"\r\n' |
||||||
|
+ '\r\n' |
||||||
|
, tail = '\r\n--'+boundary+'--\r\n' |
||||||
|
, buffer = Buffer.allocUnsafe(size); |
||||||
|
|
||||||
|
buffer.write(head, 'ascii', 0); |
||||||
|
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
/*for (var k in callbacks) { |
||||||
|
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||||
|
}*/ |
||||||
|
}); |
@ -0,0 +1,76 @@ |
|||||||
|
var assert = require('assert'), |
||||||
|
Form = require('multiparty').Form, |
||||||
|
boundary = '-----------------------------168072824752491622650073', |
||||||
|
mb = 100, |
||||||
|
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||||
|
callbacks = |
||||||
|
{ partBegin: -1, |
||||||
|
partEnd: -1, |
||||||
|
headerField: -1, |
||||||
|
headerValue: -1, |
||||||
|
partData: -1, |
||||||
|
end: -1, |
||||||
|
}; |
||||||
|
|
||||||
|
var form = new Form({ boundary: boundary }); |
||||||
|
|
||||||
|
hijack('onParseHeaderField', function() { |
||||||
|
callbacks.headerField++; |
||||||
|
}); |
||||||
|
|
||||||
|
hijack('onParseHeaderValue', function() { |
||||||
|
callbacks.headerValue++; |
||||||
|
}); |
||||||
|
|
||||||
|
hijack('onParsePartBegin', function() { |
||||||
|
callbacks.partBegin++; |
||||||
|
}); |
||||||
|
|
||||||
|
hijack('onParsePartData', function() { |
||||||
|
callbacks.partData++; |
||||||
|
}); |
||||||
|
|
||||||
|
hijack('onParsePartEnd', function() { |
||||||
|
callbacks.partEnd++; |
||||||
|
}); |
||||||
|
|
||||||
|
form.on('finish', function() { |
||||||
|
callbacks.end++; |
||||||
|
}); |
||||||
|
|
||||||
|
var start = new Date(); |
||||||
|
form.write(buffer, function(err) { |
||||||
|
var duration = new Date() - start; |
||||||
|
assert.ifError(err); |
||||||
|
var mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||||
|
console.log(mbPerSec+' mb/sec'); |
||||||
|
}); |
||||||
|
|
||||||
|
//assert.equal(nparsed, buffer.length);
|
||||||
|
|
||||||
|
function createMultipartBuffer(boundary, size) { |
||||||
|
var head = |
||||||
|
'--'+boundary+'\r\n' |
||||||
|
+ 'content-disposition: form-data; name="field1"\r\n' |
||||||
|
+ '\r\n' |
||||||
|
, tail = '\r\n--'+boundary+'--\r\n' |
||||||
|
, buffer = Buffer.allocUnsafe(size); |
||||||
|
|
||||||
|
buffer.write(head, 'ascii', 0); |
||||||
|
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
/*for (var k in callbacks) { |
||||||
|
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||||
|
}*/ |
||||||
|
}); |
||||||
|
|
||||||
|
function hijack(name, fn) { |
||||||
|
var oldFn = form[name]; |
||||||
|
form[name] = function() { |
||||||
|
fn(); |
||||||
|
return oldFn.apply(this, arguments); |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
// A special, edited version of the multipart parser from parted is needed here
|
||||||
|
// because otherwise it attempts to do some things above and beyond just parsing
|
||||||
|
// -- like saving to disk and whatnot
|
||||||
|
|
||||||
|
var assert = require('assert'); |
||||||
|
var Parser = require('./parted-multipart'), |
||||||
|
boundary = '-----------------------------168072824752491622650073', |
||||||
|
parser = new Parser('boundary=' + boundary), |
||||||
|
mb = 100, |
||||||
|
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||||
|
callbacks = |
||||||
|
{ partBegin: -1, |
||||||
|
partEnd: -1, |
||||||
|
headerField: -1, |
||||||
|
headerValue: -1, |
||||||
|
partData: -1, |
||||||
|
end: -1, |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
parser.on('header', function() { |
||||||
|
//callbacks.headerField++;
|
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('data', function() { |
||||||
|
//callbacks.partBegin++;
|
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('part', function() { |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('end', function() { |
||||||
|
//callbacks.end++;
|
||||||
|
}); |
||||||
|
|
||||||
|
var start = +new Date(), |
||||||
|
nparsed = parser.write(buffer), |
||||||
|
duration = +new Date - start, |
||||||
|
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||||
|
|
||||||
|
console.log(mbPerSec+' mb/sec'); |
||||||
|
|
||||||
|
//assert.equal(nparsed, buffer.length);
|
||||||
|
|
||||||
|
function createMultipartBuffer(boundary, size) { |
||||||
|
var head = |
||||||
|
'--'+boundary+'\r\n' |
||||||
|
+ 'content-disposition: form-data; name="field1"\r\n' |
||||||
|
+ '\r\n' |
||||||
|
, tail = '\r\n--'+boundary+'--\r\n' |
||||||
|
, buffer = Buffer.allocUnsafe(size); |
||||||
|
|
||||||
|
buffer.write(head, 'ascii', 0); |
||||||
|
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
/*for (var k in callbacks) { |
||||||
|
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||||
|
}*/ |
||||||
|
}); |
@ -0,0 +1,485 @@ |
|||||||
|
/** |
||||||
|
* Parted (https://github.com/chjj/parted)
|
||||||
|
* A streaming multipart state parser. |
||||||
|
* Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed) |
||||||
|
*/ |
||||||
|
|
||||||
|
var fs = require('fs') |
||||||
|
, path = require('path') |
||||||
|
, EventEmitter = require('events').EventEmitter |
||||||
|
, StringDecoder = require('string_decoder').StringDecoder |
||||||
|
, set = require('qs').set |
||||||
|
, each = Array.prototype.forEach; |
||||||
|
|
||||||
|
/** |
||||||
|
* Character Constants |
||||||
|
*/ |
||||||
|
|
||||||
|
var DASH = '-'.charCodeAt(0) |
||||||
|
, CR = '\r'.charCodeAt(0) |
||||||
|
, LF = '\n'.charCodeAt(0) |
||||||
|
, COLON = ':'.charCodeAt(0) |
||||||
|
, SPACE = ' '.charCodeAt(0); |
||||||
|
|
||||||
|
/** |
||||||
|
* Parser |
||||||
|
*/ |
||||||
|
|
||||||
|
var Parser = function(type, options) { |
||||||
|
if (!(this instanceof Parser)) { |
||||||
|
return new Parser(type, options); |
||||||
|
} |
||||||
|
|
||||||
|
EventEmitter.call(this); |
||||||
|
|
||||||
|
this.writable = true; |
||||||
|
this.readable = true; |
||||||
|
|
||||||
|
this.options = options || {}; |
||||||
|
|
||||||
|
var key = grab(type, 'boundary'); |
||||||
|
if (!key) { |
||||||
|
return this._error('No boundary key found.'); |
||||||
|
} |
||||||
|
|
||||||
|
this.key = Buffer.allocUnsafe('\r\n--' + key); |
||||||
|
|
||||||
|
this._key = {}; |
||||||
|
each.call(this.key, function(ch) { |
||||||
|
this._key[ch] = true; |
||||||
|
}, this); |
||||||
|
|
||||||
|
this.state = 'start'; |
||||||
|
this.pending = 0; |
||||||
|
this.written = 0; |
||||||
|
this.writtenDisk = 0; |
||||||
|
this.buff = Buffer.allocUnsafe(200); |
||||||
|
|
||||||
|
this.preamble = true; |
||||||
|
this.epilogue = false; |
||||||
|
|
||||||
|
this._reset(); |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype.__proto__ = EventEmitter.prototype; |
||||||
|
|
||||||
|
/** |
||||||
|
* Parsing |
||||||
|
*/ |
||||||
|
|
||||||
|
Parser.prototype.write = function(data) { |
||||||
|
if (!this.writable |
||||||
|
|| this.epilogue) return; |
||||||
|
|
||||||
|
try { |
||||||
|
this._parse(data); |
||||||
|
} catch (e) { |
||||||
|
this._error(e); |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype.end = function(data) { |
||||||
|
if (!this.writable) return; |
||||||
|
|
||||||
|
if (data) this.write(data); |
||||||
|
|
||||||
|
if (!this.epilogue) { |
||||||
|
return this._error('Message underflow.'); |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype._parse = function(data) { |
||||||
|
var i = 0 |
||||||
|
, len = data.length |
||||||
|
, buff = this.buff |
||||||
|
, key = this.key |
||||||
|
, ch |
||||||
|
, val |
||||||
|
, j; |
||||||
|
|
||||||
|
for (; i < len; i++) { |
||||||
|
if (this.pos >= 200) { |
||||||
|
return this._error('Potential buffer overflow.'); |
||||||
|
} |
||||||
|
|
||||||
|
ch = data[i]; |
||||||
|
|
||||||
|
switch (this.state) { |
||||||
|
case 'start': |
||||||
|
switch (ch) { |
||||||
|
case DASH: |
||||||
|
this.pos = 3; |
||||||
|
this.state = 'key'; |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'key': |
||||||
|
if (this.pos === key.length) { |
||||||
|
this.state = 'key_end'; |
||||||
|
i--; |
||||||
|
} else if (ch !== key[this.pos]) { |
||||||
|
if (this.preamble) { |
||||||
|
this.state = 'start'; |
||||||
|
i--; |
||||||
|
} else { |
||||||
|
this.state = 'body'; |
||||||
|
val = this.pos - i; |
||||||
|
if (val > 0) { |
||||||
|
this._write(key.slice(0, val)); |
||||||
|
} |
||||||
|
i--; |
||||||
|
} |
||||||
|
} else { |
||||||
|
this.pos++; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'key_end': |
||||||
|
switch (ch) { |
||||||
|
case CR: |
||||||
|
this.state = 'key_line_end'; |
||||||
|
break; |
||||||
|
case DASH: |
||||||
|
this.state = 'key_dash_end'; |
||||||
|
break; |
||||||
|
default: |
||||||
|
return this._error('Expected CR or DASH.'); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'key_line_end': |
||||||
|
switch (ch) { |
||||||
|
case LF: |
||||||
|
if (this.preamble) { |
||||||
|
this.preamble = false; |
||||||
|
} else { |
||||||
|
this._finish(); |
||||||
|
} |
||||||
|
this.state = 'header_name'; |
||||||
|
this.pos = 0; |
||||||
|
break; |
||||||
|
default: |
||||||
|
return this._error('Expected CR.'); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'key_dash_end': |
||||||
|
switch (ch) { |
||||||
|
case DASH: |
||||||
|
this.epilogue = true; |
||||||
|
this._finish(); |
||||||
|
return; |
||||||
|
default: |
||||||
|
return this._error('Expected DASH.'); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'header_name': |
||||||
|
switch (ch) { |
||||||
|
case COLON: |
||||||
|
this.header = buff.toString('ascii', 0, this.pos); |
||||||
|
this.pos = 0; |
||||||
|
this.state = 'header_val'; |
||||||
|
break; |
||||||
|
default: |
||||||
|
buff[this.pos++] = ch | 32; |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'header_val': |
||||||
|
switch (ch) { |
||||||
|
case CR: |
||||||
|
this.state = 'header_val_end'; |
||||||
|
break; |
||||||
|
case SPACE: |
||||||
|
if (this.pos === 0) { |
||||||
|
break; |
||||||
|
} |
||||||
|
; // FALL-THROUGH
|
||||||
|
default: |
||||||
|
buff[this.pos++] = ch; |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'header_val_end': |
||||||
|
switch (ch) { |
||||||
|
case LF: |
||||||
|
val = buff.toString('ascii', 0, this.pos); |
||||||
|
this._header(this.header, val); |
||||||
|
this.pos = 0; |
||||||
|
this.state = 'header_end'; |
||||||
|
break; |
||||||
|
default: |
||||||
|
return this._error('Expected LF.'); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'header_end': |
||||||
|
switch (ch) { |
||||||
|
case CR: |
||||||
|
this.state = 'head_end'; |
||||||
|
break; |
||||||
|
default: |
||||||
|
this.state = 'header_name'; |
||||||
|
i--; |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'head_end': |
||||||
|
switch (ch) { |
||||||
|
case LF: |
||||||
|
this.state = 'body'; |
||||||
|
i++; |
||||||
|
if (i >= len) return; |
||||||
|
data = data.slice(i); |
||||||
|
i = -1; |
||||||
|
len = data.length; |
||||||
|
break; |
||||||
|
default: |
||||||
|
return this._error('Expected LF.'); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'body': |
||||||
|
switch (ch) { |
||||||
|
case CR: |
||||||
|
if (i > 0) { |
||||||
|
this._write(data.slice(0, i)); |
||||||
|
} |
||||||
|
this.pos = 1; |
||||||
|
this.state = 'key'; |
||||||
|
data = data.slice(i); |
||||||
|
i = 0; |
||||||
|
len = data.length; |
||||||
|
break; |
||||||
|
default: |
||||||
|
// boyer-moore-like algorithm
|
||||||
|
// at felixge's suggestion
|
||||||
|
while ((j = i + key.length - 1) < len) { |
||||||
|
if (this._key[data[j]]) break; |
||||||
|
i = j; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (this.state === 'body') { |
||||||
|
this._write(data); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype._header = function(name, val) { |
||||||
|
/*if (name === 'content-disposition') { |
||||||
|
this.field = grab(val, 'name'); |
||||||
|
this.file = grab(val, 'filename'); |
||||||
|
|
||||||
|
if (this.file) { |
||||||
|
this.data = stream(this.file, this.options.path); |
||||||
|
} else { |
||||||
|
this.decode = new StringDecoder('utf8'); |
||||||
|
this.data = ''; |
||||||
|
} |
||||||
|
}*/ |
||||||
|
|
||||||
|
return this.emit('header', name, val); |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype._write = function(data) { |
||||||
|
/*if (this.data == null) { |
||||||
|
return this._error('No disposition.'); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.file) { |
||||||
|
this.data.write(data); |
||||||
|
this.writtenDisk += data.length; |
||||||
|
} else { |
||||||
|
this.data += this.decode.write(data); |
||||||
|
this.written += data.length; |
||||||
|
}*/ |
||||||
|
|
||||||
|
this.emit('data', data); |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype._reset = function() { |
||||||
|
this.pos = 0; |
||||||
|
this.decode = null; |
||||||
|
this.field = null; |
||||||
|
this.data = null; |
||||||
|
this.file = null; |
||||||
|
this.header = null; |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype._error = function(err) { |
||||||
|
this.destroy(); |
||||||
|
this.emit('error', typeof err === 'string' |
||||||
|
? new Error(err) |
||||||
|
: err); |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype.destroy = function(err) { |
||||||
|
this.writable = false; |
||||||
|
this.readable = false; |
||||||
|
this._reset(); |
||||||
|
}; |
||||||
|
|
||||||
|
Parser.prototype._finish = function() { |
||||||
|
var self = this |
||||||
|
, field = this.field |
||||||
|
, data = this.data |
||||||
|
, file = this.file |
||||||
|
, part; |
||||||
|
|
||||||
|
this.pending++; |
||||||
|
|
||||||
|
this._reset(); |
||||||
|
|
||||||
|
if (data && data.path) { |
||||||
|
part = data.path; |
||||||
|
data.end(next); |
||||||
|
} else { |
||||||
|
part = data; |
||||||
|
next(); |
||||||
|
} |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (!self.readable) return; |
||||||
|
|
||||||
|
self.pending--; |
||||||
|
|
||||||
|
self.emit('part', field, part); |
||||||
|
|
||||||
|
if (data && data.path) { |
||||||
|
self.emit('file', field, part, file); |
||||||
|
} |
||||||
|
|
||||||
|
if (self.epilogue && !self.pending) { |
||||||
|
self.emit('end'); |
||||||
|
self.destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Uploads |
||||||
|
*/ |
||||||
|
|
||||||
|
Parser.root = process.platform === 'win32' |
||||||
|
? 'C:/Temp' |
||||||
|
: '/tmp'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Middleware |
||||||
|
*/ |
||||||
|
|
||||||
|
Parser.middleware = function(options) { |
||||||
|
options = options || {}; |
||||||
|
return function(req, res, next) { |
||||||
|
if (options.ensureBody) { |
||||||
|
req.body = {}; |
||||||
|
} |
||||||
|
|
||||||
|
if (req.method === 'GET' |
||||||
|
|| req.method === 'HEAD' |
||||||
|
|| req._multipart) return next(); |
||||||
|
|
||||||
|
req._multipart = true; |
||||||
|
|
||||||
|
var type = req.headers['content-type']; |
||||||
|
|
||||||
|
if (type) type = type.split(';')[0].trim().toLowerCase(); |
||||||
|
|
||||||
|
if (type === 'multipart/form-data') { |
||||||
|
Parser.handle(req, res, next, options); |
||||||
|
} else { |
||||||
|
next(); |
||||||
|
} |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Handler |
||||||
|
*/ |
||||||
|
|
||||||
|
Parser.handle = function(req, res, next, options) { |
||||||
|
var parser = new Parser(req.headers['content-type'], options) |
||||||
|
, diskLimit = options.diskLimit |
||||||
|
, limit = options.limit |
||||||
|
, parts = {} |
||||||
|
, files = {}; |
||||||
|
|
||||||
|
parser.on('error', function(err) { |
||||||
|
req.destroy(); |
||||||
|
next(err); |
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('part', function(field, part) { |
||||||
|
set(parts, field, part); |
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('file', function(field, path, name) { |
||||||
|
set(files, field, { |
||||||
|
path: path, |
||||||
|
name: name, |
||||||
|
toString: function() { |
||||||
|
return path; |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('data', function() { |
||||||
|
if (this.writtenDisk > diskLimit || this.written > limit) { |
||||||
|
this.emit('error', new Error('Overflow.')); |
||||||
|
this.destroy(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
parser.on('end', next); |
||||||
|
|
||||||
|
req.body = parts; |
||||||
|
req.files = files; |
||||||
|
req.pipe(parser); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Helpers |
||||||
|
*/ |
||||||
|
|
||||||
|
var isWindows = process.platform === 'win32'; |
||||||
|
|
||||||
|
var stream = function(name, dir) { |
||||||
|
var ext = path.extname(name) || '' |
||||||
|
, name = path.basename(name, ext) || '' |
||||||
|
, dir = dir || Parser.root |
||||||
|
, tag; |
||||||
|
|
||||||
|
tag = Math.random().toString(36).substring(2); |
||||||
|
|
||||||
|
name = name.substring(0, 200) + '.' + tag; |
||||||
|
name = path.join(dir, name) + ext.substring(0, 6); |
||||||
|
name = name.replace(/\0/g, ''); |
||||||
|
|
||||||
|
if (isWindows) { |
||||||
|
name = name.replace(/[:*<>|"?]/g, ''); |
||||||
|
} |
||||||
|
|
||||||
|
return fs.createWriteStream(name); |
||||||
|
}; |
||||||
|
|
||||||
|
var grab = function(str, name) { |
||||||
|
if (!str) return; |
||||||
|
|
||||||
|
var rx = new RegExp('\\b' + name + '\\s*=\\s*("[^"]+"|\'[^\']+\'|[^;,]+)', 'i') |
||||||
|
, cap = rx.exec(str); |
||||||
|
|
||||||
|
if (cap) { |
||||||
|
return cap[1].trim().replace(/^['"]|['"]$/g, ''); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Expose |
||||||
|
*/ |
||||||
|
|
||||||
|
module.exports = Parser; |
@ -0,0 +1,239 @@ |
|||||||
|
var WritableStream = require('stream').Writable, |
||||||
|
inherits = require('util').inherits; |
||||||
|
|
||||||
|
var StreamSearch = require('streamsearch'); |
||||||
|
|
||||||
|
var PartStream = require('./PartStream'), |
||||||
|
HeaderParser = require('./HeaderParser'); |
||||||
|
|
||||||
|
var DASH = 45, |
||||||
|
B_ONEDASH = Buffer.from('-'), |
||||||
|
B_CRLF = Buffer.from('\r\n'), |
||||||
|
EMPTY_FN = function() {}; |
||||||
|
|
||||||
|
function Dicer(cfg) { |
||||||
|
if (!(this instanceof Dicer)) |
||||||
|
return new Dicer(cfg); |
||||||
|
WritableStream.call(this, cfg); |
||||||
|
|
||||||
|
if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) |
||||||
|
throw new TypeError('Boundary required'); |
||||||
|
|
||||||
|
if (typeof cfg.boundary === 'string') |
||||||
|
this.setBoundary(cfg.boundary); |
||||||
|
else |
||||||
|
this._bparser = undefined; |
||||||
|
|
||||||
|
this._headerFirst = cfg.headerFirst; |
||||||
|
|
||||||
|
var self = this; |
||||||
|
|
||||||
|
this._dashes = 0; |
||||||
|
this._parts = 0; |
||||||
|
this._finished = false; |
||||||
|
this._realFinish = false; |
||||||
|
this._isPreamble = true; |
||||||
|
this._justMatched = false; |
||||||
|
this._firstWrite = true; |
||||||
|
this._inHeader = true; |
||||||
|
this._part = undefined; |
||||||
|
this._cb = undefined; |
||||||
|
this._ignoreData = false; |
||||||
|
this._partOpts = (typeof cfg.partHwm === 'number' |
||||||
|
? { highWaterMark: cfg.partHwm } |
||||||
|
: {}); |
||||||
|
this._pause = false; |
||||||
|
|
||||||
|
this._hparser = new HeaderParser(cfg); |
||||||
|
this._hparser.on('header', function(header) { |
||||||
|
self._inHeader = false; |
||||||
|
self._part.emit('header', header); |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
inherits(Dicer, WritableStream); |
||||||
|
|
||||||
|
Dicer.prototype.emit = function(ev) { |
||||||
|
if (ev === 'finish' && !this._realFinish) { |
||||||
|
if (!this._finished) { |
||||||
|
var self = this; |
||||||
|
process.nextTick(function() { |
||||||
|
self.emit('error', new Error('Unexpected end of multipart data')); |
||||||
|
if (self._part && !self._ignoreData) { |
||||||
|
var type = (self._isPreamble ? 'Preamble' : 'Part'); |
||||||
|
self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data')); |
||||||
|
self._part.push(null); |
||||||
|
process.nextTick(function() { |
||||||
|
self._realFinish = true; |
||||||
|
self.emit('finish'); |
||||||
|
self._realFinish = false; |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
self._realFinish = true; |
||||||
|
self.emit('finish'); |
||||||
|
self._realFinish = false; |
||||||
|
}); |
||||||
|
} |
||||||
|
} else |
||||||
|
WritableStream.prototype.emit.apply(this, arguments); |
||||||
|
}; |
||||||
|
|
||||||
|
Dicer.prototype._write = function(data, encoding, cb) { |
||||||
|
// ignore unexpected data (e.g. extra trailer data after finished)
|
||||||
|
if (!this._hparser && !this._bparser) |
||||||
|
return cb(); |
||||||
|
|
||||||
|
if (this._headerFirst && this._isPreamble) { |
||||||
|
if (!this._part) { |
||||||
|
this._part = new PartStream(this._partOpts); |
||||||
|
if (this._events.preamble) |
||||||
|
this.emit('preamble', this._part); |
||||||
|
else |
||||||
|
this._ignore(); |
||||||
|
} |
||||||
|
var r = this._hparser.push(data); |
||||||
|
if (!this._inHeader && r !== undefined && r < data.length) |
||||||
|
data = data.slice(r); |
||||||
|
else |
||||||
|
return cb(); |
||||||
|
} |
||||||
|
|
||||||
|
// allows for "easier" testing
|
||||||
|
if (this._firstWrite) { |
||||||
|
this._bparser.push(B_CRLF); |
||||||
|
this._firstWrite = false; |
||||||
|
} |
||||||
|
|
||||||
|
this._bparser.push(data); |
||||||
|
|
||||||
|
if (this._pause) |
||||||
|
this._cb = cb; |
||||||
|
else |
||||||
|
cb(); |
||||||
|
}; |
||||||
|
|
||||||
|
Dicer.prototype.reset = function() { |
||||||
|
this._part = undefined; |
||||||
|
this._bparser = undefined; |
||||||
|
this._hparser = undefined; |
||||||
|
}; |
||||||
|
|
||||||
|
Dicer.prototype.setBoundary = function(boundary) { |
||||||
|
var self = this; |
||||||
|
this._bparser = new StreamSearch('\r\n--' + boundary); |
||||||
|
this._bparser.on('info', function(isMatch, data, start, end) { |
||||||
|
self._oninfo(isMatch, data, start, end); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
Dicer.prototype._ignore = function() { |
||||||
|
if (this._part && !this._ignoreData) { |
||||||
|
this._ignoreData = true; |
||||||
|
this._part.on('error', EMPTY_FN); |
||||||
|
// we must perform some kind of read on the stream even though we are
|
||||||
|
// ignoring the data, otherwise node's Readable stream will not emit 'end'
|
||||||
|
// after pushing null to the stream
|
||||||
|
this._part.resume(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Dicer.prototype._oninfo = function(isMatch, data, start, end) { |
||||||
|
var buf, self = this, i = 0, r, ev, shouldWriteMore = true; |
||||||
|
|
||||||
|
if (!this._part && this._justMatched && data) { |
||||||
|
while (this._dashes < 2 && (start + i) < end) { |
||||||
|
if (data[start + i] === DASH) { |
||||||
|
++i; |
||||||
|
++this._dashes; |
||||||
|
} else { |
||||||
|
if (this._dashes) |
||||||
|
buf = B_ONEDASH; |
||||||
|
this._dashes = 0; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (this._dashes === 2) { |
||||||
|
if ((start + i) < end && this._events.trailer) |
||||||
|
this.emit('trailer', data.slice(start + i, end)); |
||||||
|
this.reset(); |
||||||
|
this._finished = true; |
||||||
|
// no more parts will be added
|
||||||
|
if (self._parts === 0) { |
||||||
|
self._realFinish = true; |
||||||
|
self.emit('finish'); |
||||||
|
self._realFinish = false; |
||||||
|
} |
||||||
|
} |
||||||
|
if (this._dashes) |
||||||
|
return; |
||||||
|
} |
||||||
|
if (this._justMatched) |
||||||
|
this._justMatched = false; |
||||||
|
if (!this._part) { |
||||||
|
this._part = new PartStream(this._partOpts); |
||||||
|
this._part._read = function(n) { |
||||||
|
self._unpause(); |
||||||
|
}; |
||||||
|
ev = this._isPreamble ? 'preamble' : 'part'; |
||||||
|
if (this._events[ev]) |
||||||
|
this.emit(ev, this._part); |
||||||
|
else |
||||||
|
this._ignore(); |
||||||
|
if (!this._isPreamble) |
||||||
|
this._inHeader = true; |
||||||
|
} |
||||||
|
if (data && start < end && !this._ignoreData) { |
||||||
|
if (this._isPreamble || !this._inHeader) { |
||||||
|
if (buf) |
||||||
|
shouldWriteMore = this._part.push(buf); |
||||||
|
shouldWriteMore = this._part.push(data.slice(start, end)); |
||||||
|
if (!shouldWriteMore) |
||||||
|
this._pause = true; |
||||||
|
} else if (!this._isPreamble && this._inHeader) { |
||||||
|
if (buf) |
||||||
|
this._hparser.push(buf); |
||||||
|
r = this._hparser.push(data.slice(start, end)); |
||||||
|
if (!this._inHeader && r !== undefined && r < end) |
||||||
|
this._oninfo(false, data, start + r, end); |
||||||
|
} |
||||||
|
} |
||||||
|
if (isMatch) { |
||||||
|
this._hparser.reset(); |
||||||
|
if (this._isPreamble) |
||||||
|
this._isPreamble = false; |
||||||
|
else { |
||||||
|
++this._parts; |
||||||
|
this._part.on('end', function() { |
||||||
|
if (--self._parts === 0) { |
||||||
|
if (self._finished) { |
||||||
|
self._realFinish = true; |
||||||
|
self.emit('finish'); |
||||||
|
self._realFinish = false; |
||||||
|
} else { |
||||||
|
self._unpause(); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
this._part.push(null); |
||||||
|
this._part = undefined; |
||||||
|
this._ignoreData = false; |
||||||
|
this._justMatched = true; |
||||||
|
this._dashes = 0; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Dicer.prototype._unpause = function() { |
||||||
|
if (!this._pause) |
||||||
|
return; |
||||||
|
|
||||||
|
this._pause = false; |
||||||
|
if (this._cb) { |
||||||
|
var cb = this._cb; |
||||||
|
this._cb = undefined; |
||||||
|
cb(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = Dicer; |
@ -0,0 +1,110 @@ |
|||||||
|
var EventEmitter = require('events').EventEmitter, |
||||||
|
inherits = require('util').inherits; |
||||||
|
|
||||||
|
var StreamSearch = require('streamsearch'); |
||||||
|
|
||||||
|
var B_DCRLF = Buffer.from('\r\n\r\n'), |
||||||
|
RE_CRLF = /\r\n/g, |
||||||
|
RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/, |
||||||
|
MAX_HEADER_PAIRS = 2000, // from node's http.js
|
||||||
|
MAX_HEADER_SIZE = 80 * 1024; // from node's http_parser
|
||||||
|
|
||||||
|
function HeaderParser(cfg) { |
||||||
|
EventEmitter.call(this); |
||||||
|
|
||||||
|
var self = this; |
||||||
|
this.nread = 0; |
||||||
|
this.maxed = false; |
||||||
|
this.npairs = 0; |
||||||
|
this.maxHeaderPairs = (cfg && typeof cfg.maxHeaderPairs === 'number' |
||||||
|
? cfg.maxHeaderPairs |
||||||
|
: MAX_HEADER_PAIRS); |
||||||
|
this.buffer = ''; |
||||||
|
this.header = {}; |
||||||
|
this.finished = false; |
||||||
|
this.ss = new StreamSearch(B_DCRLF); |
||||||
|
this.ss.on('info', function(isMatch, data, start, end) { |
||||||
|
if (data && !self.maxed) { |
||||||
|
if (self.nread + (end - start) > MAX_HEADER_SIZE) { |
||||||
|
end = (MAX_HEADER_SIZE - self.nread); |
||||||
|
self.nread = MAX_HEADER_SIZE; |
||||||
|
} else |
||||||
|
self.nread += (end - start); |
||||||
|
|
||||||
|
if (self.nread === MAX_HEADER_SIZE) |
||||||
|
self.maxed = true; |
||||||
|
|
||||||
|
self.buffer += data.toString('binary', start, end); |
||||||
|
} |
||||||
|
if (isMatch) |
||||||
|
self._finish(); |
||||||
|
}); |
||||||
|
} |
||||||
|
inherits(HeaderParser, EventEmitter); |
||||||
|
|
||||||
|
HeaderParser.prototype.push = function(data) { |
||||||
|
var r = this.ss.push(data); |
||||||
|
if (this.finished) |
||||||
|
return r; |
||||||
|
}; |
||||||
|
|
||||||
|
HeaderParser.prototype.reset = function() { |
||||||
|
this.finished = false; |
||||||
|
this.buffer = ''; |
||||||
|
this.header = {}; |
||||||
|
this.ss.reset(); |
||||||
|
}; |
||||||
|
|
||||||
|
HeaderParser.prototype._finish = function() { |
||||||
|
if (this.buffer) |
||||||
|
this._parseHeader(); |
||||||
|
this.ss.matches = this.ss.maxMatches; |
||||||
|
var header = this.header; |
||||||
|
this.header = {}; |
||||||
|
this.buffer = ''; |
||||||
|
this.finished = true; |
||||||
|
this.nread = this.npairs = 0; |
||||||
|
this.maxed = false; |
||||||
|
this.emit('header', header); |
||||||
|
}; |
||||||
|
|
||||||
|
HeaderParser.prototype._parseHeader = function() { |
||||||
|
if (this.npairs === this.maxHeaderPairs) |
||||||
|
return; |
||||||
|
|
||||||
|
var lines = this.buffer.split(RE_CRLF), len = lines.length, m, h, |
||||||
|
modded = false; |
||||||
|
|
||||||
|
for (var i = 0; i < len; ++i) { |
||||||
|
if (lines[i].length === 0) |
||||||
|
continue; |
||||||
|
if (lines[i][0] === '\t' || lines[i][0] === ' ') { |
||||||
|
// folded header content
|
||||||
|
// RFC2822 says to just remove the CRLF and not the whitespace following
|
||||||
|
// it, so we follow the RFC and include the leading whitespace ...
|
||||||
|
this.header[h][this.header[h].length - 1] += lines[i]; |
||||||
|
} else { |
||||||
|
m = RE_HDR.exec(lines[i]); |
||||||
|
if (m) { |
||||||
|
h = m[1].toLowerCase(); |
||||||
|
if (m[2]) { |
||||||
|
if (this.header[h] === undefined) |
||||||
|
this.header[h] = [m[2]]; |
||||||
|
else |
||||||
|
this.header[h].push(m[2]); |
||||||
|
} else |
||||||
|
this.header[h] = ['']; |
||||||
|
if (++this.npairs === this.maxHeaderPairs) |
||||||
|
break; |
||||||
|
} else { |
||||||
|
this.buffer = lines[i]; |
||||||
|
modded = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (!modded) |
||||||
|
this.buffer = ''; |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = HeaderParser; |
@ -0,0 +1,11 @@ |
|||||||
|
var inherits = require('util').inherits, |
||||||
|
ReadableStream = require('stream').Readable; |
||||||
|
|
||||||
|
function PartStream(opts) { |
||||||
|
ReadableStream.call(this, opts); |
||||||
|
} |
||||||
|
inherits(PartStream, ReadableStream); |
||||||
|
|
||||||
|
PartStream.prototype._read = function(n) {}; |
||||||
|
|
||||||
|
module.exports = PartStream; |
@ -0,0 +1,66 @@ |
|||||||
|
{ |
||||||
|
"_from": "dicer@0.3.0", |
||||||
|
"_id": "dicer@0.3.0", |
||||||
|
"_inBundle": false, |
||||||
|
"_integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", |
||||||
|
"_location": "/dicer", |
||||||
|
"_phantomChildren": {}, |
||||||
|
"_requested": { |
||||||
|
"type": "version", |
||||||
|
"registry": true, |
||||||
|
"raw": "dicer@0.3.0", |
||||||
|
"name": "dicer", |
||||||
|
"escapedName": "dicer", |
||||||
|
"rawSpec": "0.3.0", |
||||||
|
"saveSpec": null, |
||||||
|
"fetchSpec": "0.3.0" |
||||||
|
}, |
||||||
|
"_requiredBy": [ |
||||||
|
"/busboy" |
||||||
|
], |
||||||
|
"_resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", |
||||||
|
"_shasum": "eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872", |
||||||
|
"_spec": "dicer@0.3.0", |
||||||
|
"_where": "/home/sigonasr2/divar/server/node_modules/busboy", |
||||||
|
"author": { |
||||||
|
"name": "Brian White", |
||||||
|
"email": "mscdex@mscdex.net" |
||||||
|
}, |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/mscdex/dicer/issues" |
||||||
|
}, |
||||||
|
"bundleDependencies": false, |
||||||
|
"dependencies": { |
||||||
|
"streamsearch": "0.1.2" |
||||||
|
}, |
||||||
|
"deprecated": false, |
||||||
|
"description": "A very fast streaming multipart parser for node.js", |
||||||
|
"engines": { |
||||||
|
"node": ">=4.5.0" |
||||||
|
}, |
||||||
|
"homepage": "https://github.com/mscdex/dicer#readme", |
||||||
|
"keywords": [ |
||||||
|
"parser", |
||||||
|
"parse", |
||||||
|
"parsing", |
||||||
|
"multipart", |
||||||
|
"form-data", |
||||||
|
"streaming" |
||||||
|
], |
||||||
|
"licenses": [ |
||||||
|
{ |
||||||
|
"type": "MIT", |
||||||
|
"url": "http://github.com/mscdex/dicer/raw/master/LICENSE" |
||||||
|
} |
||||||
|
], |
||||||
|
"main": "./lib/Dicer", |
||||||
|
"name": "dicer", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git+ssh://git@github.com/mscdex/dicer.git" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"test": "node test/test.js" |
||||||
|
}, |
||||||
|
"version": "0.3.0" |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="_method" |
||||||
|
|
||||||
|
put |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[blog]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[public_email]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[interests]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[bio]" |
||||||
|
|
||||||
|
hello |
||||||
|
|
||||||
|
"quote" |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="commit" |
||||||
|
|
||||||
|
Save |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="media"; filename="" |
||||||
|
Content-Type: application/octet-stream |
||||||
|
|
||||||
|
|
@ -0,0 +1 @@ |
|||||||
|
put |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"_method\""]} |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[blog]\""]} |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[public_email]\""]} |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[interests]\""]} |
@ -0,0 +1,3 @@ |
|||||||
|
hello |
||||||
|
|
||||||
|
"quote" |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[bio]\""]} |
@ -0,0 +1 @@ |
|||||||
|
Save |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"commit\""]} |
@ -0,0 +1,2 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""], |
||||||
|
"content-type": ["application/octet-stream"]} |
@ -0,0 +1,32 @@ |
|||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="_method" |
||||||
|
|
||||||
|
put |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[blog]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[public_email]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[interests]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[bio]" |
||||||
|
|
||||||
|
hello |
||||||
|
|
||||||
|
"quote" |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="media"; filename="" |
||||||
|
Content-Type: application/octet-stream |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="commit" |
||||||
|
|
||||||
|
Save |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- |
@ -0,0 +1,33 @@ |
|||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="_method" |
||||||
|
|
||||||
|
put |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[blog]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[public_email]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[interests]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[bio]" |
||||||
|
|
||||||
|
hello |
||||||
|
|
||||||
|
"quote" |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="media"; filename="" |
||||||
|
Content-Type: application/octet-stream |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="commit" |
||||||
|
|
||||||
|
Save |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- |
@ -0,0 +1 @@ |
|||||||
|
Preamble terminated early due to unexpected end of multipart data |
@ -0,0 +1,32 @@ |
|||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="_method" |
||||||
|
|
||||||
|
put |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[blog]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[public_email]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[interests]" |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="profile[bio]" |
||||||
|
|
||||||
|
hello |
||||||
|
|
||||||
|
"quote" |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="media"; filename="" |
||||||
|
Content-Type: application/octet-stream |
||||||
|
|
||||||
|
|
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||||
|
Content-Disposition: form-data; name="commit" |
||||||
|
|
||||||
|
Save |
||||||
|
------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- |
@ -0,0 +1 @@ |
|||||||
|
put |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"_method\""]} |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[blog]\""]} |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[public_email]\""]} |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[interests]\""]} |
@ -0,0 +1,3 @@ |
|||||||
|
hello |
||||||
|
|
||||||
|
"quote" |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"profile[bio]\""]} |
@ -0,0 +1,2 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""], |
||||||
|
"content-type": ["application/octet-stream"]} |
@ -0,0 +1 @@ |
|||||||
|
Save |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"commit\""]} |
@ -0,0 +1,24 @@ |
|||||||
|
User-Agent: foo bar baz |
||||||
|
Content-Type: multipart/form-data; boundary=AaB03x |
||||||
|
|
||||||
|
--AaB03x |
||||||
|
Content-Disposition: form-data; name="foo" |
||||||
|
|
||||||
|
bar |
||||||
|
--AaB03x |
||||||
|
Content-Disposition: form-data; name="files" |
||||||
|
Content-Type: multipart/mixed, boundary=BbC04y |
||||||
|
|
||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="file.txt" |
||||||
|
Content-Type: text/plain |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="flowers.jpg" |
||||||
|
Content-Type: image/jpeg |
||||||
|
Content-Transfer-Encoding: binary |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y-- |
||||||
|
--AaB03x-- |
@ -0,0 +1 @@ |
|||||||
|
bar |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"foo\""]} |
@ -0,0 +1,12 @@ |
|||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="file.txt" |
||||||
|
Content-Type: text/plain |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="flowers.jpg" |
||||||
|
Content-Type: image/jpeg |
||||||
|
Content-Transfer-Encoding: binary |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y-- |
@ -0,0 +1,2 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"files\""], |
||||||
|
"content-type": ["multipart/mixed, boundary=BbC04y"]} |
@ -0,0 +1,2 @@ |
|||||||
|
{"user-agent": ["foo bar baz"], |
||||||
|
"content-type": ["multipart/form-data; boundary=AaB03x"]} |
@ -0,0 +1,21 @@ |
|||||||
|
--AaB03x |
||||||
|
Content-Disposition: form-data; name="foo" |
||||||
|
|
||||||
|
bar |
||||||
|
--AaB03x |
||||||
|
Content-Disposition: form-data; name="files" |
||||||
|
Content-Type: multipart/mixed, boundary=BbC04y |
||||||
|
|
||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="file.txt" |
||||||
|
Content-Type: text/plain |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="flowers.jpg" |
||||||
|
Content-Type: image/jpeg |
||||||
|
Content-Transfer-Encoding: binary |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y-- |
||||||
|
--AaB03x-- |
@ -0,0 +1 @@ |
|||||||
|
bar |
@ -0,0 +1 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"foo\""]} |
@ -0,0 +1,12 @@ |
|||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="file.txt" |
||||||
|
Content-Type: text/plain |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y |
||||||
|
Content-Disposition: attachment; filename="flowers.jpg" |
||||||
|
Content-Type: image/jpeg |
||||||
|
Content-Transfer-Encoding: binary |
||||||
|
|
||||||
|
contents |
||||||
|
--BbC04y-- |
@ -0,0 +1,2 @@ |
|||||||
|
{"content-disposition": ["form-data; name=\"files\""], |
||||||
|
"content-type": ["multipart/mixed, boundary=BbC04y"]} |
@ -0,0 +1,87 @@ |
|||||||
|
var Dicer = require('..'); |
||||||
|
var assert = require('assert'); |
||||||
|
|
||||||
|
var CRLF = '\r\n'; |
||||||
|
var boundary = 'boundary'; |
||||||
|
|
||||||
|
var writeSep = '--' + boundary; |
||||||
|
|
||||||
|
var writePart = [ |
||||||
|
writeSep, |
||||||
|
'Content-Type: text/plain', |
||||||
|
'Content-Length: 0' |
||||||
|
].join(CRLF) |
||||||
|
+ CRLF + CRLF |
||||||
|
+ 'some data' + CRLF; |
||||||
|
|
||||||
|
var writeEnd = '--' + CRLF; |
||||||
|
|
||||||
|
var firedEnd = false; |
||||||
|
var firedFinish = false; |
||||||
|
|
||||||
|
var dicer = new Dicer({boundary: boundary}); |
||||||
|
dicer.on('part', partListener); |
||||||
|
dicer.on('finish', finishListener); |
||||||
|
dicer.write(writePart+writeSep); |
||||||
|
|
||||||
|
function partListener(partReadStream) { |
||||||
|
partReadStream.on('data', function(){}); |
||||||
|
partReadStream.on('end', partEndListener); |
||||||
|
} |
||||||
|
function partEndListener() { |
||||||
|
firedEnd = true; |
||||||
|
setImmediate(afterEnd); |
||||||
|
} |
||||||
|
function afterEnd() { |
||||||
|
dicer.end(writeEnd); |
||||||
|
setImmediate(afterWrite); |
||||||
|
} |
||||||
|
function finishListener() { |
||||||
|
assert(firedEnd, 'Failed to end before finishing'); |
||||||
|
firedFinish = true; |
||||||
|
test2(); |
||||||
|
} |
||||||
|
function afterWrite() { |
||||||
|
assert(firedFinish, 'Failed to finish'); |
||||||
|
} |
||||||
|
|
||||||
|
var isPausePush = true; |
||||||
|
|
||||||
|
var firedPauseCallback = false; |
||||||
|
var firedPauseFinish = false; |
||||||
|
|
||||||
|
var dicer2 = null; |
||||||
|
|
||||||
|
function test2() { |
||||||
|
dicer2 = new Dicer({boundary: boundary}); |
||||||
|
dicer2.on('part', pausePartListener); |
||||||
|
dicer2.on('finish', pauseFinish); |
||||||
|
dicer2.write(writePart+writeSep, 'utf8', pausePartCallback); |
||||||
|
setImmediate(pauseAfterWrite); |
||||||
|
} |
||||||
|
function pausePartListener(partReadStream) { |
||||||
|
partReadStream.on('data', function(){}); |
||||||
|
partReadStream.on('end', function(){}); |
||||||
|
var realPush = partReadStream.push; |
||||||
|
partReadStream.push = function fakePush() { |
||||||
|
realPush.apply(partReadStream, arguments); |
||||||
|
if (!isPausePush) |
||||||
|
return true; |
||||||
|
isPausePush = false; |
||||||
|
return false; |
||||||
|
}; |
||||||
|
} |
||||||
|
function pauseAfterWrite() { |
||||||
|
dicer2.end(writeEnd); |
||||||
|
setImmediate(pauseAfterEnd); |
||||||
|
} |
||||||
|
function pauseAfterEnd() { |
||||||
|
assert(firedPauseCallback, 'Failed to call callback after pause'); |
||||||
|
assert(firedPauseFinish, 'Failed to finish after pause'); |
||||||
|
} |
||||||
|
function pauseFinish() { |
||||||
|
firedPauseFinish = true; |
||||||
|
} |
||||||
|
function pausePartCallback() { |
||||||
|
firedPauseCallback = true; |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
var assert = require('assert'), |
||||||
|
path = require('path'); |
||||||
|
|
||||||
|
var HeaderParser = require('../lib/HeaderParser'); |
||||||
|
|
||||||
|
var DCRLF = '\r\n\r\n', |
||||||
|
MAXED_BUFFER = Buffer.allocUnsafe(128 * 1024); |
||||||
|
MAXED_BUFFER.fill(0x41); // 'A'
|
||||||
|
|
||||||
|
var group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
[ |
||||||
|
{ source: DCRLF, |
||||||
|
expected: {}, |
||||||
|
what: 'No header' |
||||||
|
}, |
||||||
|
{ source: ['Content-Type:\t text/plain', |
||||||
|
'Content-Length:0' |
||||||
|
].join('\r\n') + DCRLF, |
||||||
|
expected: {'content-type': [' text/plain'], 'content-length': ['0']}, |
||||||
|
what: 'Value spacing' |
||||||
|
}, |
||||||
|
{ source: ['Content-Type:\r\n text/plain', |
||||||
|
'Foo:\r\n bar\r\n baz', |
||||||
|
].join('\r\n') + DCRLF, |
||||||
|
expected: {'content-type': [' text/plain'], 'foo': [' bar baz']}, |
||||||
|
what: 'Folded values' |
||||||
|
}, |
||||||
|
{ source: ['Content-Type:', |
||||||
|
'Foo: ', |
||||||
|
].join('\r\n') + DCRLF, |
||||||
|
expected: {'content-type': [''], 'foo': ['']}, |
||||||
|
what: 'Empty values' |
||||||
|
}, |
||||||
|
{ source: MAXED_BUFFER.toString('ascii') + DCRLF, |
||||||
|
expected: {}, |
||||||
|
what: 'Max header size (single chunk)' |
||||||
|
}, |
||||||
|
{ source: ['ABCDEFGHIJ', MAXED_BUFFER.toString('ascii'), DCRLF], |
||||||
|
expected: {}, |
||||||
|
what: 'Max header size (multiple chunks #1)' |
||||||
|
}, |
||||||
|
{ source: [MAXED_BUFFER.toString('ascii'), MAXED_BUFFER.toString('ascii'), DCRLF], |
||||||
|
expected: {}, |
||||||
|
what: 'Max header size (multiple chunk #2)' |
||||||
|
}, |
||||||
|
].forEach(function(v) { |
||||||
|
var parser = new HeaderParser(), |
||||||
|
fired = false; |
||||||
|
|
||||||
|
parser.on('header', function(header) { |
||||||
|
assert(!fired, makeMsg(v.what, 'Header event fired more than once')); |
||||||
|
fired = true; |
||||||
|
assert.deepEqual(header, |
||||||
|
v.expected, |
||||||
|
makeMsg(v.what, 'Parsed result mismatch')); |
||||||
|
}); |
||||||
|
if (!Array.isArray(v.source)) |
||||||
|
v.source = [v.source]; |
||||||
|
v.source.forEach(function(s) { |
||||||
|
parser.push(s); |
||||||
|
}); |
||||||
|
assert(fired, makeMsg(v.what, 'Did not receive header from parser')); |
||||||
|
}); |
||||||
|
|
||||||
|
function makeMsg(what, msg) { |
||||||
|
return '[' + group + what + ']: ' + msg; |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
var Dicer = require('..'); |
||||||
|
var assert = require('assert'), |
||||||
|
fs = require('fs'), |
||||||
|
path = require('path'), |
||||||
|
inspect = require('util').inspect; |
||||||
|
|
||||||
|
var FIXTURES_ROOT = __dirname + '/fixtures/'; |
||||||
|
|
||||||
|
var t = 0, |
||||||
|
group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
var tests = [ |
||||||
|
{ source: 'many', |
||||||
|
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||||
|
chsize: 16, |
||||||
|
nparts: 7, |
||||||
|
what: 'Extra trailer data pushed after finished' |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (t === tests.length) |
||||||
|
return; |
||||||
|
var v = tests[t], |
||||||
|
fixtureBase = FIXTURES_ROOT + v.source, |
||||||
|
fd, |
||||||
|
n = 0, |
||||||
|
buffer = Buffer.allocUnsafe(v.chsize), |
||||||
|
state = { parts: [] }; |
||||||
|
|
||||||
|
fd = fs.openSync(fixtureBase + '/original', 'r'); |
||||||
|
|
||||||
|
var dicer = new Dicer(v.opts), |
||||||
|
error, |
||||||
|
partErrors = 0, |
||||||
|
finishes = 0; |
||||||
|
|
||||||
|
dicer.on('part', function(p) { |
||||||
|
var part = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
|
||||||
|
p.on('header', function(h) { |
||||||
|
part.header = h; |
||||||
|
}).on('data', function(data) { |
||||||
|
// make a copy because we are using readSync which re-uses a buffer ...
|
||||||
|
var copy = Buffer.allocUnsafe(data.length); |
||||||
|
data.copy(copy); |
||||||
|
data = copy; |
||||||
|
if (!part.body) |
||||||
|
part.body = [ data ]; |
||||||
|
else |
||||||
|
part.body.push(data); |
||||||
|
part.bodylen += data.length; |
||||||
|
}).on('error', function(err) { |
||||||
|
part.error = err; |
||||||
|
++partErrors; |
||||||
|
}).on('end', function() { |
||||||
|
if (part.body) |
||||||
|
part.body = Buffer.concat(part.body, part.bodylen); |
||||||
|
state.parts.push(part); |
||||||
|
}); |
||||||
|
}).on('error', function(err) { |
||||||
|
error = err; |
||||||
|
}).on('finish', function() { |
||||||
|
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||||
|
|
||||||
|
if (v.dicerError) |
||||||
|
assert(error !== undefined, makeMsg(v.what, 'Expected error')); |
||||||
|
else |
||||||
|
assert(error === undefined, makeMsg(v.what, 'Unexpected error')); |
||||||
|
|
||||||
|
if (v.events && v.events.indexOf('part') > -1) { |
||||||
|
assert.equal(state.parts.length, |
||||||
|
v.nparts, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part count mismatch:\nActual: ' |
||||||
|
+ state.parts.length |
||||||
|
+ '\nExpected: ' |
||||||
|
+ v.nparts)); |
||||||
|
|
||||||
|
if (!v.npartErrors) |
||||||
|
v.npartErrors = 0; |
||||||
|
assert.equal(partErrors, |
||||||
|
v.npartErrors, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part errors mismatch:\nActual: ' |
||||||
|
+ partErrors |
||||||
|
+ '\nExpected: ' |
||||||
|
+ v.npartErrors)); |
||||||
|
|
||||||
|
for (var i = 0, header, body; i < v.nparts; ++i) { |
||||||
|
if (fs.existsSync(fixtureBase + '/part' + (i+1))) { |
||||||
|
body = fs.readFileSync(fixtureBase + '/part' + (i+1)); |
||||||
|
if (body.length === 0) |
||||||
|
body = undefined; |
||||||
|
} else |
||||||
|
body = undefined; |
||||||
|
assert.deepEqual(state.parts[i].body, |
||||||
|
body, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part #' + (i+1) + ' body mismatch')); |
||||||
|
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) { |
||||||
|
header = fs.readFileSync(fixtureBase |
||||||
|
+ '/part' + (i+1) + '.header', 'binary'); |
||||||
|
header = JSON.parse(header); |
||||||
|
} else |
||||||
|
header = undefined; |
||||||
|
assert.deepEqual(state.parts[i].header, |
||||||
|
header, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part #' + (i+1) |
||||||
|
+ ' parsed header mismatch:\nActual: ' |
||||||
|
+ inspect(state.parts[i].header) |
||||||
|
+ '\nExpected: ' |
||||||
|
+ inspect(header))); |
||||||
|
} |
||||||
|
} |
||||||
|
++t; |
||||||
|
next(); |
||||||
|
}); |
||||||
|
|
||||||
|
while (true) { |
||||||
|
n = fs.readSync(fd, buffer, 0, buffer.length, null); |
||||||
|
if (n === 0) { |
||||||
|
setTimeout(function() { |
||||||
|
dicer.write('\r\n\r\n\r\n'); |
||||||
|
dicer.end(); |
||||||
|
}, 50); |
||||||
|
break; |
||||||
|
} |
||||||
|
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)); |
||||||
|
} |
||||||
|
fs.closeSync(fd); |
||||||
|
} |
||||||
|
next(); |
||||||
|
|
||||||
|
function makeMsg(what, msg) { |
||||||
|
return '[' + group + what + ']: ' + msg; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
assert(t === tests.length, |
||||||
|
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests')); |
||||||
|
}); |
@ -0,0 +1,228 @@ |
|||||||
|
var Dicer = require('..'); |
||||||
|
var assert = require('assert'), |
||||||
|
fs = require('fs'), |
||||||
|
path = require('path'), |
||||||
|
inspect = require('util').inspect; |
||||||
|
|
||||||
|
var FIXTURES_ROOT = __dirname + '/fixtures/'; |
||||||
|
|
||||||
|
var t = 0, |
||||||
|
group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
var tests = [ |
||||||
|
{ source: 'many', |
||||||
|
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||||
|
chsize: 16, |
||||||
|
nparts: 0, |
||||||
|
what: 'No preamble or part listeners' |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (t === tests.length) |
||||||
|
return; |
||||||
|
var v = tests[t], |
||||||
|
fixtureBase = FIXTURES_ROOT + v.source, |
||||||
|
fd, |
||||||
|
n = 0, |
||||||
|
buffer = Buffer.allocUnsafe(v.chsize), |
||||||
|
state = { done: false, parts: [], preamble: undefined }; |
||||||
|
|
||||||
|
fd = fs.openSync(fixtureBase + '/original', 'r'); |
||||||
|
|
||||||
|
var dicer = new Dicer(v.opts), |
||||||
|
error, |
||||||
|
partErrors = 0, |
||||||
|
finishes = 0; |
||||||
|
|
||||||
|
if (v.events && v.events.indexOf('preamble') > -1) { |
||||||
|
dicer.on('preamble', function(p) { |
||||||
|
var preamble = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
|
||||||
|
p.on('header', function(h) { |
||||||
|
preamble.header = h; |
||||||
|
}).on('data', function(data) { |
||||||
|
// make a copy because we are using readSync which re-uses a buffer ...
|
||||||
|
var copy = Buffer.allocUnsafe(data.length); |
||||||
|
data.copy(copy); |
||||||
|
data = copy; |
||||||
|
if (!preamble.body) |
||||||
|
preamble.body = [ data ]; |
||||||
|
else |
||||||
|
preamble.body.push(data); |
||||||
|
preamble.bodylen += data.length; |
||||||
|
}).on('error', function(err) { |
||||||
|
preamble.error = err; |
||||||
|
}).on('end', function() { |
||||||
|
if (preamble.body) |
||||||
|
preamble.body = Buffer.concat(preamble.body, preamble.bodylen); |
||||||
|
if (preamble.body || preamble.header) |
||||||
|
state.preamble = preamble; |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
if (v.events && v.events.indexOf('part') > -1) { |
||||||
|
dicer.on('part', function(p) { |
||||||
|
var part = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
|
||||||
|
p.on('header', function(h) { |
||||||
|
part.header = h; |
||||||
|
}).on('data', function(data) { |
||||||
|
// make a copy because we are using readSync which re-uses a buffer ...
|
||||||
|
var copy = Buffer.allocUnsafe(data.length); |
||||||
|
data.copy(copy); |
||||||
|
data = copy; |
||||||
|
if (!part.body) |
||||||
|
part.body = [ data ]; |
||||||
|
else |
||||||
|
part.body.push(data); |
||||||
|
part.bodylen += data.length; |
||||||
|
}).on('error', function(err) { |
||||||
|
part.error = err; |
||||||
|
++partErrors; |
||||||
|
}).on('end', function() { |
||||||
|
if (part.body) |
||||||
|
part.body = Buffer.concat(part.body, part.bodylen); |
||||||
|
state.parts.push(part); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
dicer.on('error', function(err) { |
||||||
|
error = err; |
||||||
|
}).on('finish', function() { |
||||||
|
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||||
|
|
||||||
|
if (v.dicerError) |
||||||
|
assert(error !== undefined, makeMsg(v.what, 'Expected error')); |
||||||
|
else |
||||||
|
assert(error === undefined, makeMsg(v.what, 'Unexpected error')); |
||||||
|
|
||||||
|
if (v.events && v.events.indexOf('preamble') > -1) { |
||||||
|
var preamble; |
||||||
|
if (fs.existsSync(fixtureBase + '/preamble')) { |
||||||
|
var prebody = fs.readFileSync(fixtureBase + '/preamble'); |
||||||
|
if (prebody.length) { |
||||||
|
preamble = { |
||||||
|
body: prebody, |
||||||
|
bodylen: prebody.length, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
if (fs.existsSync(fixtureBase + '/preamble.header')) { |
||||||
|
var prehead = JSON.parse(fs.readFileSync(fixtureBase |
||||||
|
+ '/preamble.header', 'binary')); |
||||||
|
if (!preamble) { |
||||||
|
preamble = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: prehead |
||||||
|
}; |
||||||
|
} else |
||||||
|
preamble.header = prehead; |
||||||
|
} |
||||||
|
if (fs.existsSync(fixtureBase + '/preamble.error')) { |
||||||
|
var err = new Error(fs.readFileSync(fixtureBase |
||||||
|
+ '/preamble.error', 'binary')); |
||||||
|
if (!preamble) { |
||||||
|
preamble = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: err, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
} else |
||||||
|
preamble.error = err; |
||||||
|
} |
||||||
|
|
||||||
|
assert.deepEqual(state.preamble, |
||||||
|
preamble, |
||||||
|
makeMsg(v.what, |
||||||
|
'Preamble mismatch:\nActual:' |
||||||
|
+ inspect(state.preamble) |
||||||
|
+ '\nExpected: ' |
||||||
|
+ inspect(preamble))); |
||||||
|
} |
||||||
|
|
||||||
|
if (v.events && v.events.indexOf('part') > -1) { |
||||||
|
assert.equal(state.parts.length, |
||||||
|
v.nparts, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part count mismatch:\nActual: ' |
||||||
|
+ state.parts.length |
||||||
|
+ '\nExpected: ' |
||||||
|
+ v.nparts)); |
||||||
|
|
||||||
|
if (!v.npartErrors) |
||||||
|
v.npartErrors = 0; |
||||||
|
assert.equal(partErrors, |
||||||
|
v.npartErrors, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part errors mismatch:\nActual: ' |
||||||
|
+ partErrors |
||||||
|
+ '\nExpected: ' |
||||||
|
+ v.npartErrors)); |
||||||
|
|
||||||
|
for (var i = 0, header, body; i < v.nparts; ++i) { |
||||||
|
if (fs.existsSync(fixtureBase + '/part' + (i+1))) { |
||||||
|
body = fs.readFileSync(fixtureBase + '/part' + (i+1)); |
||||||
|
if (body.length === 0) |
||||||
|
body = undefined; |
||||||
|
} else |
||||||
|
body = undefined; |
||||||
|
assert.deepEqual(state.parts[i].body, |
||||||
|
body, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part #' + (i+1) + ' body mismatch')); |
||||||
|
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) { |
||||||
|
header = fs.readFileSync(fixtureBase |
||||||
|
+ '/part' + (i+1) + '.header', 'binary'); |
||||||
|
header = JSON.parse(header); |
||||||
|
} else |
||||||
|
header = undefined; |
||||||
|
assert.deepEqual(state.parts[i].header, |
||||||
|
header, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part #' + (i+1) |
||||||
|
+ ' parsed header mismatch:\nActual: ' |
||||||
|
+ inspect(state.parts[i].header) |
||||||
|
+ '\nExpected: ' |
||||||
|
+ inspect(header))); |
||||||
|
} |
||||||
|
} |
||||||
|
++t; |
||||||
|
next(); |
||||||
|
}); |
||||||
|
|
||||||
|
while (true) { |
||||||
|
n = fs.readSync(fd, buffer, 0, buffer.length, null); |
||||||
|
if (n === 0) { |
||||||
|
dicer.end(); |
||||||
|
break; |
||||||
|
} |
||||||
|
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)); |
||||||
|
} |
||||||
|
fs.closeSync(fd); |
||||||
|
} |
||||||
|
next(); |
||||||
|
|
||||||
|
function makeMsg(what, msg) { |
||||||
|
return '[' + group + what + ']: ' + msg; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
assert(t === tests.length, |
||||||
|
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests')); |
||||||
|
}); |
@ -0,0 +1,240 @@ |
|||||||
|
var Dicer = require('..'); |
||||||
|
var assert = require('assert'), |
||||||
|
fs = require('fs'), |
||||||
|
path = require('path'), |
||||||
|
inspect = require('util').inspect; |
||||||
|
|
||||||
|
var FIXTURES_ROOT = __dirname + '/fixtures/'; |
||||||
|
|
||||||
|
var t = 0, |
||||||
|
group = path.basename(__filename, '.js') + '/'; |
||||||
|
|
||||||
|
var tests = [ |
||||||
|
{ source: 'nested', |
||||||
|
opts: { boundary: 'AaB03x' }, |
||||||
|
chsize: 32, |
||||||
|
nparts: 2, |
||||||
|
what: 'One nested multipart' |
||||||
|
}, |
||||||
|
{ source: 'many', |
||||||
|
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||||
|
chsize: 16, |
||||||
|
nparts: 7, |
||||||
|
what: 'Many parts' |
||||||
|
}, |
||||||
|
{ source: 'many-wrongboundary', |
||||||
|
opts: { boundary: 'LOLOLOL' }, |
||||||
|
chsize: 8, |
||||||
|
nparts: 0, |
||||||
|
dicerError: true, |
||||||
|
what: 'Many parts, wrong boundary' |
||||||
|
}, |
||||||
|
{ source: 'many-noend', |
||||||
|
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||||
|
chsize: 16, |
||||||
|
nparts: 7, |
||||||
|
npartErrors: 1, |
||||||
|
dicerError: true, |
||||||
|
what: 'Many parts, end boundary missing, 1 file open' |
||||||
|
}, |
||||||
|
{ source: 'nested-full', |
||||||
|
opts: { boundary: 'AaB03x', headerFirst: true }, |
||||||
|
chsize: 32, |
||||||
|
nparts: 2, |
||||||
|
what: 'One nested multipart with preceding header' |
||||||
|
}, |
||||||
|
{ source: 'nested-full', |
||||||
|
opts: { headerFirst: true }, |
||||||
|
chsize: 32, |
||||||
|
nparts: 2, |
||||||
|
setBoundary: 'AaB03x', |
||||||
|
what: 'One nested multipart with preceding header, using setBoundary' |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (t === tests.length) |
||||||
|
return; |
||||||
|
var v = tests[t], |
||||||
|
fixtureBase = FIXTURES_ROOT + v.source, |
||||||
|
n = 0, |
||||||
|
buffer = Buffer.allocUnsafe(v.chsize), |
||||||
|
state = { parts: [], preamble: undefined }; |
||||||
|
|
||||||
|
var dicer = new Dicer(v.opts), |
||||||
|
error, |
||||||
|
partErrors = 0, |
||||||
|
finishes = 0; |
||||||
|
|
||||||
|
dicer.on('preamble', function(p) { |
||||||
|
var preamble = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
|
||||||
|
p.on('header', function(h) { |
||||||
|
preamble.header = h; |
||||||
|
if (v.setBoundary) |
||||||
|
dicer.setBoundary(v.setBoundary); |
||||||
|
}).on('data', function(data) { |
||||||
|
// make a copy because we are using readSync which re-uses a buffer ...
|
||||||
|
var copy = Buffer.allocUnsafe(data.length); |
||||||
|
data.copy(copy); |
||||||
|
data = copy; |
||||||
|
if (!preamble.body) |
||||||
|
preamble.body = [ data ]; |
||||||
|
else |
||||||
|
preamble.body.push(data); |
||||||
|
preamble.bodylen += data.length; |
||||||
|
}).on('error', function(err) { |
||||||
|
preamble.error = err; |
||||||
|
}).on('end', function() { |
||||||
|
if (preamble.body) |
||||||
|
preamble.body = Buffer.concat(preamble.body, preamble.bodylen); |
||||||
|
if (preamble.body || preamble.header) |
||||||
|
state.preamble = preamble; |
||||||
|
}); |
||||||
|
}); |
||||||
|
dicer.on('part', function(p) { |
||||||
|
var part = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
|
||||||
|
p.on('header', function(h) { |
||||||
|
part.header = h; |
||||||
|
}).on('data', function(data) { |
||||||
|
if (!part.body) |
||||||
|
part.body = [ data ]; |
||||||
|
else |
||||||
|
part.body.push(data); |
||||||
|
part.bodylen += data.length; |
||||||
|
}).on('error', function(err) { |
||||||
|
part.error = err; |
||||||
|
++partErrors; |
||||||
|
}).on('end', function() { |
||||||
|
if (part.body) |
||||||
|
part.body = Buffer.concat(part.body, part.bodylen); |
||||||
|
state.parts.push(part); |
||||||
|
}); |
||||||
|
}).on('error', function(err) { |
||||||
|
error = err; |
||||||
|
}).on('finish', function() { |
||||||
|
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||||
|
|
||||||
|
if (v.dicerError) |
||||||
|
assert(error !== undefined, makeMsg(v.what, 'Expected error')); |
||||||
|
else |
||||||
|
assert(error === undefined, makeMsg(v.what, 'Unexpected error: ' + error)); |
||||||
|
|
||||||
|
var preamble; |
||||||
|
if (fs.existsSync(fixtureBase + '/preamble')) { |
||||||
|
var prebody = fs.readFileSync(fixtureBase + '/preamble'); |
||||||
|
if (prebody.length) { |
||||||
|
preamble = { |
||||||
|
body: prebody, |
||||||
|
bodylen: prebody.length, |
||||||
|
error: undefined, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
if (fs.existsSync(fixtureBase + '/preamble.header')) { |
||||||
|
var prehead = JSON.parse(fs.readFileSync(fixtureBase |
||||||
|
+ '/preamble.header', 'binary')); |
||||||
|
if (!preamble) { |
||||||
|
preamble = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: undefined, |
||||||
|
header: prehead |
||||||
|
}; |
||||||
|
} else |
||||||
|
preamble.header = prehead; |
||||||
|
} |
||||||
|
if (fs.existsSync(fixtureBase + '/preamble.error')) { |
||||||
|
var err = new Error(fs.readFileSync(fixtureBase |
||||||
|
+ '/preamble.error', 'binary')); |
||||||
|
if (!preamble) { |
||||||
|
preamble = { |
||||||
|
body: undefined, |
||||||
|
bodylen: 0, |
||||||
|
error: err, |
||||||
|
header: undefined |
||||||
|
}; |
||||||
|
} else |
||||||
|
preamble.error = err; |
||||||
|
} |
||||||
|
|
||||||
|
assert.deepEqual(state.preamble, |
||||||
|
preamble, |
||||||
|
makeMsg(v.what, |
||||||
|
'Preamble mismatch:\nActual:' |
||||||
|
+ inspect(state.preamble) |
||||||
|
+ '\nExpected: ' |
||||||
|
+ inspect(preamble))); |
||||||
|
|
||||||
|
assert.equal(state.parts.length, |
||||||
|
v.nparts, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part count mismatch:\nActual: ' |
||||||
|
+ state.parts.length |
||||||
|
+ '\nExpected: ' |
||||||
|
+ v.nparts)); |
||||||
|
|
||||||
|
if (!v.npartErrors) |
||||||
|
v.npartErrors = 0; |
||||||
|
assert.equal(partErrors, |
||||||
|
v.npartErrors, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part errors mismatch:\nActual: ' |
||||||
|
+ partErrors |
||||||
|
+ '\nExpected: ' |
||||||
|
+ v.npartErrors)); |
||||||
|
|
||||||
|
for (var i = 0, header, body; i < v.nparts; ++i) { |
||||||
|
if (fs.existsSync(fixtureBase + '/part' + (i+1))) { |
||||||
|
body = fs.readFileSync(fixtureBase + '/part' + (i+1)); |
||||||
|
if (body.length === 0) |
||||||
|
body = undefined; |
||||||
|
} else |
||||||
|
body = undefined; |
||||||
|
assert.deepEqual(state.parts[i].body, |
||||||
|
body, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part #' + (i+1) + ' body mismatch')); |
||||||
|
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) { |
||||||
|
header = fs.readFileSync(fixtureBase |
||||||
|
+ '/part' + (i+1) + '.header', 'binary'); |
||||||
|
header = JSON.parse(header); |
||||||
|
} else |
||||||
|
header = undefined; |
||||||
|
assert.deepEqual(state.parts[i].header, |
||||||
|
header, |
||||||
|
makeMsg(v.what, |
||||||
|
'Part #' + (i+1) |
||||||
|
+ ' parsed header mismatch:\nActual: ' |
||||||
|
+ inspect(state.parts[i].header) |
||||||
|
+ '\nExpected: ' |
||||||
|
+ inspect(header))); |
||||||
|
} |
||||||
|
++t; |
||||||
|
next(); |
||||||
|
}); |
||||||
|
|
||||||
|
fs.createReadStream(fixtureBase + '/original').pipe(dicer); |
||||||
|
} |
||||||
|
next(); |
||||||
|
|
||||||
|
function makeMsg(what, msg) { |
||||||
|
return '[' + group + what + ']: ' + msg; |
||||||
|
} |
||||||
|
|
||||||
|
process.on('exit', function() { |
||||||
|
assert(t === tests.length, |
||||||
|
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests')); |
||||||
|
}); |
@ -0,0 +1,4 @@ |
|||||||
|
require('fs').readdirSync(__dirname).forEach(function(f) { |
||||||
|
if (f.substr(0, 5) === 'test-') |
||||||
|
require('./' + f); |
||||||
|
}); |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue